/* * 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 "network.h" #include "packet.h" #include "array.h" #include "taglist.h" #include "window.h" #include "label.h" #include "timer.h" #include "msgbox.h" #include "image.h" #include "file.h" #include "hangup.h" #include "welcome.h" // All this queue nonsense allows you to request more // downloads while downloads are currently running. typedef struct FileListS { char **files; fileCallback callback; } FileListT; static uint8_t _channel = 0; static uint8_t _dialogVisible = 0; static FileListT **_fileList = NULL; static FileListT *_current = NULL; static uint32_t _currentLength = 0; static char *_currentSha256 = NULL; static char *_file = NULL; static FILE *_handle = NULL; static WindowT *_winFile = NULL; static LabelT *_lblFile = NULL; static TimerT *_timTimer = NULL; static void btnMsgBoxOkay(MsgBoxButtonT button); static void fileCheckNext(void); static void fileCurrentUpdate(uint8_t dontCallback); static void fileShowDialog(void); static void fileShowError(char *message); static void packetHandler(PacketDecodeDataT *packet); static void timTimerTimeout(WidgetT *widget); static void btnMsgBoxOkay(MsgBoxButtonT button) { (void)button; hangupShow(welcomeShow); } // List of character array. void fileCacheCheck(fileCallback callback, char *vpaths[]) { uint16_t i = 0; char **files = NULL; // Used during development & debugging. if (!netPacketHandlerIsRunning()) { callback(NULL); return; } BEGIN while (vpaths[i] != NULL) { arrput(files, strdup(vpaths[i])); i++; } fileCacheCheckArr(callback, files); END } // List of dynamic array. void fileCacheCheckArr(fileCallback callback, char **vpathArr) { FileListT *newList = NULL; // Used during development & debugging. if (!netPacketHandlerIsRunning()) { callback(NULL); return; } BEGIN // Add new entries to anything already in the queue. NEW(FileListT, newList); newList->callback = callback; newList->files = vpathArr; arrput(_fileList, newList); // New queue? if (_channel == 0) { _dialogVisible = 0; _channel = netChannelGet(packetHandler); } fileCheckNext(); END } static void fileCheckNext(void) { PacketEncodeDataT encoded = { 0 }; uint8_t *packetData = NULL; uint16_t length = 0; uint32_t temp = 0; char *shaPointer = NULL; static char *badSHA = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; BEGIN fileCurrentUpdate(0); if (_file) { // Check next file. temp = FILE_REQUEST_CHECK; shaPointer = cacheSha256Get(_file); if (!shaPointer) shaPointer = badSHA; packetData = packetContentPack(&length, "iss", temp, shaPointer, _file); encoded.control = PACKET_CONTROL_DAT; encoded.packetType = PACKET_TYPE_FILE_REQUEST; encoded.channel = _channel; encoded.encrypt = 0; packetEncode(__packetThreadData, &encoded, packetData, length); packetSend(__packetThreadData, &encoded); DEL(packetData); } else { // Finished with transfers. if (_dialogVisible) guiDelete(D(_winFile)); netChannelRelease(_channel); _channel = 0; } END } static void fileCurrentUpdate(uint8_t dontCallback) { uint8_t doRecheck = 0; // This function is ugly and happened because a bunch of // code got moved around and this was needed in multiple // places. It updates several global variables. In short, // if '_file' is not null after calling this, there is // more to do. BEGIN do { // This is ugly since both lists kind of depend on each other. doRecheck = 0; // Do we need a new entry from the queue? if (_current == NULL) { // End of queue? if (arrlen(_fileList) == 0) { logWrite("End of file queue.\n"); arrfree(_fileList); return; } // Get next queue entry. _currentLength = 0; _current = _fileList[0]; arrdel(_fileList, 0); } if (_file) DEL(_file); // End of current queue file list? if (arrlen(_current->files) == 0) { // End of list! logWrite("End of file list.\n"); arrfree(_current->files); // Call with NULL to signify end of transfers. if (!dontCallback) _current->callback(NULL); DEL(_current); // See if there's more. doRecheck = 1; } else { // No. Get next file. _file = _current->files[0]; arrdel(_current->files, 0); logWrite("File to check: %s\n", _file); } } while (doRecheck); END } static void fileShowDialog(void) { TagItemT uiFile[] = { T_START, T_WINDOW, O(_winFile), T_TITLE, P("Updating Cache"), T_WIDTH, 200, T_HEIGHT, 100, T_LABEL, O(_lblFile), T_X, 41, T_Y, 25, T_TITLE, P("Downloading..."), T_LABEL, T_DONE, T_TIMER, O(_timTimer), T_EVENT, P(timTimerTimeout), // ***TODO*** This should probably be placed on the desktop so it can run while the first file is checked. T_VALUE, 4 * 5, T_ENABLED, 1, T_TIMER, T_DONE, T_WINDOW, T_DONE, T_END }; tagListRun(uiFile); _dialogVisible = 1; } static void fileShowError(char *message) { // ***TODO*** This should close all open windows. // Reset a possibly busy mouse pointer. guiMouseBusyClear(); // Show error. This is fatal. if (_dialogVisible) guiDelete(D(_winFile)); netChannelRelease(_channel); _channel = 0; msgBoxOne("Uh Oh!", MSGBOX_ICON_QUESTION, message, "Okay", btnMsgBoxOkay); } static void packetHandler(PacketDecodeDataT *packet) { FileResponseT response = 0; PacketEncodeDataT encoded = { 0 }; uint8_t *packetData = NULL; uint16_t length = 0; uint32_t temp = 0; BEGIN if (packet->packetType == PACKET_TYPE_FILE_RESPONSE) { // Extract the response type. We get more data later. packetContentUnpack(packet->data, "i", &response); switch (response) { case FILE_RESPONSE_UNKNOWN: fileShowError("Unknown file transfer response."); break; case FILE_RESPONSE_OKAY: // This file is already up-to-date, move on to next. fileCheckNext(); break; case FILE_RESPONSE_SEND: // Start of new file. Get SHA and length. packetContentUnpack(packet->data, "iis", &response, &_currentLength, &_currentSha256); break; case FILE_RESPONSE_DATA: // Receive new file data. if (!_dialogVisible) fileShowDialog(); // Are we starting a new file? if (_handle == NULL) { logWrite("Creating file %s\n", _file); _handle = cacheFOpen(_file, "wb"); if (!_handle) { fileShowError("Unable to write to cache."); break; } } // Write data to file, skipping first integer stored in packet. logWrite("Writing %d bytes\n", packet->length - 4); fwrite(&packet->data[4], packet->length - 4, 1, _handle); // Is this file complete? if (ftell(_handle) >= _currentLength) { logWrite("Closing file %s\n", _file); // Close this file. cacheFClose(_handle); _handle = NULL; // Update cache entry to include SHA. 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 { // Tell the server we got this bit of data, send the next. temp = FILE_REQUEST_NEXT; packetData = packetContentPack(&length, "i", temp); encoded.control = PACKET_CONTROL_DAT; encoded.packetType = PACKET_TYPE_FILE_REQUEST; encoded.channel = _channel; encoded.encrypt = 0; packetEncode(__packetThreadData, &encoded, packetData, length); packetSend(__packetThreadData, &encoded); DEL(packetData); } // Reset timeout timer. timerReset(_timTimer); break; } } packetDecodeDataDestroy(&packet); END } static void timTimerTimeout(WidgetT *widget) { uint8_t missing = 0; (void)widget; BEGIN // Download timed out. timerStop(_timTimer); // If we at least have some version of the file, try to continue. while (_file) { // Passing 'missing' prevents file completion callbacks from // occurring when there are missing files. fileCurrentUpdate(missing); if (_file && !cacheFilenameGet(_file)) missing = 1; } if (missing) fileShowError("Unable to download needed files."); END }