diff --git a/client/client.pro b/client/client.pro
index d6846ee..b62b1d1 100644
--- a/client/client.pro
+++ b/client/client.pro
@@ -21,9 +21,8 @@
TEMPLATE = app
CONFIG -= qt
-CONFIG += \
- ASAN \
- c99
+CONFIG += c99
+CONFIG += ASAN
DESTDIR = $$OUT_PWD/bin
SHARED = $$PWD/../shared
@@ -136,6 +135,7 @@ SOURCES = \
src/gui/msgbox.c \
src/gui/timer.c \
src/hangup.c \
+ src/runtime.c \
src/system/cache.c \
src/system/comport.c \
src/system/db.c \
diff --git a/client/src/file.c b/client/src/file.c
index 7843f23..c993886 100644
--- a/client/src/file.c
+++ b/client/src/file.c
@@ -137,7 +137,8 @@ static void fileCheckNext(void) {
// End of list!
logWrite("End of file list.\n");
arrfree(_current->files);
- _current->callback();
+ // Call with NULL to signify end of transfers.
+ _current->callback(NULL);
DEL(_current);
// See if there's more.
doRecheck = 1;
@@ -253,6 +254,8 @@ static void packetHandler(PacketDecodeDataT *packet) {
cacheEntryAdd(_currentSha256, cacheEntryNameGet(_file), _file);
imageCacheIfNeeded(_file, _currentSha256); // If this was an image, decompress it now.
DEL(_currentSha256);
+ // Call with filename to signify this file was updated.
+ _current->callback(_file);
// Next file!
fileCheckNext();
} else {
@@ -299,7 +302,7 @@ static void timTimerTimeout(WidgetT *widget) {
}
}
// If we're good so far, call the callback.
- if (!missing) _fileList[0]->callback();
+ if (!missing) _fileList[0]->callback(NULL);
arrfree(_fileList[0]->files);
arrdel(_fileList, 0);
}
diff --git a/client/src/file.h b/client/src/file.h
index 27c66ab..d153f02 100644
--- a/client/src/file.h
+++ b/client/src/file.h
@@ -25,7 +25,7 @@
#include "os.h"
-typedef void (*fileCallback)(void);
+typedef void (*fileCallback)(char *updatedFile);
void fileCacheCheck(fileCallback callback, char *vpaths[]);
diff --git a/client/src/linux/linux.c b/client/src/linux/linux.c
index b6e24ea..52d9714 100644
--- a/client/src/linux/linux.c
+++ b/client/src/linux/linux.c
@@ -626,7 +626,7 @@ uint8_t vbeStartup(uint16_t xRes, uint16_t yRes, uint8_t bpp) {
_windowScale = 3;
- _window = SDL_CreateWindow("GUI Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, xRes, yRes, SDL_WINDOW_ALLOW_HIGHDPI);
+ _window = SDL_CreateWindow("KangaWorld Debug Client", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, xRes, yRes, SDL_WINDOW_ALLOW_HIGHDPI);
_surface = SDL_GetWindowSurface(_window);
_renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
_texture = SDL_CreateTexture(_renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, xRes, yRes);
diff --git a/client/src/login.c b/client/src/login.c
index 6cf0d7d..15a945c 100644
--- a/client/src/login.c
+++ b/client/src/login.c
@@ -43,8 +43,6 @@ typedef enum LoginStateE {
} LoginStateT;
-static char *_shaClientDat = NULL;
-
static WindowT *_winLogin = NULL;
static TextboxT *_txtUser = NULL;
static TextboxT *_txtPass = NULL;
@@ -61,7 +59,7 @@ static void btnLoginClick(WidgetT *widget);
static void btnSignUpClick(WidgetT *widget);
static void btnMsgBoxCancel(MsgBoxButtonT button);
static void btnMsgBoxContinue(MsgBoxButtonT button);
-static void loginFilesReady(void);
+static void loginFilesReady(char *updatedFile);
static void packetHandler(PacketDecodeDataT *packet);
static void setButtons(uint8_t enabled);
static void timLoginProgress(WidgetT *widget);
@@ -114,74 +112,78 @@ static void btnMsgBoxContinue(MsgBoxButtonT button) {
}
-static void loginFilesReady(void) {
- char *p;
+static void loginFilesReady(char *updatedFile) {
BEGIN
- //***TODO*** Load into database if changed.
- p = cacheSha256Get("generated:client.dat");
+ // Is this an updated file or the end of the transfer list?
+ if (updatedFile) {
+ // client.dat was updated - read it into DB.
+ runtimeDataUpdate();
+ } else {
+ // End of cache update. Display UI.
+ runtimeDataLoad();
- // ***TODO*** We used to have a FORGOT PASSWORD link here, too.
+ // ***TODO*** We used to have a FORGOT PASSWORD link here, too.
- TagItemT uiLogin[] = {
- T_START,
- T_WINDOW, O(_winLogin),
- T_TITLE, P("Login"),
- T_WIDTH, 300, T_HEIGHT, 155,
-
- T_TEXTBOX, O(_txtUser),
- T_TITLE, P("User Name:"),
- T_X, 42, T_Y, 10,
- T_WIDTH, 200,
- T_LENGTH, shget(__runtimeData.integers, "maxUser"),
- T_TEXTBOX, T_DONE,
-
- T_TEXTBOX, O(_txtPass),
- T_TITLE, P(" Password:"),
- T_X, 42, T_Y, 40,
- T_WIDTH, 200,
- T_LENGTH, shget(__runtimeData.integers, "maxPass"),
- T_MASK, '*',
- T_TEXTBOX, T_DONE,
-
- T_BUTTON, O(_btnCancel),
- T_TITLE, P("Cancel"),
- T_X, 25, T_Y, 85,
- T_CLICK, P(btnCancelClick),
- T_BUTTON, T_DONE,
- T_BUTTON, O(_btnSignUp),
- T_TITLE, P("Sign Up"),
- T_X, 110, T_Y, 85,
- T_CLICK, P(btnSignUpClick),
- T_BUTTON, T_DONE,
- T_BUTTON, O(_btnLogin),
+ TagItemT uiLogin[] = {
+ T_START,
+ T_WINDOW, O(_winLogin),
T_TITLE, P("Login"),
- T_X, 199, T_Y, 85,
- T_CLICK, P(btnLoginClick),
- T_BUTTON, T_DONE,
- T_TIMER, O(_timProgress),
- T_EVENT, P(timLoginProgress),
- T_VALUE, 0,
- T_ENABLED, 0,
- T_TIMER, T_DONE,
+ T_WIDTH, 300, T_HEIGHT, 155,
- T_WINDOW, T_DONE,
- T_END
- };
+ T_TEXTBOX, O(_txtUser),
+ T_TITLE, P("User Name:"),
+ T_X, 42, T_Y, 10,
+ T_WIDTH, 200,
+ T_LENGTH, RUNTIME_INT("maxUser"),
+ T_TEXTBOX, T_DONE,
- tagListRun(uiLogin);
- _channel = netChannelGet(packetHandler);
+ T_TEXTBOX, O(_txtPass),
+ T_TITLE, P(" Password:"),
+ T_X, 42, T_Y, 40,
+ T_WIDTH, 200,
+ T_LENGTH, RUNTIME_INT("maxPass"),
+ T_MASK, '*',
+ T_TEXTBOX, T_DONE,
- textboxValueSet(_txtUser, "test");
- textboxValueSet(_txtPass, "test");
+ T_BUTTON, O(_btnCancel),
+ T_TITLE, P("Cancel"),
+ T_X, 25, T_Y, 85,
+ T_CLICK, P(btnCancelClick),
+ T_BUTTON, T_DONE,
+ T_BUTTON, O(_btnSignUp),
+ T_TITLE, P("Sign Up"),
+ T_X, 110, T_Y, 85,
+ T_CLICK, P(btnSignUpClick),
+ T_BUTTON, T_DONE,
+ T_BUTTON, O(_btnLogin),
+ T_TITLE, P("Login"),
+ T_X, 199, T_Y, 85,
+ T_CLICK, P(btnLoginClick),
+ T_BUTTON, T_DONE,
+ T_TIMER, O(_timProgress),
+ T_EVENT, P(timLoginProgress),
+ T_VALUE, 0,
+ T_ENABLED, 0,
+ T_TIMER, T_DONE,
+
+ T_WINDOW, T_DONE,
+ T_END
+ };
+
+ tagListRun(uiLogin);
+ _channel = netChannelGet(packetHandler);
+
+ textboxValueSet(_txtUser, "test");
+ textboxValueSet(_txtPass, "test");
+ }
END
}
void loginShow() {
- char *p;
char *fileList[] = {
"generated:client.dat",
NULL
@@ -189,14 +191,6 @@ void loginShow() {
BEGIN
- // Keep old SHA to know if we need to reload after updating.
- p = cacheSha256Get("generated:client.dat");
- if (p) {
- _shaClientDat = strdup(p);
- } else {
- _shaClientDat = NULL;
- }
-
fileCacheCheck(loginFilesReady, fileList);
END
diff --git a/client/src/main.c b/client/src/main.c
index 65d852f..2dd594c 100644
--- a/client/src/main.c
+++ b/client/src/main.c
@@ -21,6 +21,7 @@
/*
* To Do:
*
+ * - In-app video settings configuration with sensible auto-detected defaults
* - Replace any direct data manipulation from outside a class with methods to handle it
* - More widget states: Ghosted (underway)
* - Methods that can change the width of a widget (such as setTitle) need to repaint the parent window as well
@@ -31,6 +32,8 @@
* - No thumb in listbox scrollbar
* - Layout container widgets!
* - Fix variable names. something = local; _something = file global; __something = project global
+ * - Use https://qoiformat.org/ instead of PNG for faster decoding
+ * - Store unpacked images on surfaces instead of inside an ImageT for faster blitting and less RAM usage
*/
@@ -55,7 +58,7 @@
PacketThreadDataT *__packetThreadData = NULL; // Exported in os.h
-RuntimeDataT __runtimeData; // Exported in runtime.h
+
static MouseT *_mouse = NULL;
static ImageT *_pointer = NULL;
@@ -71,8 +74,6 @@ static void eventLoop(void);
static uint8_t hasValidSettings(void);
static void shutdown(void);
static uint8_t startup(int argc, char *argv[]);
-static void tableLoad(void);
-static void tableSave(void);
static void checkSettings(void) {
@@ -124,13 +125,12 @@ static void shutdown(void) {
imageUnload(&_pointer);
- tableSave();
-
netShutdown();
guiShutdown();
mouseShutdown();
surfaceShutdown();
vbeShutdown();
+ runtimeShutdown();
cacheShutdown();
dbShutdown();
configShutdown();
@@ -190,6 +190,7 @@ static uint8_t startup(int argc, char *argv[]) {
dbStartup();
cacheStartup(argv[0]);
+ runtimeStartup();
surfaceStartup();
mouseStartup();
guiStartup();
@@ -198,102 +199,10 @@ static uint8_t startup(int argc, char *argv[]) {
_pointer = imageLoadCache("gui:mouse.png");
_alpha = imagePixelGet(_pointer, 5, 0);
- tableLoad();
-
return 0;
}
-static void tableLoad(void) {
- FILE *cache = NULL;
- char *line = NULL;
- char *p = NULL;
- char *temp = NULL;
-
- __runtimeData.integers = NULL;
- __runtimeData.strings = NULL;
- __runtimeData.protocolVersion = 0;
-
- sh_new_strdup(__runtimeData.integers);
- sh_new_strdup(__runtimeData.strings);
-
- // ***TODO*** Default initial tables
-
- line = (char *)malloc(4096);
- if (line) {
- // Load string cache.
- cache = cacheFOpen("data:strings.dat", "rt");
- if (cache) {
- while (fscanf(cache, "%s\n", line) != EOF) {
- p = strstr(line, "=");
- if (p) {
- *p = 0;
- p++;
- // Do we have this string already?
- temp = shget(__runtimeData.strings, line);
- if (temp) {
- DEL(temp);
- shdel(__runtimeData.strings, line);
- }
- shput(__runtimeData.strings, line, strdup(p));
- }
- }
- cacheFClose(cache);
- }
- // Load integer cache.
- cache = cacheFOpen("data:integers.dat", "rt");
- if (cache) {
- while (fscanf(cache, "%s\n", line) != EOF) {
- p = strstr(line, "=");
- if (p) {
- *p = 0;
- p++;
- shput(__runtimeData.integers, line, atol(p));
- }
- }
- cacheFClose(cache);
- }
- free(line);
- line = NULL;
- }
-
-}
-
-
-static void tableSave(void) {
- FILE *cache = NULL;
-
- // Save & free integer table.
- cache = cacheFOpen("data:integers.dat", "wt");
- if (cache) {
- if (__runtimeData.integers) {
- while (shlen(__runtimeData.integers) > 0) {
- //logWrite("[%s]=[%d]\n", __runtimeData.integers[0].key, __runtimeData.integers[0].value);
- fprintf(cache, "%s=%ld\n", __runtimeData.integers[0].key, (long)__runtimeData.integers[0].value);
- shdel(__runtimeData.integers, __runtimeData.integers[0].key);
- }
- shfree(__runtimeData.integers);
- }
- cacheFClose(cache);
- }
-
- // Save & free string table.
- cache = cacheFOpen("data:strings.dat", "wt");
- if (cache) {
- if (__runtimeData.strings) {
- while (shlen(__runtimeData.strings) > 0) {
- //logWrite("[%s]=[%s]\n", __runtimeData.strings[0].key, __runtimeData.strings[0].value);
- fprintf(cache, "%s=%s\n", __runtimeData.strings[0].key, __runtimeData.strings[0].value);
- DEL(__runtimeData.strings[0].value);
- shdel(__runtimeData.strings, __runtimeData.strings[0].key);
- }
- shfree(__runtimeData.strings);
- }
- cacheFClose(cache);
- }
-}
-
-
extern void browserShow(void);
int main(int argc, char *argv[]) {
diff --git a/client/src/menu.c b/client/src/menu.c
index 87147c1..d763d49 100644
--- a/client/src/menu.c
+++ b/client/src/menu.c
@@ -1,4 +1,4 @@
-/*
+/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
@@ -20,6 +20,7 @@
#include "network.h"
#include "vesa.h"
+#include "db.h"
#include "taglist.h"
#include "msgbox.h"
@@ -36,17 +37,16 @@
static void btnMsgBoxLogoff(MsgBoxButtonT button);
static void btnMsgBoxOkay(MsgBoxButtonT button);
static void menuEnable(uint8_t enable);
-static void menuFilesReady(void);
+static void menuFilesReady(char *updatedFile);
static void picChatClick(WidgetT *widget);
static void picEmailClick(WidgetT *widget);
static void picForumsClick(WidgetT *widget);
static void picGamesClick(WidgetT *widget);
static void picLogoffClick(WidgetT *widget);
static void picProfileClick(WidgetT *widget);
+static void updateGameDatabase(void);
-static char *_shaGamesDat = NULL;
-
static PictureT *_picChat = NULL;
static PictureT *_picEmail = NULL;
static PictureT *_picForums = NULL;
@@ -91,51 +91,59 @@ static void menuEnable(uint8_t enable) {
}
-static void menuFilesReady(void) {
- uint16_t x = vbeDisplayWidthGet() - 49;
- uint16_t y = vbeDisplayHeightGet() - 49;
- char *p;
+static void menuFilesReady(char *updatedFile) {
+ uint16_t x;
+ uint16_t y;
BEGIN
- //***TODO*** Load into database if changed.
- p = cacheSha256Get("generated:games.dat");
+ // Did a file get updated or are we done?
+ if (updatedFile) {
+ // Was this the game database?
+ if (strcmp(updatedFile, "generated:games.dat") == 0) {
+ // Load changes into database.
+ updateGameDatabase();
+ }
+ } else {
+ // Show UI
+ x = vbeDisplayWidthGet() - 49;
+ y = vbeDisplayHeightGet() - 49;
- _picLogoff = pictureNew(x, y, "menu:48logoff.png");
- pictureClickHandlerSet(_picLogoff, picLogoffClick);
- guiAttach(guiRootGet(), W(_picLogoff));
- x -= 49;
+ _picLogoff = pictureNew(x, y, "menu:48logoff.png");
+ pictureClickHandlerSet(_picLogoff, picLogoffClick);
+ guiAttach(guiRootGet(), W(_picLogoff));
+ x -= 49;
- _picForums = pictureNew(x, y, "menu:48forums.png");
- pictureClickHandlerSet(_picForums, picForumsClick);
- guiAttach(guiRootGet(), W(_picForums));
- x -= 49;
+ _picForums = pictureNew(x, y, "menu:48forums.png");
+ pictureClickHandlerSet(_picForums, picForumsClick);
+ guiAttach(guiRootGet(), W(_picForums));
+ x -= 49;
- _picEmail = pictureNew(x, y, "menu:48email.png");
- pictureClickHandlerSet(_picEmail, picEmailClick);
- guiAttach(guiRootGet(), W(_picEmail));
- x -= 49;
+ _picEmail = pictureNew(x, y, "menu:48email.png");
+ pictureClickHandlerSet(_picEmail, picEmailClick);
+ guiAttach(guiRootGet(), W(_picEmail));
+ x -= 49;
- _picGames = pictureNew(x, y, "menu:48games.png");
- pictureClickHandlerSet(_picGames, picGamesClick);
- guiAttach(guiRootGet(), W(_picGames));
- x -= 49;
+ _picGames = pictureNew(x, y, "menu:48games.png");
+ pictureClickHandlerSet(_picGames, picGamesClick);
+ guiAttach(guiRootGet(), W(_picGames));
+ x -= 49;
- _picChat = pictureNew(x, y, "menu:48chat.png");
- pictureClickHandlerSet(_picChat, picChatClick);
- guiAttach(guiRootGet(), W(_picChat));
- x -= 49;
+ _picChat = pictureNew(x, y, "menu:48chat.png");
+ pictureClickHandlerSet(_picChat, picChatClick);
+ guiAttach(guiRootGet(), W(_picChat));
+ x -= 49;
- _picProfile = pictureNew(x, y, "menu:48profile.png");
- pictureClickHandlerSet(_picProfile, picProfileClick);
- guiAttach(guiRootGet(), W(_picProfile));
+ _picProfile = pictureNew(x, y, "menu:48profile.png");
+ pictureClickHandlerSet(_picProfile, picProfileClick);
+ guiAttach(guiRootGet(), W(_picProfile));
+ }
END
}
void menuShow(void) {
- char *p;
char *fileList[] = {
"menu:48chat.png",
"menu:48email.png",
@@ -149,14 +157,6 @@ void menuShow(void) {
BEGIN
- // Keep old SHA to know if we need to reload after updating.
- p = cacheSha256Get("generated:games.dat");
- if (p) {
- _shaGamesDat = strdup(p);
- } else {
- _shaGamesDat = NULL;
- }
-
fileCacheCheck(menuFilesReady, fileList);
END
@@ -208,3 +208,49 @@ static void picProfileClick(WidgetT *widget) {
menuEnable(0);
msgBoxOne("Options", MSGBOX_ICON_MESSAGE, "Yeah, this doesn't do anything yet.", "Okay", btnMsgBoxOkay);
}
+
+
+static void updateGameDatabase(void) {
+ FILE *f = NULL;
+ uint8_t c;
+
+ // Do we need to create the game database?
+ dbExecute(
+ "CREATE TABLE IF NOT EXISTS games ("
+ "title VARCHAR(255), "
+ "publisher VARCHAR(255), "
+ "developer VARCHAR(255), "
+ "description TEXT, "
+ "releaseDate VARCHAR(255), "
+ "rating VARCHAR(255), "
+ "series VARCHAR(255), "
+ "origin VARCHAR(255), "
+ "shortName VARCHAR(8) PRIMARY KEY, "
+ "worksWith VARCHAR(255), "
+ "type VARCHAR(8), "
+ "maxPlayers INTEGER, "
+ "joinable BOOLEAN, "
+ "screens INTEGER, "
+ "boxes INTEGER, "
+ "touched BOOLEAN "
+ ")",
+ NULL);
+
+ // Mark everything untouched.
+ dbExecute("UPDATE games SET touced=0", NULL);
+
+ // Process latest downloaded game list.
+ f = cacheFOpen("generated:games.dat", "rb");
+ if (f) {
+ while (1) {
+ // Get next byte.
+ c = fgetc(f);
+ // End of file?
+ if (feof(f)) break;
+
+ }
+ cacheFClose(f);
+ // Delete anything untouched.
+ dbExecute("DELETE FROM games WHERE touced=0", NULL);
+ }
+}
diff --git a/client/src/runtime.c b/client/src/runtime.c
new file mode 100644
index 0000000..0c32921
--- /dev/null
+++ b/client/src/runtime.c
@@ -0,0 +1,130 @@
+/*
+ * Kangaroo Punch MultiPlayer Game Server Mark II
+ * Copyright (C) 2020-2021 Scott Duensing
+ *
+ * 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 3 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, see .
+ *
+ */
+
+
+#include "db.h"
+#include "array.h"
+
+#include "runtime.h"
+
+
+RuntimeDataT __runtimeData;
+
+
+void runtimeDataLoad(void) {
+ uint16_t count = 0;
+ char ***records = NULL;
+ char **fields = NULL;
+
+ dbQueryMultiple(&records, "SELECT name, data FROM data", NULL);
+ if (records) {
+ for (count = 0; count < arrlen(records); count++) {
+ fields = records[count];
+ if (fields) {
+ shput(__runtimeData.strings, fields[0], strdup(fields[1]));
+ }
+ }
+ }
+
+ dbResultRelease(&records);
+
+ /*
+ logWrite("\n\n");
+ for (int r = 0; r < shlen(__runtimeData.strings); r++) {
+ logWrite("%s = %s\n", __runtimeData.strings[r].key, __runtimeData.strings[r].value);
+ }
+ logWrite("\n\n");
+ */
+}
+
+
+void runtimeDataUpdate(void) {
+ FILE *f;
+ uint8_t c;
+ char name[33];
+ char data[1025];
+ uint8_t which = 0; // 0 = Reading name, 1 = Reading data.
+ uint16_t x = 0;
+
+ f = cacheFOpen("generated:client.dat", "rb");
+ if (f) {
+ while (1) {
+ // Get next byte.
+ c = fgetc(f);
+ // End of file?
+ if (feof(f)) break;
+ // End of string?
+ if (c == 0) {
+ if (which == 0) {
+ // Got the name, move on to data.
+ which++;
+ name[x] = 0;
+ x = 0;
+ } else {
+ // Got data. Write both to database and start over.
+ which = 0;
+ data[x] = 0;
+ x = 0;
+ dbExecute(
+ "REPLACE INTO data (name, data) VALUES (?, ?)",
+ "vv",
+ name, data
+ );
+ }
+ continue;
+ }
+ // Got byte.
+ if (which == 0) {
+ name[x++] = c;
+ } else {
+ data[x++] = c;
+ }
+ }
+ cacheFClose(f);
+ }
+
+ runtimeDataLoad();
+}
+
+
+void runtimeShutdown(void) {
+ // Free string table.
+ while (shlen(__runtimeData.strings) > 0) {
+ DEL(__runtimeData.strings[0].value);
+ shdel(__runtimeData.strings, __runtimeData.strings[0].key);
+ }
+ shfree(__runtimeData.strings);
+}
+
+
+void runtimeStartup(void) {
+
+ __runtimeData.strings = NULL;
+ __runtimeData.protocolVersion = 0;
+
+ sh_new_strdup(__runtimeData.strings);
+
+ // Do we need to create the client runtime config database?
+ dbExecute(
+ "CREATE TABLE IF NOT EXISTS data ("
+ "name VARCHAR(32) PRIMARY KEY, "
+ "data VARCHAR(1024) "
+ ")",
+ NULL);
+}
diff --git a/client/src/runtime.h b/client/src/runtime.h
index 33be6db..347b847 100644
--- a/client/src/runtime.h
+++ b/client/src/runtime.h
@@ -25,25 +25,28 @@
#include "os.h"
+#define RUNTIME_INT(k) (atoi(shget(__runtimeData.strings, (k))))
+#define RUNTIME_STR(k) (shget(__runtimeData.strings, (k)))
+
+
typedef struct StringMapS {
char *key;
char *value;
} StringMapT;
-typedef struct IntegerMapS {
- char *key;
- int32_t value;
-} IntegerMapT;
-
-
typedef struct RuntimeDataS {
uint32_t protocolVersion;
StringMapT *strings;
- IntegerMapT *integers;
} RuntimeDataT;
extern RuntimeDataT __runtimeData;
+void runtimeDataLoad(void);
+void runtimeDataUpdate(void);
+void runtimeShutdown(void);
+void runtimeStartup(void);
+
+
#endif // RUNTIME_H
diff --git a/client/src/signup.c b/client/src/signup.c
index b11b204..4a96b61 100644
--- a/client/src/signup.c
+++ b/client/src/signup.c
@@ -188,7 +188,7 @@ void signupShow(void) {
T_TITLE, P(" User Name:"),
T_X, 40, T_Y, 64,
T_WIDTH, 300,
- T_LENGTH, shget(__runtimeData.integers, "maxUser"),
+ T_LENGTH, RUNTIME_INT("maxUser"),
T_TEXTBOX, T_DONE,
T_TEXTBOX, O(_txtPass1),
@@ -196,7 +196,7 @@ void signupShow(void) {
T_TITLE, P(" Password:"),
T_X, 40, T_Y, 94,
T_WIDTH, 300,
- T_LENGTH, shget(__runtimeData.integers, "maxPass"),
+ T_LENGTH, RUNTIME_INT("maxPass"),
T_MASK, '*',
T_TEXTBOX, T_DONE,
@@ -205,7 +205,7 @@ void signupShow(void) {
T_TITLE, P(" Password (again):"),
T_X, 40, T_Y, 124,
T_WIDTH, 300,
- T_LENGTH, shget(__runtimeData.integers, "maxPass"),
+ T_LENGTH, RUNTIME_INT("maxPass"),
T_MASK, '*',
T_TEXTBOX, T_DONE,
@@ -214,7 +214,7 @@ void signupShow(void) {
T_TITLE, P(" REAL First Name:"),
T_X, 40, T_Y, 154,
T_WIDTH, 300,
- T_LENGTH, shget(__runtimeData.integers, "maxName"),
+ T_LENGTH, RUNTIME_INT("maxName"),
T_TEXTBOX, T_DONE,
T_TEXTBOX, O(_txtLast),
@@ -222,7 +222,7 @@ void signupShow(void) {
T_TITLE, P(" REAL Last Name:"),
T_X, 40, T_Y, 184,
T_WIDTH, 300,
- T_LENGTH, shget(__runtimeData.integers, "maxName"),
+ T_LENGTH, RUNTIME_INT("maxName"),
T_TEXTBOX, T_DONE,
T_TEXTBOX, O(_txtEmail),
@@ -230,7 +230,7 @@ void signupShow(void) {
T_TITLE, P(" VALID E-Mail:"),
T_X, 40, T_Y, 214,
T_WIDTH, 300,
- T_LENGTH, shget(__runtimeData.integers, "maxEmail"),
+ T_LENGTH, RUNTIME_INT("maxEmail"),
T_TEXTBOX, T_DONE,
T_BUTTON, O(_btnCancel),
@@ -363,8 +363,8 @@ static uint8_t validateName(char *username) {
char *allowed;
char needle[2];
- if ((int32_t)strlen(username) < shget(__runtimeData.integers, "minName")) return 0;
- if ((int32_t)strlen(username) > shget(__runtimeData.integers, "maxName")) return 0;
+ if ((int32_t)strlen(username) < RUNTIME_INT("minName")) return 0;
+ if ((int32_t)strlen(username) > RUNTIME_INT("maxName")) return 0;
allowed = shget(__runtimeData.strings, "nameAllowed");
needle[1] = 0;
@@ -389,13 +389,13 @@ static uint8_t validatePassword(char *password) {
char *passNumeric;
char needle[2];
- if ((int32_t)strlen(password) < shget(__runtimeData.integers, "minPass")) return 0;
- if ((int32_t)strlen(password) > shget(__runtimeData.integers, "maxPass")) return 0;
+ if ((int32_t)strlen(password) < RUNTIME_INT("minPass")) return 0;
+ if ((int32_t)strlen(password) > RUNTIME_INT("maxPass")) return 0;
- passLower = shget(__runtimeData.strings, "passLower");
- passUpper = shget(__runtimeData.strings, "passUpper");
- passSpecial = shget(__runtimeData.strings, "passSpecial");
- passNumeric = shget(__runtimeData.strings, "passNumeric");
+ passLower = RUNTIME_STR("passLower");
+ passUpper = RUNTIME_STR("passUpper");
+ passSpecial = RUNTIME_STR("passSpecial");
+ passNumeric = RUNTIME_STR("passNumeric");
needle[1] = 0;
for (x=0; x shget(__runtimeData.integers, "maxUser")) return 0;
+ if ((int32_t)strlen(username) < RUNTIME_INT("minUser")) return 0;
+ if ((int32_t)strlen(username) > RUNTIME_INT("maxUser")) return 0;
- allowed = shget(__runtimeData.strings, "userAllowed");
+ allowed = RUNTIME_STR("userAllowed");
needle[1] = 0;
for (x=0; x 0) {
+ arrput(fields, strdup((char *)sqlite3_column_text(stmt, x++)));
+ }
+ arrput(records, fields);
+ fields = NULL;
+ }
+ }
+
+ /*
+ logWrite("\n\n");
+ for (int r = 0; r < arrlen(records); r++) {
+ fields = records[r];
+ for (int f = 0; f < arrlen(fields); f++) {
+ logWrite("%s\t", fields[f]);
+ }
+ logWrite("\n");
+ }
+ logWrite("\n\n");
+ */
+
+ sqlite3_finalize(stmt);
+
+ *result = records;
+
+ return (r == SQLITE_OK || r == SQLITE_DONE) ? SUCCESS : FAIL;
+}
+
+
+
uint8_t dbQuerySingle(char rformat, void **result, char *sql, char *format, ...) {
va_list args;
sqlite3_stmt *stmt = NULL;
- int32_t r;
- static double d;
- static int32_t i;
+ int32_t r = 0;
+ static double d = 0;
+ static int32_t i = 0;
static char *v = NULL;
if (v) DEL(v);
+ // If everything is NULL, we're cleaning up internal variables (which we do before this). Just exit.
+ if (rformat == 0 && result == NULL && sql == NULL && format == NULL) return SUCCESS;
+
if (format == NULL) {
// No parameters, just run it.
r = sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL);
@@ -140,11 +197,32 @@ uint8_t dbQuerySingle(char rformat, void **result, char *sql, char *format, ...)
sqlite3_finalize(stmt);
- return r == SQLITE_OK ? SUCCESS : FAIL;
+ return (r == SQLITE_OK || r == SQLITE_DONE || r == SQLITE_ROW) ? SUCCESS : FAIL;
+}
+
+
+void dbResultRelease(char ****result) {
+ char ***records = *result;
+ char **fields = NULL;
+ char *value = NULL;
+
+ while (arrlen(records) > 0) {
+ fields = records[0];
+ while (arrlen(fields) > 0) {
+ value = fields[0];
+ DEL(value);
+ arrdel(fields, 0);
+ }
+ arrdel(records, 0);
+ }
+ arrfree(records);
+
+ result = NULL;
}
void dbShutdown(void) {
+ dbQuerySingle(0, NULL, NULL, NULL); // Release memory held by dbQuerySingle.
sqlite3_close(_db);
_db = NULL;
}
diff --git a/client/src/system/db.h b/client/src/system/db.h
index 8008220..02260cd 100644
--- a/client/src/system/db.h
+++ b/client/src/system/db.h
@@ -29,7 +29,9 @@
uint8_t dbExecute(char *sql, char *format, ...);
+uint8_t dbQueryMultiple(char ****result, char *sql, char *format, ...);
uint8_t dbQuerySingle(char rformat, void **result, char *sql, char *format, ...);
+void dbResultRelease(char ****result);
void dbShutdown(void);
void dbStartup(void);
diff --git a/server/src/database.c b/server/src/database.c
index a343607..a34e8a6 100644
--- a/server/src/database.c
+++ b/server/src/database.c
@@ -298,6 +298,7 @@ void dbGameRelease(DbGameT **game) {
DEL(g->series);
DEL(g->origin);
DEL(g->shortName);
+ DEL(g->worksWith);
DEL(g);
}
}
@@ -312,6 +313,8 @@ DbGameT **dbGamesGet(void) {
int32_t i = 0;
DbGameT **gameList = NULL;
DbGameT *game = NULL;
+ char filename[MAX_PATH];
+ int32_t x = 0;
pthread_mutex_lock(&_mutex);
@@ -321,8 +324,8 @@ DbGameT **dbGamesGet(void) {
}
p += sprintf(p,
- "SELECT title, publisher, developer, description, releaseDate, "
- "rating, series, origin, shortName, type, maxPlayers, joinable "
+ "SELECT title, publisher, developer, description, releaseDate, rating, series, "
+ "origin, shortName, worksWith, type, maxPlayers, joinable "
"FROM games WHERE active=1");
if (mysql_real_query(_sql, statement, p - statement) != 0) {
logWrite("dbGamesGet: %s\n", mysql_error(_sql));
@@ -357,13 +360,31 @@ DbGameT **dbGamesGet(void) {
game->series = strdup(row[6]);
game->origin = strdup(row[7]);
game->shortName = strdup(row[8]);
+ game->worksWith = strdup(row[9]);
game->type = GAME_TYPE_UNKNOWN;
- game->maxPlayers = atoi(row[10]);
- game->joinable = atoi(row[11]);
- if (!strcasecmp(row[9], "DOOR")) game->type = GAME_TYPE_DOOR;
- if (!strcasecmp(row[9], "SERIAL")) game->type = GAME_TYPE_SERIAL;
- if (!strcasecmp(row[9], "IPX")) game->type = GAME_TYPE_IPX;
- if (!strcasecmp(row[9], "FICTION")) game->type = GAME_TYPE_FICTION;
+ if (!strcasecmp(row[10], "DOOR")) game->type = GAME_TYPE_DOOR;
+ if (!strcasecmp(row[10], "SERIAL")) game->type = GAME_TYPE_SERIAL;
+ if (!strcasecmp(row[10], "IPX")) game->type = GAME_TYPE_IPX;
+ if (!strcasecmp(row[10], "FICTION")) game->type = GAME_TYPE_FICTION;
+ game->maxPlayers = atoi(row[11]);
+ game->joinable = atoi(row[12]);
+
+ // Find screenshot and box image count from filesystem.
+ x = 0;
+ while (1) {
+ snprintf(filename, MAX_PATH, "%s/games/%c/%s/screen%d.png", __settingsFile, game->shortName[0], game->shortName, x + 1);
+ if (!utilFileExists(filename)) break;
+ x++;
+ }
+ game->screens = x;
+ x = 0;
+ while (1) {
+ snprintf(filename, MAX_PATH, "%s/games/%c/%s/box%d.png", __settingsFile, game->shortName[0], game->shortName, x + 1);
+ if (!utilFileExists(filename)) break;
+ x++;
+ }
+ game->boxes = x;
+
arrput(gameList, game);
}
}
diff --git a/server/src/database.h b/server/src/database.h
index 477023b..933e4cc 100644
--- a/server/src/database.h
+++ b/server/src/database.h
@@ -62,9 +62,12 @@ typedef struct DbGameS {
char *series;
char *origin;
char *shortName;
+ char *worksWith;
DbGameTypeT type;
uint8_t maxPlayers;
uint8_t joinable;
+ uint8_t screens;
+ uint8_t boxes;
} DbGameT;
diff --git a/server/src/update.c b/server/src/update.c
index 3fa3732..08250b1 100644
--- a/server/src/update.c
+++ b/server/src/update.c
@@ -233,9 +233,12 @@ static void updateGames(void) {
fwrite(game->series, strlen(game->series) + 1, 1, f);
fwrite(game->origin, strlen(game->origin) + 1, 1, f);
fwrite(game->shortName, strlen(game->shortName) + 1, 1, f);
+ fwrite(game->worksWith, strlen(game->worksWith) + 1, 1, f);
c = game->type; fputc(c, f);
c = game->maxPlayers; fputc(c, f);
c = game->joinable; fputc(c, f);
+ c = game->screens; fputc(c, f);
+ c = game->boxes; fputc(c, f);
dbGameRelease(&game);
arrdel(gameList, 0);
diff --git a/shared/util.c b/shared/util.c
index 91d1cb9..b5877a7 100644
--- a/shared/util.c
+++ b/shared/util.c
@@ -105,6 +105,18 @@ void utilDie(const char *why, ...) {
}
+uint8_t utilFileExists(char *filename) {
+ FILE *f = fopen(filename, "rb");
+
+ if (f) {
+ fclose(f);
+ return SUCCESS;
+ }
+
+ return FAIL;
+}
+
+
char **utilWrapText(char *text, uint16_t width) {
char **lines = NULL;
char *head = text;
diff --git a/shared/util.h b/shared/util.h
index 24eeff1..610622b 100644
--- a/shared/util.h
+++ b/shared/util.h
@@ -25,12 +25,13 @@
#include "os.h"
-char *utilAppNameWithNewExtensionGet(char *appName, char *extension);
-void utilBitsPrint(uint8_t byte);
-char *utilCreateString(char *format, ...);
-char *utilCreateStringVArgs(char *format, va_list args);
-void utilDie(const char *why, ...);
-char **utilWrapText(char *text, uint16_t width);
+char *utilAppNameWithNewExtensionGet(char *appName, char *extension);
+void utilBitsPrint(uint8_t byte);
+char *utilCreateString(char *format, ...);
+char *utilCreateStringVArgs(char *format, va_list args);
+void utilDie(const char *why, ...);
+uint8_t utilFileExists(char *filename);
+char **utilWrapText(char *text, uint16_t width);
#endif // UTIL_H