Simplified DawnGAme a lot

This commit is contained in:
2023-03-15 15:50:29 -07:00
parent cc7091717c
commit 646f8b057a
31 changed files with 564 additions and 714 deletions

View File

@ -1,217 +1,217 @@
// 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(this->validateSave(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;
// 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;
}

View File

@ -1,91 +1,91 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "SaveFile.hpp"
#include "util/string.hpp"
#define SAVE_KEY_TIMESTAMP "meta.timestamp"
#define SAVE_KEY_SLOT "meta.slot"
#define SAVE_MANAGER_BUFFER_LENGTH 128
#define SAVE_MANAGER_FILENAME_LENGTH 512
namespace Dawn {
class DawnGame;
enum SaveLoadResult {
SAVE_LOAD_SUCCESS,
SAVE_LOAD_RESULT_FILE_NOT_PRESENT,
SAVE_LOAD_RESULT_TOO_SMALL,
SAVE_LOAD_RESULT_CORRUPTED_DE_SAVE,
SAVE_LOAD_RESULT_CORRUPTED_VERSION,
SAVE_LOAD_RESULT_CORRUPTED_TIMESTAMP,
SAVE_LOAD_RESULT_CORRUPTED_DATA_KEY,
SAVE_LOAD_RESULT_CORRUPTED_DATA_VALUE,
SAVE_LOAD_RESULT_MISMATCH_TIMESTAMP,
SAVE_LOAD_RESULT_ERROR
};
class SaveManager {
protected:
int16_t currentSaveSlot = -1;
struct SaveFile currentSave;
/**
* Reads a save file and then validates all of the values. If a value is
* not validated then it will NOT be retained.
*
* @param file Save file to read from.
* @return Whether or not the save file is corrupt, false for not corrupt.
*/
virtual bool_t validateSave(struct SaveFile raw) = 0;
public:
DawnGame *game;
/**
* Create a new Save Manager. Used by the game instance to decide when/how
* to perform saving.
*
* @param game Game that this save manager belongs to.
*/
SaveManager(DawnGame *game);
/**
* Immediately save the game to the given file. This will invoke the
* game's custom writer to write the output data to the save file.
*/
void saveFile();
/**
* Loads the current slotted save file. Invokes the internal managers read
* function.
*/
enum SaveLoadResult loadFile();
/**
* Deletes the given save file slot. We use slots here because we need to
* be able to delete a file without reading it (in case of corruption).
*
* @param slot Slot to delete.
*/
void deleteFile(int16_t slot);
/**
* Determines which save slot to use.
*
* @param slot Save slot to use.
*/
void useSlot(int16_t slot);
/**
* Returns a list of used save slots, does not confirm if they are corrupt
* or not, just whether they are in existance or not.
*
* @return List of used save slots.
*/
std::vector<int16_t> getUsedSlots();
};
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "SaveFile.hpp"
#include "util/string.hpp"
#define SAVE_KEY_TIMESTAMP "meta.timestamp"
#define SAVE_KEY_SLOT "meta.slot"
#define SAVE_MANAGER_BUFFER_LENGTH 128
#define SAVE_MANAGER_FILENAME_LENGTH 512
namespace Dawn {
class DawnGame;
enum SaveLoadResult {
SAVE_LOAD_SUCCESS,
SAVE_LOAD_RESULT_FILE_NOT_PRESENT,
SAVE_LOAD_RESULT_TOO_SMALL,
SAVE_LOAD_RESULT_CORRUPTED_DE_SAVE,
SAVE_LOAD_RESULT_CORRUPTED_VERSION,
SAVE_LOAD_RESULT_CORRUPTED_TIMESTAMP,
SAVE_LOAD_RESULT_CORRUPTED_DATA_KEY,
SAVE_LOAD_RESULT_CORRUPTED_DATA_VALUE,
SAVE_LOAD_RESULT_MISMATCH_TIMESTAMP,
SAVE_LOAD_RESULT_ERROR
};
/**
* Reads a save file and then validates all of the values. If a value is
* not validated then it will NOT be retained.
*
* @param file Save file to read from.
* @return Whether or not the save file is corrupt, false for not corrupt.
*/
bool_t saveValidateFile(struct SaveFile raw);
class SaveManager {
protected:
int16_t currentSaveSlot = -1;
struct SaveFile currentSave;
public:
DawnGame *game;
/**
* Create a new Save Manager. Used by the game instance to decide when/how
* to perform saving.
*
* @param game Game that this save manager belongs to.
*/
SaveManager(DawnGame *game);
/**
* Immediately save the game to the given file. This will invoke the
* game's custom writer to write the output data to the save file.
*/
void saveFile();
/**
* Loads the current slotted save file. Invokes the internal managers read
* function.
*/
enum SaveLoadResult loadFile();
/**
* Deletes the given save file slot. We use slots here because we need to
* be able to delete a file without reading it (in case of corruption).
*
* @param slot Slot to delete.
*/
void deleteFile(int16_t slot);
/**
* Determines which save slot to use.
*
* @param slot Save slot to use.
*/
void useSlot(int16_t slot);
/**
* Returns a list of used save slots, does not confirm if they are corrupt
* or not, just whether they are in existance or not.
*
* @return List of used save slots.
*/
std::vector<int16_t> getUsedSlots();
};
}