/* * 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 "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 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); } void fileCacheCheck(fileCallback callback, char *vpaths[]) { FileListT *newList = NULL; uint16_t i = 0; // Add new entries to anything already in the queue. NEW(FileListT, newList); newList->callback = callback; newList->files = NULL; while (vpaths[i] != NULL) { arrput(newList->files, strdup(vpaths[i])); i++; } arrput(_fileList, newList); // New queue? if (_channel == 0) { _dialogVisible = 0; _channel = netChannelGet(packetHandler); } fileCheckNext(); } static void fileCheckNext(void) { PacketEncodeDataT encoded = { 0 }; uint8_t *packetData = NULL; uint16_t length = 0; uint32_t temp = 0; uint8_t doRecheck = 0; char *shaPointer = NULL; static char *badSHA = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; 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 queue.\n"); arrfree(_fileList); if (_dialogVisible) guiDelete(D(_winFile)); netChannelRelease(_channel); return; } // Get next queue entry. logWrite("Next queue entry.\n"); _currentLength = 0; _current = _fileList[0]; arrdel(_fileList, 0); } if (_file) DEL(_file); // End of current queue file list? if (arrlen(_current->files) == 0) { logWrite("End of files.\n"); // End of list! arrfree(_current->files); _current->callback(); DEL(_current); // See if there's more. doRecheck = 1; } else { // No. Get next file. logWrite("Next file.\n"); _file = _current->files[0]; arrdel(_current->files, 0); } } while (doRecheck); // Check next file. temp = FILE_REQUEST_CHECK; shaPointer = cacheSha256Get(_file); if (!shaPointer) shaPointer = badSHA; packetData = packetContentPack(&length, "iss", temp, shaPointer, _file); logWrite("Checking [%d] [%s] [%s]\n", 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); } 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. // Show error. This is fatal. if (_dialogVisible) guiDelete(D(_winFile)); netChannelRelease(_channel); 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; 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: logWrite("Unknown file transfer response.\n"); fileShowError("Unknown file transfer response."); break; case FILE_RESPONSE_OKAY: // This file is already up-to-date, move on to next. logWrite("Got FILE_RESPONSE_OKAY\n"); fileCheckNext(); break; case FILE_RESPONSE_SEND: // Start of new file. Get SHA and length. logWrite("Got FILE_RESPONSE_SEND\n"); packetContentUnpack(packet->data, "iis", &response, &_currentLength, &_currentSha256); break; case FILE_RESPONSE_DATA: logWrite("Got FILE_RESPONSE_DATA\n"); // Receive new file data. if (!_dialogVisible) fileShowDialog(); // Are we starting a new file? if (_handle == NULL) { logWrite("Opening [%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 to [%s]\n", packet->length - 4, _file); fwrite(&packet->data[4], packet->length - 4, 1, _handle); // Is this file complete? if (ftell(_handle) >= _currentLength) { logWrite("Closing [%s]\n", _file); // Close this file. cacheFClose(_handle); _handle = NULL; // Update cache entry to include SHA. cacheEntryAdd(_currentSha256, cacheEntryNameGet(_file), _file); // Next file! fileCheckNext(); } else { logWrite("Sending FILE_REQUEST_NEXT\n"); // 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); } static void timTimerTimeout(WidgetT *widget) { uint8_t missing = 0; (void)widget; // Download timed out. timerStop(_timTimer); // If we at least have some version of the file, try to continue. while (arrlen(_fileList) > 0) { while (arrlen(_fileList[0]->files) > 0) { if (cacheFilenameGet(_fileList[0]->files[0])) { arrdel(_fileList[0]->files, 0); } else { missing = 1; } } // If we're good so far, call the callback. if (!missing) _fileList[0]->callback(); arrfree(_fileList[0]->files); arrdel(_fileList, 0); } arrfree(_fileList); if (missing) fileShowError("Unable to download needed files."); }