diff --git a/client/client.pro b/client/client.pro index aba09f4..3119489 100644 --- a/client/client.pro +++ b/client/client.pro @@ -150,7 +150,8 @@ SOURCES = \ src/welcome.c LIBS = \ - -lSDL2 + -lSDL2 \ + -lSDL2_image OTHER_FILES = \ $$DOS_HEADERS \ diff --git a/client/src/linux/linux.c b/client/src/linux/linux.c index 57d5a56..7e16a22 100644 --- a/client/src/linux/linux.c +++ b/client/src/linux/linux.c @@ -19,6 +19,7 @@ #include +#include #define ENET_IMPLEMENTATION #include "thirdparty/enet/include/enet.h" @@ -72,6 +73,8 @@ static uint16_t _bufferHead = 0; static uint16_t _bufferTail = 0; static uint8_t _connected = 0; static char _command[COM_BUFFER_SIZE] = { 0 }; +static uint32_t _pixelFormat = SDL_PIXELFORMAT_RGBA8888; +static uint8_t _takeScreenshot = 0; static void comAddToBuffer(char *data, uint16_t len); @@ -79,6 +82,7 @@ static void comBufferShow(void); static void comModem(uint8_t c); static void processEvent(void); static void processNetworkEvent(void); +static void vbeScreenshot(void); long biostime(int cmd, long newtime) { @@ -453,6 +457,8 @@ static void processEvent(void) { _keyPressed = 1; _debounce = 1; } + // Screenshot? + if (e.key.keysym.scancode == SDL_SCANCODE_F10) _takeScreenshot = 1; } break; } @@ -553,6 +559,11 @@ void vbePresent(void) { // No network activity. Just sleep. SDL_Delay(32); } + + if (_takeScreenshot) { + _takeScreenshot = 0; + vbeScreenshot(); + } } @@ -562,6 +573,37 @@ int16_t vbeInfoShow(void) { } +static void vbeScreenshot(void) { + int32_t x = 0; + char filename[16] = { 0 }; + void *pixels = NULL; + SDL_Surface *surface = NULL; + SDL_Surface *save = NULL; + FILE *check = NULL; + + while (x <= 999) { + snprintf(filename, 16, "shot%03d.png", x); + check = fopen(filename, "rb"); + if (check) { + x++; + fclose(check); + } else { + break; + } + } + if (x > 999) utilDie("Seriously? You have 1000 screenshots in this folder? Remove some."); + + surface = SDL_GetWindowSurface(_window); + pixels = (uint8_t *)malloc(surface->w * surface->h * surface->format->BytesPerPixel); + SDL_RenderReadPixels(_renderer, &surface->clip_rect, surface->format->format, pixels, surface->w * surface->format->BytesPerPixel); + save = SDL_CreateRGBSurfaceFrom(pixels, surface->w, surface->h, surface->format->BitsPerPixel, surface->w * surface->format->BytesPerPixel, surface->format->Rmask, surface->format->Gmask, surface->format->Bmask, surface->format->Amask); + IMG_SavePNG(save, filename); + SDL_FreeSurface(save); + SDL_FreeSurface(surface); + free(pixels); +} + + int16_t vbeShutdown(void) { if (_texture) { SDL_DestroyTexture(_texture); @@ -601,7 +643,7 @@ uint8_t vbeStartup(uint16_t xRes, uint16_t yRes, uint8_t bpp) { _window = SDL_CreateWindow("GUI Test", 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, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, xRes, yRes); + _texture = SDL_CreateTexture(_renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, xRes, yRes); SDL_RenderSetLogicalSize(_renderer, xRes, yRes); SDL_SetWindowSize(_window, xRes * _windowScale, yRes * _windowScale); diff --git a/client/src/login.c b/client/src/login.c index 7baa984..9a8d0e6 100644 --- a/client/src/login.c +++ b/client/src/login.c @@ -22,6 +22,7 @@ #include "button.h" #include "msgbox.h" +#include "runtime.h" #include "config.h" #include "comport.h" #include "network.h" @@ -131,14 +132,14 @@ void taskLogin(void *data) { T_TITLE, P("User Name:"), T_X, 42, T_Y, 10, T_WIDTH, 200, - T_LENGTH, 16, + 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, 16, + T_LENGTH, shget(__runtimeData.integers, "maxPass"), T_MASK, '*', T_TEXTBOX, T_DONE, diff --git a/client/src/network.c b/client/src/network.c index 4ad68ca..2df811f 100644 --- a/client/src/network.c +++ b/client/src/network.c @@ -78,7 +78,6 @@ void netStartup(void) { void taskNetwork(void *data) { int32_t r = 0; - int8_t pingTimeout = 0; char buffer[1024] = { 0 }; PacketDecodeDataT *packet = NULL; PacketDecodeDataT decoded = { 0 }; @@ -96,10 +95,24 @@ void taskNetwork(void *data) { r = comRead(__configData.serialCom - 1, buffer, 1024); // New data or not, process anything in the queue. if (packetDecode(__packetThreadData, &decoded, buffer, r)) { - // Is this a PONG? If so, ignore it. - if (decoded.packetType == PACKET_TYPE_PONG) { - packetDecodeDataStaticDestroy(&decoded); - continue; + + // Is this something we care about? + switch (decoded.packetType) { + case PACKET_TYPE_PING: + // Reply with PONG + encoded.control = PACKET_CONTROL_DAT; + encoded.packetType = PACKET_TYPE_PONG; + encoded.channel = 0; + encoded.encrypt = 0; + packetEncode(__packetThreadData, &encoded, NULL, 0); + packetSend(__packetThreadData, &encoded); + packetDecodeDataStaticDestroy(&decoded); + continue; + break; + + default: + // Silences a warning. + break; } // Copy the packet out. @@ -126,20 +139,6 @@ void taskNetwork(void *data) { // Yield to UI. taskYield(); - // Send a ping to the server every 5 seconds, because, ping. - if (__timerSecondTick) { - pingTimeout++; - if (pingTimeout > 5) { - pingTimeout = 0; - encoded.control = PACKET_CONTROL_DAT; - encoded.packetType = PACKET_TYPE_PING; - encoded.channel = 0; - encoded.encrypt = 0; - packetEncode(__packetThreadData, &encoded, NULL, 0); // Must encode each packet - no reusing encoded data. - packetSend(__packetThreadData, &encoded); - } - } - } while (!guiHasStopped() && _netRunning); // Free any unclaimed packets. diff --git a/client/src/signup.c b/client/src/signup.c index 95112ff..2e8d4cd 100644 --- a/client/src/signup.c +++ b/client/src/signup.c @@ -107,7 +107,7 @@ void taskSignUp(void *data) { T_TITLE, P(" User Name:"), T_X, 40, T_Y, 64, T_WIDTH, 300, - T_LENGTH, 16, + T_LENGTH, shget(__runtimeData.integers, "maxUser"), T_TEXTBOX, T_DONE, T_TEXTBOX, O(_txtPass1), @@ -115,7 +115,7 @@ void taskSignUp(void *data) { T_TITLE, P(" Password:"), T_X, 40, T_Y, 94, T_WIDTH, 300, - T_LENGTH, 16, + T_LENGTH, shget(__runtimeData.integers, "maxPass"), T_MASK, '*', T_TEXTBOX, T_DONE, @@ -124,7 +124,7 @@ void taskSignUp(void *data) { T_TITLE, P(" Password (again):"), T_X, 40, T_Y, 124, T_WIDTH, 300, - T_LENGTH, 16, + T_LENGTH, shget(__runtimeData.integers, "maxPass"), T_MASK, '*', T_TEXTBOX, T_DONE, @@ -133,8 +133,7 @@ void taskSignUp(void *data) { T_TITLE, P(" REAL First Name:"), T_X, 40, T_Y, 154, T_WIDTH, 300, - T_LENGTH, 16, - T_MASK, '*', + T_LENGTH, shget(__runtimeData.integers, "maxName"), T_TEXTBOX, T_DONE, T_TEXTBOX, O(_txtLast), @@ -142,8 +141,7 @@ void taskSignUp(void *data) { T_TITLE, P(" REAL Last Name:"), T_X, 40, T_Y, 184, T_WIDTH, 300, - T_LENGTH, 16, - T_MASK, '*', + T_LENGTH, shget(__runtimeData.integers, "maxName"), T_TEXTBOX, T_DONE, T_TEXTBOX, O(_txtEmail), @@ -151,8 +149,7 @@ void taskSignUp(void *data) { T_TITLE, P(" VALID E-Mail:"), T_X, 40, T_Y, 214, T_WIDTH, 300, - T_LENGTH, 16, - T_MASK, '*', + T_LENGTH, shget(__runtimeData.integers, "maxEmail"), T_TEXTBOX, T_DONE, T_BUTTON, O(_btnCancel), diff --git a/kanga.world/site/blueprints/sections/userdata.yml b/kanga.world/site/blueprints/sections/userdata.yml new file mode 100644 index 0000000..64a13ca --- /dev/null +++ b/kanga.world/site/blueprints/sections/userdata.yml @@ -0,0 +1,81 @@ +columns: + left: + width: 1/2 + fields: + firstname: + label: First Name + width: 1/2 + type: text + lastname: + label: Last Name + width: 1/2 + type: text + street: + label: Street + type: text + city: + label: City + type: text + width: 1/2 + state: + label: State + type: text + width: 1/2 + zip: + label: ZIP + type: text + width: 1/4 + country: + label: Country + type: text + width: 3/4 + + right: + width: 1/2 + fields: + website: + label: Website + type: url + steam: + label: Steam + type: text + placeholder: username + width: 1/2 + epic: + label: Epic Games + type: text + placeholder: username + width: 1/2 + xbox: + label: Xbox Live + type: text + placeholder: username + width: 1/2 + playstation: + label: PlayStation Network + type: text + placeholder: username + width: 1/2 + nintendo: + label: Nintendo + type: text + placeholder: friendcode + width: 1/2 + facebook: + label: Facebook + type: text + icon: facebook + placeholder: username + width: 1/2 + twitter: + label: Twitter + type: text + icon: twitter + placeholder: @username + width: 1/2 + instagram: + label: Instagram + type: text + icon: instagram + placeholder: username + width: 1/2 diff --git a/kanga.world/site/blueprints/users/admin.yml b/kanga.world/site/blueprints/users/admin.yml new file mode 100644 index 0000000..6e7e017 --- /dev/null +++ b/kanga.world/site/blueprints/users/admin.yml @@ -0,0 +1,8 @@ +title: Administrator +description: Kanga World administrator + +permissions: + access: + panel: true + +extends: sections/userdata diff --git a/kanga.world/site/blueprints/users/user.yml b/kanga.world/site/blueprints/users/user.yml index 439975b..3ebf37d 100644 --- a/kanga.world/site/blueprints/users/user.yml +++ b/kanga.world/site/blueprints/users/user.yml @@ -1,4 +1,8 @@ title: User +description: Standard Kanga World user + permissions: access: - panel: false \ No newline at end of file + panel: false + +extends: sections/userdata diff --git a/kanga.world/site/controllers/register.php b/kanga.world/site/controllers/register.php index e724af1..065e7ba 100644 --- a/kanga.world/site/controllers/register.php +++ b/kanga.world/site/controllers/register.php @@ -30,7 +30,7 @@ return function ($kirby) { ]; // INVALID DATA - if($invalid = invalid($data, $rules, $messages)) { + if ($invalid = invalid($data, $rules, $messages)) { $alert = $invalid; $error = true; @@ -43,6 +43,9 @@ return function ($kirby) { try { + // A lot of this code is duplicated in: + // site/plugins/kangaworld-integration/api/user.php + // CREATE USER $user = $kirby->users()->create([ 'email' => $data['email'], @@ -53,7 +56,6 @@ return function ($kirby) { // CHECK EMAIL ACTIVATION if (option('user.email.activation', false) === true) { - $user->update([ 'emailActivation' => false, 'emailActivationToken' => $token diff --git a/kanga.world/site/plugins/kangaworld-integration/api/user.php b/kanga.world/site/plugins/kangaworld-integration/api/user.php index 19e85dd..7672a76 100644 --- a/kanga.world/site/plugins/kangaworld-integration/api/user.php +++ b/kanga.world/site/plugins/kangaworld-integration/api/user.php @@ -1,18 +1,54 @@ users()->create([ - 'name' => $name, - 'email' => $email, - 'password' => $password, - 'language' => 'en', - 'role' => 'user' - ]); - $response['result'] = 'true'; - $response['reason'] = 'User created.'; + // Check for duplicate username. + $user = kirby()->users()->filterBy('name', $username); + if ($user->first()) { + $response['result'] = 'false'; + $response['reason'] = 'User name already exists.'; + } else { + // Save Kirby attributes. + $user = kirby()->users()->create([ + 'name' => $username, + 'email' => $email, + 'password' => $password, + 'language' => 'en', + 'role' => 'user' + ]); + // Send activation email. + // This duplicates a lot of code from site/controllers/register.php + $token = Str::random(16); + $link = kirby()->site()->url() . "/user/activate/" . $token; + $email = kirby()->email([ + 'to' => $email, + 'from' => option('user.email.activation.sender'), + 'subject' => option('user.email.activation.sender', 'Account Activation Link'), + 'template' => 'account-activation', + 'data' => [ + 'link' => $link, + ] + ]); + // Save our extended attributes. + $user->update([ + 'firstname' => $first, + 'lastname' => $last, + 'emailActivation' => false, + 'emailActivationToken' => $token + ]); + // Save SQL attributes. + // ***TODO*** + + // Return result. + $response['result'] = 'true'; + $response['reason'] = 'User created. Check your E-mail for activation instructions.'; + } } catch(Exception $e) { - $response['reason'] = $e->getMessage(); + if (strpos($e->getMessage(), "email")) { + $response['reason'] = 'E-mail already exists.'; + } else { + $response['reason'] = $e->getMessage(); + } } } diff --git a/kanga.world/site/plugins/kangaworld-integration/features/api.php b/kanga.world/site/plugins/kangaworld-integration/features/api.php index 3dcd9b7..98df8a9 100644 --- a/kanga.world/site/plugins/kangaworld-integration/features/api.php +++ b/kanga.world/site/plugins/kangaworld-integration/features/api.php @@ -40,7 +40,7 @@ return [ break; case 'USER_CREATE': - kpApiUserCreate(get('name'), get('email'), get('password'), $response); + kpApiUserCreate(get('first'), get('last'), get('user'), get('email'), get('pass'), $response); break; case 'USER_GET': diff --git a/kanga.world/test.sh b/kanga.world/start.sh similarity index 100% rename from kanga.world/test.sh rename to kanga.world/start.sh diff --git a/server/src/client.c b/server/src/client.c index 0f68dd7..1981b17 100644 --- a/server/src/client.c +++ b/server/src/client.c @@ -64,15 +64,19 @@ static uint8_t clientDequeuePacket(ClientThreadT *client) { static void clientProcessPacket(ClientThreadT *client, PacketDecodeDataT *data) { - uint64_t i = 0; - uint64_t x = 0; - uint32_t y = 0; - uint64_t length = 0; - char *buffer = NULL; - PacketEncodeDataT encoded = { 0 }; - json_object *response = NULL; - RestStringMapT *strings = NULL; - RestIntegerMapT *integers = NULL; + uint64_t i = 0; + uint64_t x = 0; + uint32_t y = 0; + uint64_t length = 0; + char *buffer = NULL; + PacketEncodeDataT encoded = { 0 }; + json_object *response = NULL; + RestStringMapT *strings = NULL; + RestIntegerMapT *integers = NULL; + struct timespec timer = { 0 }; + double d = 0; + PacketTypeSignUpT *signup = NULL; + PacketTypeSignUpResultT signupResult = { 0 }; switch (data->packetType) { case PACKET_TYPE_CLIENT_SHUTDOWN: @@ -83,19 +87,56 @@ static void clientProcessPacket(ClientThreadT *client, PacketDecodeDataT *data) consoleMessageQueue("%ld: Channel %d %s %s\n", client->threadIndex, data->channel, ((PacketTypeLoginT *)data->data)->user, ((PacketTypeLoginT *)data->data)->pass); break; - case PACKET_TYPE_PING: - // Build PONG packet. - encoded.control = PACKET_CONTROL_DAT; - encoded.packetType = PACKET_TYPE_PONG; - encoded.channel = 0; - encoded.encrypt = 0; - packetEncode(client->packetThreadData, &encoded, NULL, 0); - // Send it. - packetSend(client->packetThreadData, &encoded); + case PACKET_TYPE_PONG: + // Time round-trip. + clock_gettime(CLOCK_MONOTONIC_RAW, &timer); + d = (timer.tv_sec - client->pingStart.tv_sec) * 1e9; + d = (d + (timer.tv_nsec - client->pingStart.tv_nsec)) * 1e-9; + client->pingStats[client->pingHead] = d; + client->pingHead++; + if (client->pingHead >= PING_STATS_SIZE) client->pingHead = 0; + // ***TODO*** Probably need a mutex here. + if (d > client->pingHigh) client->pingHigh = d; + if (d < client->pingLow) client->pingLow = d; + client->pingAverage = 0; + x = 0; + for (i=0; ipingStats[i] != 0) { + x++; + client->pingAverage += client->pingStats[i]; + } + } + client->pingAverage /= (double)x; + consoleMessageQueue("%ld: Ping: %f Low: %f Average: %f High: %f\n", client->threadIndex, d, client->pingLow, client->pingAverage, client->pingHigh); break; case PACKET_TYPE_SIGNUP: - //***TODO*** + signup = (PacketTypeSignUpT *)data->data; + response = restRequest("USER_CREATE", "sssss", + "first", signup->first, + "last", signup->last, + "user", signup->user, + "pass", signup->pass, + "email", signup->email + ); + if (response) { + signupResult.success = (json_object_get_boolean(json_object_object_get(response, "result")) == TRUE) ? 1 : 0; + buffer = (char *)json_object_get_string(json_object_object_get(response, "reason")); + } else { + // Something bad happened. + signupResult.success = 0; + buffer = "Unknown error. Sorry."; + } + memcpy(signupResult.message, buffer, strlen(buffer)); + if (response) restRelease(response); + // Build packet. + encoded.control = PACKET_CONTROL_DAT; + encoded.packetType = PACKET_TYPE_SIGNUP_RESULT; + encoded.channel = 0; + encoded.encrypt = 0; + packetEncode(client->packetThreadData, &encoded, (char *)&signupResult, sizeof(PacketTypeSignUpResultT)); + // Send it. + packetSend(client->packetThreadData, &encoded); break; case PACKET_TYPE_VERSION_BAD: @@ -230,6 +271,9 @@ void *clientThread(void *data) { struct timespec sleepTime = { 0, 1000000000/100 }; // 1/100th second. PacketTypeVersionT version = { 0 }; uint8_t versionSent = 0; + time_t ticks = { 0 }; + time_t lastTicks = { 0 }; + int8_t pingTimeout = 0; // Process packets until we're done. while (client->running) { @@ -250,11 +294,30 @@ void *clientThread(void *data) { } } + // Ping the client every 5 seconds. + ticks = time(NULL); + if (ticks != lastTicks) { + lastTicks = ticks; + pingTimeout++; + if (pingTimeout >= 5) { + pingTimeout = 0; + encoded.control = PACKET_CONTROL_DAT; + encoded.packetType = PACKET_TYPE_PING; + encoded.channel = 0; + encoded.encrypt = 0; + packetEncode(client->packetThreadData, &encoded, (char *)&version, sizeof(PacketTypeVersionT)); + packetSend(client->packetThreadData, &encoded); + clock_gettime(CLOCK_MONOTONIC_RAW, &client->pingStart); + } + } + // Don't eat all the CPU. nanosleep(&remaining, &sleepTime); } } + // ***TODO*** Write ping stats to database. + // Clean up client data on the way out. while (arrlen(client->packetQueue) > 0) { if (client->packetQueue[0]->data) DEL(client->packetQueue[0]->data); diff --git a/server/src/client.h b/server/src/client.h index c9cf1a4..dc34e4f 100644 --- a/server/src/client.h +++ b/server/src/client.h @@ -26,6 +26,9 @@ #include "packet.h" +#define PING_STATS_SIZE 100 + + typedef struct ClientRawPacketS { char *data; uint32_t length; @@ -39,6 +42,12 @@ typedef struct ClientThreadS { PacketThreadDataT *packetThreadData; ClientRawPacketT **packetQueue; pthread_mutex_t packetQueueMutex; + struct timespec pingStart; + double pingStats[PING_STATS_SIZE]; + uint8_t pingHead; + double pingAverage; + double pingHigh; + double pingLow; void *peer; } ClientThreadT; diff --git a/server/src/server.c b/server/src/server.c index 4840a88..f9fca7a 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -118,6 +118,11 @@ void *serverThread(void *data) { client->running = 1; client->packetQueue = NULL; client->peer = event.peer; + client->pingHead = 0; + client->pingAverage = 0; + client->pingHigh = 0; + client->pingLow = 9999; + for (i=0; ipingStats[i] = 0; memset(&client->packetQueueMutex, 1, sizeof(pthread_mutex_t)); pthread_mutex_init(&client->packetQueueMutex, NULL); // Keep our client in the peer data for later.