354 lines
9 KiB
C
354 lines
9 KiB
C
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#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
|
|
}
|