485 lines
12 KiB
C
485 lines
12 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/>.
|
|
*
|
|
*/
|
|
|
|
|
|
// ***TODO*** Maybe add a cacheFOpen() and cacheFClose() for reading and writing directly to virtual pathnames?
|
|
|
|
|
|
#include "thirdparty/SHA256/sha256.h"
|
|
|
|
#include "cache.h"
|
|
|
|
|
|
#define CACHE_FIELD_SHA256 0
|
|
#define CACHE_FIELD_ENTRYNAME 1
|
|
#define CACHE_FIELD_FILENAME 2
|
|
|
|
|
|
static char *cacheEntryNameDirGet(char *entryName, uint8_t includeCache);
|
|
static char *cacheEntryNameGenerate(void);
|
|
static char *cacheFieldGet(char *virtualPath, uint8_t field);
|
|
|
|
|
|
uint8_t cacheDelete(char *virtualPath) {
|
|
FILE *in = NULL;
|
|
FILE *out = NULL;
|
|
char index[16] = { 0 };
|
|
char indexNew[16] = { 0 };
|
|
char buffer[1024] = { 0 };
|
|
char *name = NULL;
|
|
char *path = NULL;
|
|
char *dir2 = NULL;
|
|
char *dir1 = NULL;
|
|
char *entryDir = NULL;
|
|
|
|
// Deletes entry from cache index AND the data from the cache.
|
|
|
|
sprintf(index, "CACHE%cINDEX.DAT", OS_PATH_SLASH);
|
|
sprintf(indexNew, "CACHE%cINDEX.NEW", OS_PATH_SLASH);
|
|
|
|
// Do we have an index yet?
|
|
if (osFileExists(index)) {
|
|
in = fopen(index, "rt");
|
|
if (in) {
|
|
out = fopen(indexNew, "wt");
|
|
if (out) {
|
|
while (fgets(buffer, 1024, in) != 0) {
|
|
name = strstr(buffer, " ");
|
|
*name = 0;
|
|
name++;
|
|
path = strstr(name, " ");
|
|
*path = 0;
|
|
path++;
|
|
path[strlen(path) - 1] = 0;
|
|
if (strcmp(virtualPath, path) == 0) {
|
|
// Found! Delete data from disk.
|
|
entryDir = cacheEntryNameDirGet(name, 1);
|
|
unlink(entryDir);
|
|
// Attempt to remove the second-level directory.
|
|
dir2 = osLastPathComponentGetUpTo(entryDir);
|
|
if (rmdir(dir2) == 0) {
|
|
// Attempt to move the top level directory.
|
|
dir1 = osLastPathComponentGetUpTo(dir2);
|
|
rmdir(dir1);
|
|
DEL(dir1);
|
|
}
|
|
DEL(dir2);
|
|
} else {
|
|
// Not what we're after, copy to new index.
|
|
fprintf(out, "%s %s %s\n", buffer, name, path);
|
|
}
|
|
}
|
|
} else {
|
|
fclose(in);
|
|
return FAIL;
|
|
}
|
|
fclose(in);
|
|
unlink(index);
|
|
rename(indexNew, index);
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
|
|
return FAIL;
|
|
}
|
|
|
|
|
|
uint8_t cacheEntryAdd(char *sha256, char *entryName, char *virtualPath) {
|
|
FILE *in = NULL;
|
|
FILE *out = NULL;
|
|
char index[16] = { 0 };
|
|
char indexNew[16] = { 0 };
|
|
char *name = NULL;
|
|
char *path = NULL;
|
|
char buffer[1024] = { 0 };
|
|
uint8_t found = 0;
|
|
|
|
// This adds or updates an entry to the index.
|
|
// It does not add data to the actual cache.
|
|
|
|
// Index format is: SHA256 ENTRYNAME VIRTUALPATH
|
|
|
|
sprintf(index, "CACHE%cINDEX.DAT", OS_PATH_SLASH);
|
|
sprintf(indexNew, "CACHE%cINDEX.NEW", OS_PATH_SLASH);
|
|
|
|
// Do we have an index yet?
|
|
if (!osFileExists(index)) {
|
|
// Nope. Just add this entry and be done.
|
|
out = fopen(index, "wt");
|
|
if (out) {
|
|
fprintf(out, "%s %s %s\n", sha256, entryName, virtualPath);
|
|
fclose(out);
|
|
return SUCCESS;
|
|
}
|
|
return FAIL;
|
|
}
|
|
|
|
// Read existing index, update the entry if it exists.
|
|
// If not, we'll add it at the end.
|
|
in = fopen(index, "rt");
|
|
if (in) {
|
|
out = fopen(indexNew, "wt");
|
|
if (out) {
|
|
while (fgets(buffer, 1024, in) != 0) {
|
|
name = strstr(buffer, " ");
|
|
*name = 0;
|
|
name++;
|
|
path = strstr(name, " ");
|
|
*path = 0;
|
|
path++;
|
|
path[strlen(path) - 1] = 0;
|
|
if (strcmp(virtualPath, path) == 0) {
|
|
// Update this entry.
|
|
fprintf(out, "%s %s %s\n", sha256, entryName, virtualPath);
|
|
found = 1;
|
|
} else {
|
|
// Not what we're after, copy to new index.
|
|
fprintf(out, "%s %s %s\n", buffer, name, path);
|
|
}
|
|
}
|
|
// Did we replace an entry?
|
|
if (!found) {
|
|
// Add new entry to end.
|
|
fprintf(out, "%s %s %s\n", sha256, entryName, virtualPath);
|
|
}
|
|
fclose(out);
|
|
} else {
|
|
fclose(in);
|
|
return FAIL;
|
|
}
|
|
fclose(in);
|
|
unlink(index);
|
|
rename(indexNew, index);
|
|
return SUCCESS;
|
|
}
|
|
|
|
return FAIL;
|
|
}
|
|
|
|
|
|
static char *cacheEntryNameDirGet(char *entryName, uint8_t includeCache) {
|
|
uint8_t i = 0;
|
|
uint8_t j = 0;
|
|
static char dir[21] = "CACHE";
|
|
|
|
dir[5] = OS_PATH_SLASH;
|
|
|
|
j = 6;
|
|
for (i=0; i<13; i++) {
|
|
dir[j++] = entryName[i];
|
|
if ((i == 1) || (i == 3)) {
|
|
dir[j++] = OS_PATH_SLASH;
|
|
}
|
|
}
|
|
|
|
dir[20] = 0;
|
|
|
|
return includeCache ? dir : &dir[6];
|
|
}
|
|
|
|
|
|
static char *cacheEntryNameGenerate(void) {
|
|
uint8_t i = 0;
|
|
static char name[13] = { 0 };
|
|
|
|
// Cached files are stored in a two-layer directory tree to
|
|
// reduce the number of files in any single folder.
|
|
// The folder names are two digits.
|
|
// The filename is eight random letters.
|
|
|
|
// First directory level.
|
|
i = random() % 100;
|
|
sprintf(&name[0], "%02d", i);
|
|
|
|
// Second directory level.
|
|
i = random() % 100;
|
|
sprintf(&name[2], "%02d", i);
|
|
|
|
// Random name.
|
|
for (i=4; i<12; i++) name[i] = 'A' + (random() % 26);
|
|
|
|
name[12] = 0;
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
char *cacheEntryNameGet(char *virtualPath) {
|
|
return cacheFieldGet(virtualPath, CACHE_FIELD_ENTRYNAME);
|
|
}
|
|
|
|
|
|
uint8_t cacheExists(void) {
|
|
char index[16] = { 0 };
|
|
|
|
sprintf(index, "CACHE%cINDEX.DAT", OS_PATH_SLASH);
|
|
|
|
return osFileExists(index);
|
|
}
|
|
|
|
|
|
void cacheFClose(FILE *handle) {
|
|
// Just wrap fclose() for now.
|
|
fclose(handle);
|
|
}
|
|
|
|
|
|
static char *cacheFieldGet(char *virtualPath, uint8_t field) {
|
|
FILE *in = NULL;
|
|
char index[16] = { 0 };
|
|
static char buffer[1024] = { 0 };
|
|
static char *name = NULL;
|
|
static char *path = NULL;
|
|
static char *result = NULL;
|
|
|
|
// Return SHA256 given virtual path or NULL if not found.
|
|
|
|
sprintf(index, "CACHE%cINDEX.DAT", OS_PATH_SLASH);
|
|
|
|
result = NULL;
|
|
|
|
// Do we have an index yet?
|
|
if (osFileExists(index)) {
|
|
in = fopen(index, "rt");
|
|
if (in) {
|
|
while ((fgets(buffer, 1024, in) != 0) && result == NULL) {
|
|
name = strstr(buffer, " ");
|
|
*name = 0;
|
|
name++;
|
|
path = strstr(name, " ");
|
|
*path = 0;
|
|
path++;
|
|
path[strlen(path) - 1] = 0;
|
|
if (strcmp(virtualPath, path) == 0) {
|
|
// Found! Return requested data.
|
|
switch (field) {
|
|
case CACHE_FIELD_SHA256:
|
|
result = buffer;
|
|
break;
|
|
|
|
case CACHE_FIELD_ENTRYNAME:
|
|
result = name;
|
|
break;
|
|
|
|
case CACHE_FIELD_FILENAME:
|
|
result = cacheEntryNameDirGet(name, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fclose(in);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
char *cacheFilenameGet(char *virtualPath) {
|
|
return cacheFieldGet(virtualPath, CACHE_FIELD_FILENAME);
|
|
}
|
|
|
|
|
|
FILE *cacheFOpen(char *vpath, char *mode) {
|
|
FILE *handle = NULL;
|
|
char *entry = NULL;
|
|
char *dir = NULL;
|
|
char *temp = NULL;
|
|
|
|
// Does this file already exist?
|
|
entry = cacheEntryNameGet(vpath);
|
|
|
|
// If we're writing or appending, do we need to create it?
|
|
if ((entry == NULL) && (mode[0] == 'w' || mode[0] == 'a')) {
|
|
do {
|
|
entry = cacheEntryNameGenerate();
|
|
dir = cacheEntryNameDirGet(entry, 1);
|
|
temp = osLastPathComponentGetUpTo(dir);
|
|
osMkDirP(temp);
|
|
DEL(temp);
|
|
} while (osFileExists(dir));
|
|
// Dummy SHA for files we create ourselves. They're never checked against the server.
|
|
cacheEntryAdd("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", entry, vpath);
|
|
}
|
|
|
|
// Did we get a file?
|
|
if (entry) {
|
|
dir = cacheEntryNameDirGet(entry, 1);
|
|
handle = fopen(dir, mode);
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
|
|
uint8_t cachePrePack(char *name, CachePreMakeListT *list) {
|
|
|
|
FILE *in = NULL;
|
|
FILE *out = NULL;
|
|
uint16_t i = 0;
|
|
uint8_t result = SUCCESS;
|
|
uint32_t length = 0;
|
|
uint32_t total = 0;
|
|
uint32_t temp = 0;
|
|
int8_t buffer[8192] = { 0 };
|
|
sha256_context ctx = { 0 };
|
|
uint8_t hv[32] = { 0 };
|
|
|
|
// Prebuilt cache format:
|
|
// - Virtual Path (zero terminated)
|
|
// - x bytes hash
|
|
// - 4 bytes unsigned length of data
|
|
// - length bytes of data
|
|
// ... repeat for each file ...
|
|
// - 4 bytes length of entire cache (used for embedding cache data)
|
|
|
|
out = fopen(name, "wb");
|
|
if (out) {
|
|
while (list[i].filename != NULL) {
|
|
in = fopen(list[i].filename, "rb");
|
|
if (in) {
|
|
// Get length of file.
|
|
fseek(in, 0, SEEK_END);
|
|
length = ftell(in);
|
|
fseek(in, 0, SEEK_SET);
|
|
// Generate SHA256.
|
|
sha256_init(&ctx);
|
|
do {
|
|
temp = fread(buffer, 1, sizeof(buffer), in);
|
|
if (temp) sha256_hash(&ctx, (uint8_t *)buffer, temp);
|
|
} while (temp > 0);
|
|
sha256_done(&ctx, hv);
|
|
fseek(in, 0, SEEK_SET);
|
|
// Write header.
|
|
temp = strlen(list[i].virtualPath) + 1;
|
|
fwrite(list[i].virtualPath, temp, 1, out);
|
|
fwrite(hv, sizeof(hv), 1, out);
|
|
fwrite(&length, sizeof(uint32_t), 1, out);
|
|
total += temp + sizeof(hv) + sizeof(uint32_t) + length;
|
|
// Copy data.
|
|
do {
|
|
temp = fread(buffer, 1, sizeof(buffer), in);
|
|
if (temp) fwrite(buffer, 1, temp, out);
|
|
} while (temp > 0);
|
|
fclose(in);
|
|
} else {
|
|
result = FAIL;
|
|
}
|
|
i++;
|
|
}
|
|
// Write length of entire block.
|
|
total += sizeof(uint32_t);
|
|
fwrite(&total, sizeof(uint32_t), 1, out);
|
|
fclose(out);
|
|
} else {
|
|
result = FAIL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
uint8_t cachePreUnpack(char *name) {
|
|
FILE *in = NULL;
|
|
FILE *out = NULL;
|
|
char *entryName = NULL;
|
|
char *entryDir = NULL;
|
|
char *temp = NULL;
|
|
uint8_t result = SUCCESS;
|
|
uint32_t i = 0;
|
|
uint32_t length = 0;
|
|
uint32_t total = 0;
|
|
int8_t buffer[8192] = { 0 };
|
|
uint8_t hv[32] = { 0 };
|
|
char virtualPath[CACHE_VIRTUAL_PATH_MAX] = { 0 };
|
|
|
|
in = fopen(name, "rb");
|
|
if (in) {
|
|
// Get entire pack size.
|
|
fseek(in, -sizeof(uint32_t), SEEK_END);
|
|
fread(&total, 1, sizeof(uint32_t), in);
|
|
fseek(in, 0, SEEK_SET);
|
|
do {
|
|
// Read virtual path.
|
|
i = 0;
|
|
do {
|
|
virtualPath[i++] = fgetc(in);
|
|
} while (virtualPath[i - 1] != 0);
|
|
// Read SHA256.
|
|
fread(hv, 1, sizeof(hv), in);
|
|
for (i=0; i<32; i++) sprintf((char *)&buffer[i * 2], "%02x", hv[i]);
|
|
buffer[64] = 0;
|
|
// Read length.
|
|
fread(&length, 1, sizeof(uint32_t), in);
|
|
// Get filename from cache manager & create missing directories.
|
|
do {
|
|
entryName = cacheEntryNameGenerate();
|
|
entryDir = cacheEntryNameDirGet(entryName, 1);
|
|
temp = osLastPathComponentGetUpTo(entryDir);
|
|
osMkDirP(temp);
|
|
DEL(temp);
|
|
} while (osFileExists(entryDir));
|
|
cacheEntryAdd((char *)buffer, entryName, virtualPath);
|
|
out = fopen(entryDir, "wb");
|
|
if (out) {
|
|
// Copy out data.
|
|
do {
|
|
if (length > sizeof(buffer)) {
|
|
i = sizeof(buffer);
|
|
} else {
|
|
i = length;
|
|
}
|
|
length -= i;
|
|
fread(buffer, 1, i, in);
|
|
fwrite(buffer, 1, i, out);
|
|
} while (length > 0);
|
|
fclose(out);
|
|
} else {
|
|
result = FAIL;
|
|
}
|
|
} while ((unsigned)ftell(in) < (total - sizeof(uint32_t)));
|
|
fclose(in);
|
|
} else {
|
|
result = FAIL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
char *cacheSha256Get(char *virtualPath) {
|
|
return cacheFieldGet(virtualPath, CACHE_FIELD_SHA256);
|
|
}
|
|
|
|
|
|
void cacheShutdown(void) {
|
|
// Nada.
|
|
}
|
|
|
|
|
|
void cacheStartup(char *appName) {
|
|
char *temp = NULL;
|
|
|
|
// Do we need to unpack the initial cache?
|
|
if (!cacheExists()) {
|
|
temp = utilAppNameWithNewExtensionGet(appName, "pre");
|
|
cachePreUnpack(temp);
|
|
DEL(temp);
|
|
}
|
|
}
|