217 lines
6.1 KiB
C++
217 lines
6.1 KiB
C++
// Copyright (c) 2022 Dominic Masters
|
|
//
|
|
// This software is released under the MIT License.
|
|
// https://opensource.org/licenses/MIT
|
|
|
|
#include "SaveManager.hpp"
|
|
#include "game/DawnGame.hpp"
|
|
|
|
using namespace Dawn;
|
|
|
|
SaveManager::SaveManager(DawnGame *game) {
|
|
this->game = game;
|
|
}
|
|
|
|
void SaveManager::saveFile() {
|
|
savedata_t value;
|
|
|
|
assertTrue(this->currentSaveSlot >= 0);
|
|
|
|
// Update metadata
|
|
auto timestamp = this->game->timeManager.getTimestamp();
|
|
value.i64 = timestamp;
|
|
this->currentSave.set(SAVE_KEY_TIMESTAMP, value);
|
|
value.i16 = this->currentSaveSlot;
|
|
this->currentSave.set(SAVE_KEY_SLOT, value);
|
|
|
|
// Now open output buffer.
|
|
char filename[SAVE_MANAGER_FILENAME_LENGTH];
|
|
sprintf(filename, "savefile_%u.save", this->currentSaveSlot);
|
|
FILE *fptr = fopen(filename, "wb");
|
|
if(fptr == NULL) {
|
|
printf("Error opening %s\n", filename);
|
|
assertUnreachable();
|
|
return;
|
|
}
|
|
|
|
// Now begin buffering data. First we start with the common phrase, the engine
|
|
// version, the timestamp. In future we will store the MD5 here too.
|
|
char buffer[SAVE_MANAGER_BUFFER_LENGTH];
|
|
auto len = sprintf(buffer, "DE_SAVE|%s|%lld|", "1.00", timestamp);
|
|
fwrite(buffer, sizeof(char), len, fptr);
|
|
|
|
// Now buffer out the data, this is almost certain to change over time.
|
|
auto it = this->currentSave.values.begin();
|
|
while(it != this->currentSave.values.end()) {
|
|
uint8_t *buffer = (uint8_t *)&it->second;
|
|
char delim = '|';
|
|
fwrite(it->first.c_str(), sizeof(char), strlen(it->first.c_str()), fptr);
|
|
fwrite(&delim, sizeof(char), 1, fptr);
|
|
fwrite(buffer, sizeof(uint8_t), sizeof(savedata_t) / sizeof(uint8_t), fptr);
|
|
fwrite(&delim, sizeof(char), 1, fptr);
|
|
++it;
|
|
}
|
|
|
|
// Buffering done, close the file buffer.
|
|
fclose(fptr);
|
|
this->currentSave.hasChanges = false;
|
|
}
|
|
|
|
enum SaveLoadResult SaveManager::loadFile() {
|
|
this->currentSave.reset();
|
|
|
|
assertTrue(this->currentSaveSlot >= 0);
|
|
|
|
// Load file
|
|
struct SaveFile temporaryFile;
|
|
|
|
char filename[SAVE_MANAGER_FILENAME_LENGTH];
|
|
sprintf(filename, "savefile_%u.save", this->currentSaveSlot);
|
|
FILE *fptr = fopen(filename, "rb");
|
|
if(fptr == NULL) {
|
|
return SAVE_LOAD_RESULT_FILE_NOT_PRESENT;
|
|
}
|
|
|
|
// Let's read how long the file is first.
|
|
fseek(fptr, 0, SEEK_END);
|
|
auto len = ftell(fptr);
|
|
|
|
// Now let's just ensure the file isn't too small, this starts by us making
|
|
// sure we have at least "DE_SAVE|%s|%lld|", the string is unknown length,
|
|
// but we can be 100% sure of the rest of the file size. So we take
|
|
// 7+1+1+1+19+1=30
|
|
if(len < 30) {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_TOO_SMALL;
|
|
}
|
|
|
|
// Rewind.
|
|
fseek(fptr, 0, SEEK_SET);
|
|
|
|
// Ok let's buffer the first 8 characters to validate "DE_SAVE|"
|
|
char buffer[SAVE_MANAGER_BUFFER_LENGTH];
|
|
auto read = fread(buffer, sizeof(char), SAVE_MANAGER_BUFFER_LENGTH, fptr);
|
|
if(
|
|
read < 8 ||
|
|
buffer[0] != 'D' ||
|
|
buffer[1] != 'E' ||
|
|
buffer[2] != '_' ||
|
|
buffer[3] != 'S' ||
|
|
buffer[4] != 'A' ||
|
|
buffer[5] != 'V' ||
|
|
buffer[6] != 'E' ||
|
|
buffer[7] != '|'
|
|
) {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_CORRUPTED_DE_SAVE;
|
|
}
|
|
|
|
// Now read ahead to the next vertical bar, that will give us the engine
|
|
// version
|
|
char *bufferCurrent = buffer + 8;
|
|
char *p = stringFindNext(bufferCurrent, '|', 10);
|
|
if(p == NULL) {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_CORRUPTED_VERSION;
|
|
}
|
|
*p = '\0';
|
|
char *version = bufferCurrent;
|
|
bufferCurrent = p + 1;
|
|
|
|
// Now read the timestamp string.
|
|
p = stringFindNext(bufferCurrent, '|', 64);
|
|
if(p == NULL) {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_CORRUPTED_TIMESTAMP;
|
|
}
|
|
*p = '\0';
|
|
char *strTimestamp = bufferCurrent;
|
|
|
|
// Parse timestamp
|
|
int64_t timestamp = strtoll(strTimestamp, NULL, 10);
|
|
if(timestamp == 0) {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_CORRUPTED_TIMESTAMP;
|
|
}
|
|
|
|
// Now begin parsing the data in the save file. We start here, at the end of
|
|
// the previous string, so it will INCLUDE "|" at the first char.
|
|
read = p - buffer;
|
|
while(read < (len-1)) {
|
|
// Rewind and then buffer the next set of bytes
|
|
fseek(fptr, (long)read, SEEK_SET);
|
|
if(fread(buffer, sizeof(char), SAVE_MANAGER_BUFFER_LENGTH, fptr) <= 1) {
|
|
break;
|
|
}
|
|
|
|
// Because we finish the last string INCLUDING "|", then we skip it here.
|
|
bufferCurrent = buffer + 1;
|
|
|
|
// Now, read the key
|
|
p = stringFindNext(bufferCurrent, '|', SAVE_MANAGER_BUFFER_LENGTH);
|
|
if(p == NULL) {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_CORRUPTED_DATA_KEY;
|
|
}
|
|
|
|
*p = '\0';
|
|
char *key = bufferCurrent;
|
|
|
|
// Validate the string lengths
|
|
if(strlen(key) <= 0) {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_CORRUPTED_DATA_KEY;
|
|
}
|
|
|
|
// Now read the value
|
|
bufferCurrent = p + 1;
|
|
savedata_t destination;
|
|
uint8_t *ptrValue = (uint8_t*)bufferCurrent;
|
|
memoryCopy(ptrValue, &destination, sizeof(savedata_t));
|
|
|
|
// Now advance the pointer...
|
|
p = bufferCurrent + sizeof(savedata_t);
|
|
if(*p != '|') {
|
|
fclose(fptr);
|
|
return SAVE_LOAD_RESULT_CORRUPTED_DATA_VALUE;
|
|
}
|
|
|
|
// Set the value.
|
|
temporaryFile.set(std::string(key), destination);
|
|
read += (p - buffer);
|
|
}
|
|
|
|
// Close file
|
|
fclose(fptr);
|
|
|
|
// OK Let's validate that everything was read OK
|
|
if(temporaryFile.get(SAVE_KEY_TIMESTAMP).i64 != timestamp) {
|
|
return SAVE_LOAD_RESULT_MISMATCH_TIMESTAMP;
|
|
}
|
|
|
|
// Now pass off to the loader
|
|
if(saveValidateFile(temporaryFile) != false) {
|
|
return SAVE_LOAD_RESULT_ERROR;
|
|
}
|
|
|
|
// Unmark changes. In future I may need to do more complex testing, e.g. if
|
|
// the game has a newer version that updates save files some how.
|
|
this->currentSave.hasChanges = false;
|
|
return SAVE_LOAD_SUCCESS;
|
|
}
|
|
|
|
void SaveManager::deleteFile(int16_t slot) {
|
|
assertTrue(slot >= 0);
|
|
}
|
|
|
|
void SaveManager::useSlot(int16_t slot) {
|
|
assertTrue(slot >= 0);
|
|
this->currentSaveSlot = slot;
|
|
this->currentSave.reset();
|
|
}
|
|
|
|
std::vector<int16_t> SaveManager::getUsedSlots() {
|
|
std::vector<int16_t> slots;
|
|
|
|
return slots;
|
|
} |