/* * 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 . * */ // ***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 logWrite("Adding SHA [%s] Entry [%s] VPath [%s]\n", 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[2048] = { 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) { // Be sure the fread is the last conditional so it short-circuts properly. while (result == NULL && (fgets(buffer, 2048, 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! 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); } }