From 794bd96d3f14076efd4132b48e3d95ca1044a2e9 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Sat, 9 Apr 2022 19:35:23 -0500 Subject: [PATCH] New configuration system working. Game database import started. --- client/client.pro | 6 +- client/src/file.c | 7 ++- client/src/file.h | 2 +- client/src/linux/linux.c | 2 +- client/src/login.c | 122 +++++++++++++++++------------------- client/src/main.c | 103 ++----------------------------- client/src/menu.c | 130 ++++++++++++++++++++++++++------------- client/src/runtime.c | 130 +++++++++++++++++++++++++++++++++++++++ client/src/runtime.h | 17 ++--- client/src/signup.c | 42 ++++++------- client/src/system/db.c | 86 ++++++++++++++++++++++++-- client/src/system/db.h | 2 + server/src/database.c | 37 ++++++++--- server/src/database.h | 3 + server/src/update.c | 3 + shared/util.c | 12 ++++ shared/util.h | 13 ++-- 17 files changed, 461 insertions(+), 256 deletions(-) create mode 100644 client/src/runtime.c 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