mirror of
https://github.com/cxong/cdogs-sdl.git
synced 2025-07-23 07:23:01 +02:00
Base64 encode high score entries #151
Also include explosives in favourites
This commit is contained in:
@@ -20,6 +20,7 @@ set(CDOGS_SDL_SOURCES
|
||||
ammo_menu.c
|
||||
animated_counter.c
|
||||
autosave.c
|
||||
base64/base64.c
|
||||
briefing_screens.c
|
||||
cdogs.c
|
||||
command_line.c
|
||||
@@ -46,6 +47,7 @@ set(CDOGS_SDL_HEADERS
|
||||
ammo_menu.h
|
||||
animated_counter.h
|
||||
autosave.h
|
||||
base64/base64.h
|
||||
briefing_screens.h
|
||||
command_line.h
|
||||
credits.h
|
||||
|
4
src/base64/README.txt
Normal file
4
src/base64/README.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Base64 C Implementation
|
||||
|
||||
License:
|
||||
Public Domain
|
164
src/base64/base64.c
Normal file
164
src/base64/base64.c
Normal file
@@ -0,0 +1,164 @@
|
||||
/* This is a public domain base64 implementation written by WEI Zhicheng. */
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
#define BASE64_PAD '='
|
||||
#define BASE64DE_FIRST '+'
|
||||
#define BASE64DE_LAST 'z'
|
||||
|
||||
/* BASE 64 encode table */
|
||||
static const char base64en[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '+', '/',
|
||||
};
|
||||
|
||||
/* ASCII order for BASE 64 decode, 255 in unused character */
|
||||
static const unsigned char base64de[] = {
|
||||
/* nul, soh, stx, etx, eot, enq, ack, bel, */
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
|
||||
/* bs, ht, nl, vt, np, cr, so, si, */
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
|
||||
/* dle, dc1, dc2, dc3, dc4, nak, syn, etb, */
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
|
||||
/* can, em, sub, esc, fs, gs, rs, us, */
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
|
||||
/* sp, '!', '"', '#', '$', '%', '&', ''', */
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
|
||||
/* '(', ')', '*', '+', ',', '-', '.', '/', */
|
||||
255, 255, 255, 62, 255, 255, 255, 63,
|
||||
|
||||
/* '0', '1', '2', '3', '4', '5', '6', '7', */
|
||||
52, 53, 54, 55, 56, 57, 58, 59,
|
||||
|
||||
/* '8', '9', ':', ';', '<', '=', '>', '?', */
|
||||
60, 61, 255, 255, 255, 255, 255, 255,
|
||||
|
||||
/* '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', */
|
||||
255, 0, 1, 2, 3, 4, 5, 6,
|
||||
|
||||
/* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', */
|
||||
7, 8, 9, 10, 11, 12, 13, 14,
|
||||
|
||||
/* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', */
|
||||
15, 16, 17, 18, 19, 20, 21, 22,
|
||||
|
||||
/* 'X', 'Y', 'Z', '[', '\', ']', '^', '_', */
|
||||
23, 24, 25, 255, 255, 255, 255, 255,
|
||||
|
||||
/* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', */
|
||||
255, 26, 27, 28, 29, 30, 31, 32,
|
||||
|
||||
/* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', */
|
||||
33, 34, 35, 36, 37, 38, 39, 40,
|
||||
|
||||
/* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', */
|
||||
41, 42, 43, 44, 45, 46, 47, 48,
|
||||
|
||||
/* 'x', 'y', 'z', '{', '|', '}', '~', del, */
|
||||
49, 50, 51, 255, 255, 255, 255, 255
|
||||
};
|
||||
|
||||
unsigned int
|
||||
base64_encode(const unsigned char *in, unsigned int inlen, char *out)
|
||||
{
|
||||
int s;
|
||||
unsigned int i;
|
||||
unsigned int j;
|
||||
unsigned char c;
|
||||
unsigned char l;
|
||||
|
||||
s = 0;
|
||||
l = 0;
|
||||
for (i = j = 0; i < inlen; i++) {
|
||||
c = in[i];
|
||||
|
||||
switch (s) {
|
||||
case 0:
|
||||
s = 1;
|
||||
out[j++] = base64en[(c >> 2) & 0x3F];
|
||||
break;
|
||||
case 1:
|
||||
s = 2;
|
||||
out[j++] = base64en[((l & 0x3) << 4) | ((c >> 4) & 0xF)];
|
||||
break;
|
||||
case 2:
|
||||
s = 0;
|
||||
out[j++] = base64en[((l & 0xF) << 2) | ((c >> 6) & 0x3)];
|
||||
out[j++] = base64en[c & 0x3F];
|
||||
break;
|
||||
}
|
||||
l = c;
|
||||
}
|
||||
|
||||
switch (s) {
|
||||
case 1:
|
||||
out[j++] = base64en[(l & 0x3) << 4];
|
||||
out[j++] = BASE64_PAD;
|
||||
out[j++] = BASE64_PAD;
|
||||
break;
|
||||
case 2:
|
||||
out[j++] = base64en[(l & 0xF) << 2];
|
||||
out[j++] = BASE64_PAD;
|
||||
break;
|
||||
}
|
||||
|
||||
out[j] = 0;
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
unsigned int
|
||||
base64_decode(const char *in, unsigned int inlen, unsigned char *out)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int j;
|
||||
unsigned char c;
|
||||
|
||||
if (inlen & 0x3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = j = 0; i < inlen; i++) {
|
||||
if (in[i] == BASE64_PAD) {
|
||||
break;
|
||||
}
|
||||
if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
c = base64de[(unsigned char)in[i]];
|
||||
if (c == 255) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (i & 0x3) {
|
||||
case 0:
|
||||
out[j] = (c << 2) & 0xFF;
|
||||
break;
|
||||
case 1:
|
||||
out[j++] |= (c >> 4) & 0x3;
|
||||
out[j] = (c & 0xF) << 4;
|
||||
break;
|
||||
case 2:
|
||||
out[j++] |= (c >> 2) & 0xF;
|
||||
out[j] = (c & 0x3) << 6;
|
||||
break;
|
||||
case 3:
|
||||
out[j++] |= c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
20
src/base64/base64.h
Normal file
20
src/base64/base64.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
#define BASE64_ENCODE_OUT_SIZE(s) ((unsigned int)((((s) + 2) / 3) * 4 + 1))
|
||||
#define BASE64_DECODE_OUT_SIZE(s) ((unsigned int)(((s) / 4) * 3))
|
||||
|
||||
/*
|
||||
* out is null-terminated encode string.
|
||||
* return values is out length, exclusive terminating `\0'
|
||||
*/
|
||||
unsigned int
|
||||
base64_encode(const unsigned char *in, unsigned int inlen, char *out);
|
||||
|
||||
/*
|
||||
* return values is out length
|
||||
*/
|
||||
unsigned int
|
||||
base64_decode(const char *in, unsigned int inlen, unsigned char *out);
|
||||
|
||||
#endif /* BASE64_H */
|
41
src/base64/main.c
Normal file
41
src/base64/main.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
static void
|
||||
test(unsigned char *encode, unsigned int encodelen,
|
||||
char *decode, unsigned int decodelen)
|
||||
{
|
||||
char *encode_out;;
|
||||
unsigned char *decode_out;
|
||||
|
||||
encode_out = malloc(BASE64_ENCODE_OUT_SIZE(encodelen));
|
||||
decode_out = malloc(BASE64_DECODE_OUT_SIZE(decodelen));
|
||||
assert(encode_out);
|
||||
assert(decode_out);
|
||||
|
||||
assert(base64_encode(encode, encodelen, encode_out) == decodelen);
|
||||
assert(memcmp(encode_out, decode, decodelen) == 0);
|
||||
assert(base64_decode(decode, decodelen, decode_out) == encodelen);
|
||||
assert(memcmp(decode_out, encode, encodelen) == 0);
|
||||
|
||||
free(encode_out);
|
||||
free(decode_out);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
test((void *)"", 0, "", 0);
|
||||
test((void *)"f", 1, "Zg==", 4);
|
||||
test((void *)"fo", 2, "Zm8=", 4);
|
||||
test((void *)"foo", 3, "Zm9v", 4);
|
||||
test((void *)"foob", 4, "Zm9vYg==", 8);
|
||||
test((void *)"fooba", 5, "Zm9vYmE=", 8);
|
||||
test((void *)"foobar", 6, "Zm9vYmFy", 8);
|
||||
|
||||
return 0;
|
||||
}
|
@@ -38,8 +38,7 @@ void WeaponUsagesTerminate(WeaponUsages wu)
|
||||
void WeaponUsagesUpdate(
|
||||
WeaponUsages wu, const WeaponClass *wc, const int dShot, const int dHit)
|
||||
{
|
||||
// TODO: accuracy for explosives?
|
||||
if (!wc || !wc->IsRealGun || WeaponClassIsHighDPS(wc))
|
||||
if (!wc || !wc->IsRealGun)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -76,14 +75,18 @@ float WeaponUsagesGetAccuracy(const WeaponUsages wu)
|
||||
static int GetAccuracyItem(any_t data, any_t item)
|
||||
{
|
||||
NWeaponUsage *w = item;
|
||||
// Don't include explosives in accuracy
|
||||
if (w->Shots > 0)
|
||||
{
|
||||
GetAccuracyData gData;
|
||||
gData.accuracy = (float)w->Hits / w->Shots;
|
||||
const WeaponClass *wc = StrWeaponClass(w->Weapon);
|
||||
gData.weight = MAX(wc->Lock, 1);
|
||||
CArray *accuracies = data;
|
||||
CArrayPushBack(accuracies, &gData);
|
||||
if (!WeaponClassIsHighDPS(wc))
|
||||
{
|
||||
GetAccuracyData gData;
|
||||
gData.accuracy = (float)w->Hits / w->Shots;
|
||||
gData.weight = MAX(wc->Lock, 1);
|
||||
CArray *accuracies = data;
|
||||
CArrayPushBack(accuracies, &gData);
|
||||
}
|
||||
}
|
||||
return MAP_OK;
|
||||
}
|
||||
@@ -106,6 +109,8 @@ static int GetFavoriteItem(any_t data, any_t item)
|
||||
GetFavoriteData *gData = data;
|
||||
NWeaponUsage *w = item;
|
||||
const WeaponClass *wc = StrWeaponClass(w->Weapon);
|
||||
// TODO: this overcounts guns with multiple bullets per shot
|
||||
// Need to divide by this multiple
|
||||
const int ticks = w->Shots * MAX(wc->Lock, 1);
|
||||
if (ticks > gData->ticks)
|
||||
{
|
||||
|
118
src/hiscores.c
118
src/hiscores.c
@@ -1,28 +1,7 @@
|
||||
/*
|
||||
C-Dogs SDL
|
||||
A port of the legendary (and fun) action/arcade cdogs.
|
||||
Copyright (C) 1995 Ronny Wester
|
||||
Copyright (C) 2003 Jeremy Chin
|
||||
Copyright (C) 2003-2007 Lucas Martin-King
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
This file incorporates work covered by the following copyright and
|
||||
permission notice:
|
||||
|
||||
Copyright (c) 2013-2014, 2017, 2021, 2025 Cong Xu
|
||||
Copyright (c) 2025 Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -64,6 +43,8 @@
|
||||
#include <cdogs/log.h>
|
||||
#include <cdogs/yajl_utils.h>
|
||||
|
||||
#include "base64/base64.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
@@ -76,8 +57,6 @@ typedef struct
|
||||
time_t Time;
|
||||
Character Character;
|
||||
NPlayerStats Stats;
|
||||
int Missions;
|
||||
int LastMission;
|
||||
WeaponUsages WeaponUsages;
|
||||
} HighScoreEntry;
|
||||
// High score structure is:
|
||||
@@ -206,8 +185,6 @@ static bool EnterHighScore(const PlayerData *data, CArray *scores)
|
||||
CSTRDUP(entry->Name, data->name);
|
||||
CharacterCopy(&entry->Character, &data->Char, NULL);
|
||||
entry->Stats = data->Totals;
|
||||
entry->Missions = data->missions;
|
||||
entry->LastMission = data->lastMission;
|
||||
entry->WeaponUsages = hashmap_copy(data->WeaponUsages, NULL);
|
||||
return true;
|
||||
}
|
||||
@@ -309,7 +286,6 @@ static void DisplayPage(const CArray *entries)
|
||||
{
|
||||
if (e->Stats.Score == localScores[i])
|
||||
{
|
||||
// TODO: don't just highlight, display character next to entry
|
||||
isHighlighted = true;
|
||||
localScores[i] = 0;
|
||||
break;
|
||||
@@ -382,6 +358,7 @@ static int SaveHighScoreEntries(any_t data, any_t key)
|
||||
CASSERT(false, "cannot find high score entry");
|
||||
return error;
|
||||
}
|
||||
|
||||
#define YAJL_CHECK(func) \
|
||||
{ \
|
||||
yajl_gen_status _status = func; \
|
||||
@@ -397,44 +374,64 @@ static int SaveHighScoreEntries(any_t data, any_t key)
|
||||
YAJL_CHECK(yajl_gen_array_open(g));
|
||||
|
||||
CA_FOREACH(const HighScoreEntry, entry, *entries)
|
||||
YAJL_CHECK(yajl_gen_map_open(g));
|
||||
|
||||
YAJL_CHECK(YAJLAddStringPair(g, "Name", entry->Name));
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "Time", (int)entry->Time));
|
||||
// Save entry as a base64-encoded YAJL string
|
||||
yajl_gen g2 = yajl_gen_alloc(NULL);
|
||||
if (g2 == NULL)
|
||||
{
|
||||
LOG(LM_MAIN, LL_ERROR,
|
||||
"Unable to alloc JSON generator for saving high score entry\n");
|
||||
return MAP_MISSING;
|
||||
}
|
||||
YAJL_CHECK(yajl_gen_map_open(g2));
|
||||
|
||||
YAJL_CHECK(YAJLAddStringPair(g2, "Name", entry->Name));
|
||||
YAJL_CHECK(YAJLAddIntPair(g2, "Time", (int)entry->Time));
|
||||
|
||||
YAJL_CHECK(yajl_gen_string(
|
||||
g, (const unsigned char *)"Character", strlen("Character")));
|
||||
if (!CharacterSave(g, &entry->Character))
|
||||
g2, (const unsigned char *)"Character", strlen("Character")));
|
||||
if (!CharacterSave(g2, &entry->Character))
|
||||
{
|
||||
return MAP_MISSING;
|
||||
}
|
||||
|
||||
// TODO: base64 encode
|
||||
YAJL_CHECK(
|
||||
yajl_gen_string(g, (const unsigned char *)"Stats", strlen("Stats")));
|
||||
YAJL_CHECK(yajl_gen_map_open(g));
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "Score", entry->Stats.Score));
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "Kills", entry->Stats.Kills));
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "Suicides", entry->Stats.Suicides));
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "Friendlies", entry->Stats.Friendlies));
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "TimeTicks", entry->Stats.TimeTicks));
|
||||
YAJL_CHECK(yajl_gen_map_close(g));
|
||||
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "Missions", entry->Missions));
|
||||
YAJL_CHECK(YAJLAddIntPair(g, "LastMission", entry->LastMission));
|
||||
yajl_gen_string(g2, (const unsigned char *)"Stats", strlen("Stats")));
|
||||
YAJL_CHECK(yajl_gen_map_open(g2));
|
||||
YAJL_CHECK(YAJLAddIntPair(g2, "Score", entry->Stats.Score));
|
||||
YAJL_CHECK(YAJLAddIntPair(g2, "Kills", entry->Stats.Kills));
|
||||
YAJL_CHECK(YAJLAddIntPair(g2, "Suicides", entry->Stats.Suicides));
|
||||
YAJL_CHECK(YAJLAddIntPair(g2, "Friendlies", entry->Stats.Friendlies));
|
||||
YAJL_CHECK(YAJLAddIntPair(g2, "TimeTicks", entry->Stats.TimeTicks));
|
||||
YAJL_CHECK(yajl_gen_map_close(g2));
|
||||
|
||||
YAJL_CHECK(yajl_gen_string(
|
||||
g, (const unsigned char *)"WeaponUsages", strlen("WeaponUsages")));
|
||||
YAJL_CHECK(yajl_gen_map_open(g));
|
||||
// TODO: weapon usage
|
||||
const int res = hashmap_iterate(entry->WeaponUsages, SaveWeaponUsage, g);
|
||||
g2, (const unsigned char *)"WeaponUsages", strlen("WeaponUsages")));
|
||||
YAJL_CHECK(yajl_gen_map_open(g2));
|
||||
const int res = hashmap_iterate(entry->WeaponUsages, SaveWeaponUsage, g2);
|
||||
if (res != MAP_OK)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
YAJL_CHECK(yajl_gen_map_close(g));
|
||||
YAJL_CHECK(yajl_gen_map_close(g2));
|
||||
|
||||
YAJL_CHECK(yajl_gen_map_close(g));
|
||||
YAJL_CHECK(yajl_gen_map_close(g2));
|
||||
|
||||
// Convert to base64 string
|
||||
const char *buf;
|
||||
size_t len;
|
||||
yajl_gen_get_buf(g2, (const unsigned char **)&buf, &len);
|
||||
char *encodeOut;
|
||||
CMALLOC(encodeOut, BASE64_ENCODE_OUT_SIZE(len));
|
||||
const unsigned encodeLen = base64_encode((const unsigned char *)buf, (unsigned int)len, encodeOut);
|
||||
YAJL_CHECK(
|
||||
yajl_gen_string(g, (const unsigned char *)encodeOut, encodeLen));
|
||||
if (g2)
|
||||
{
|
||||
yajl_gen_clear(g2);
|
||||
yajl_gen_free(g2);
|
||||
}
|
||||
CFREE(encodeOut);
|
||||
CA_FOREACH_END()
|
||||
YAJL_CHECK(yajl_gen_array_close(g));
|
||||
#undef YAJL_CHECK
|
||||
@@ -535,7 +532,23 @@ static map_t LoadHighScores(void)
|
||||
CArrayInit(entries, sizeof(HighScoreEntry));
|
||||
for (int j = 0; j < (int)entriesNode->len; j++)
|
||||
{
|
||||
yajl_val entryNode = entriesNode->values[j];
|
||||
// Decode base64-encoded entries
|
||||
const char *entryEncoded = YAJL_GET_STRING(entriesNode->values[j]);
|
||||
char *decodeOut;
|
||||
const size_t decodeLen = strlen(entryEncoded);
|
||||
CMALLOC(decodeOut, BASE64_DECODE_OUT_SIZE(decodeLen));
|
||||
base64_decode(entryEncoded, (unsigned int)decodeLen, (unsigned char *)decodeOut);
|
||||
char errbuf[1024];
|
||||
yajl_val entryNode =
|
||||
yajl_tree_parse(decodeOut, errbuf, sizeof errbuf);
|
||||
if (entryNode == NULL)
|
||||
{
|
||||
LOG(LM_MAIN, LL_ERROR,
|
||||
"Unexpected format for high score entry %s %d\n", path, j);
|
||||
continue;
|
||||
}
|
||||
CFREE(decodeOut);
|
||||
|
||||
HighScoreEntry entry;
|
||||
memset(&entry, 0, sizeof entry);
|
||||
entry.WeaponUsages = WeaponUsagesNew();
|
||||
@@ -596,9 +609,6 @@ static map_t LoadHighScores(void)
|
||||
YAJLInt((int *)&entry.Stats.Friendlies, statsNode, "Friendlies");
|
||||
YAJLInt((int *)&entry.Stats.TimeTicks, statsNode, "TimeTicks");
|
||||
|
||||
YAJLInt(&entry.Missions, entryNode, "Missions");
|
||||
YAJLInt(&entry.LastMission, entryNode, "LastMission");
|
||||
|
||||
const char *wuPath[] = {"WeaponUsages", NULL};
|
||||
yajl_val wuNode = yajl_tree_get(entryNode, wuPath, yajl_t_object);
|
||||
if (!wuNode || !YAJL_IS_OBJECT(wuNode))
|
||||
|
Reference in New Issue
Block a user