Movies: Save/load settings and cheats, fixed bugs/crashes, fixed todos

This commit is contained in:
Sour 2022-07-26 23:36:38 -04:00
parent 077e8db3d7
commit aaf240c070
23 changed files with 1654 additions and 233 deletions

View file

@ -14,7 +14,7 @@ public:
{
_debugger = debugger;
_needBreak = debugger->GetEmulator()->GetEmulationThreadId() != std::this_thread::get_id();
_needBreak = !debugger->GetEmulator()->IsEmulationThread();
if(_needBreak) {
//Only attempt to break if this is done in a thread other than the main emulation thread (and the debugger is active)

View file

@ -100,7 +100,7 @@ uint8_t NesApu::ReadRam(uint16_t addr)
uint8_t NesApu::PeekRam(uint16_t addr)
{
if(_console->GetEmulator()->GetEmulationThreadId() == std::this_thread::get_id()) {
if(_console->GetEmulator()->IsEmulationThread()) {
//Only run the Apu (to catch up) if we're running this in the emulation thread (not 100% accurate, but we can't run the Apu from any other thread without locking)
Run();
}

View file

@ -135,8 +135,8 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile)
}
}
//If in DB or a NES 2.0 file, auto-configure the inputs (if option is enabled)
if(GetNesConfig().AutoConfigureInput && (romData.Info.IsInDatabase || romData.Info.IsNes20Header) && romData.Info.InputType != GameInputType::Unspecified) {
if(GetNesConfig().AutoConfigureInput && romData.Info.InputType != GameInputType::Unspecified) {
//Auto-configure the inputs (if option is enabled)
InitializeInputDevices(romData.Info.InputType, romData.Info.System);
}
@ -152,14 +152,6 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile)
_controlManager.reset(new NesControlManager(this));
}
/*
//Temporarely disable battery saves to prevent battery files from being created for the wrong game (for Battle Box & Turbo File)
_batteryManager->SetSaveEnabled(false);
*/
//Re-enable battery saves
/*_batteryManager->SetSaveEnabled(true);
*/
if(_hdData && (!_hdData->Tiles.empty() || !_hdData->Backgrounds.empty())) {
_ppu.reset(new HdNesPpu(this, _hdData.get()));
} else if(dynamic_cast<NsfMapper*>(_mapper.get())) {

View file

@ -70,7 +70,7 @@ void GameClient::ProcessNotification(ConsoleNotificationType type, void* paramet
{
if(type == ConsoleNotificationType::GameLoaded &&
std::this_thread::get_id() != _clientThread->get_id() &&
std::this_thread::get_id() != _emu->GetEmulationThreadId()
!_emu->IsEmulationThread()
) {
//Disconnect if the client tried to manually load a game
//A deadlock occurs if this is called from the emulation thread while a network message is being processed

View file

@ -65,7 +65,6 @@ Emulator::Emulator() :
_isRunAheadFrame = false;
_lockCounter = 0;
_threadPaused = false;
_lockCounter = 0;
_debugRequestCount = 0;
_allowDebuggerRequest = true;
@ -147,8 +146,6 @@ void Emulator::Run()
}
}
_movieManager->Stop();
_emulationThreadId = thread::id();
if(_runLock.IsLockedByCurrentThread()) {
@ -283,6 +280,7 @@ void Emulator::Stop(bool sendNotification, bool preventRecentGameSave, bool save
_notificationManager->SendNotification(ConsoleNotificationType::BeforeEmulationStop);
}
_movieManager->Stop();
_videoDecoder->StopThread();
_videoRenderer->StopThread();
_rewindManager.reset();
@ -330,7 +328,7 @@ void Emulator::PowerCycle()
bool Emulator::LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom, bool forPowerCycle)
{
if(GetEmulationThreadId() == std::this_thread::get_id()) {
if(IsEmulationThread()) {
_threadPaused = true;
}
@ -367,7 +365,11 @@ bool Emulator::LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom,
//Make sure the battery is saved to disk before we load another game (or reload the same game)
_console->SaveBattery();
}
if(!forPowerCycle) {
_movieManager->Stop();
}
unique_ptr<IConsole> console;
LoadRomResult result = LoadRomResult::UnknownType;
TryLoadRom<NesConsole>(romFile, result, console);
@ -1013,6 +1015,11 @@ thread::id Emulator::GetEmulationThreadId()
return _emulationThreadId;
}
bool Emulator::IsEmulationThread()
{
return _emulationThreadId == std::this_thread::get_id();
}
void Emulator::RegisterMemory(MemoryType type, void* memory, uint32_t size)
{
_consoleMemory[(int)type] = { memory, size };

View file

@ -226,6 +226,7 @@ public:
bool IsDebugging();
thread::id GetEmulationThreadId();
bool IsEmulationThread();
void RegisterMemory(MemoryType type, void* memory, uint32_t size);
ConsoleMemoryInfo GetMemory(MemoryType type);

View file

@ -45,6 +45,7 @@ std::unordered_map<string, string> MessageManager::_enResources = {
{ "Lag", u8"Lag" },
{ "Mapper", u8"Mapper: %1, SubMapper: %2" },
{ "MovieEnded", u8"Movie ended." },
{ "MovieStopped", u8"Movie stopped." },
{ "MovieInvalid", u8"Invalid movie file." },
{ "MovieMissingRom", u8"Missing ROM required (%1) to play movie." },
{ "MovieNewerVersion", u8"Cannot load movies created by a more recent version of Mesen. Please download the latest version." },

View file

@ -15,6 +15,8 @@
#include "Utilities/StringUtilities.h"
#include "Utilities/HexUtilities.h"
#include "Utilities/VirtualFile.h"
#include "Utilities/magic_enum.hpp"
#include "Utilities/Serializer.h"
MesenMovie::MesenMovie(Emulator* emu, bool forTest)
{
@ -30,19 +32,30 @@ MesenMovie::~MesenMovie()
void MesenMovie::Stop()
{
if(_playing) {
bool isEndOfMovie = _lastPollCounter >= _inputData.size();
if(!_forTest) {
MessageManager::DisplayMessage("Movies", "MovieEnded");
MessageManager::DisplayMessage("Movies", isEndOfMovie ? "MovieEnded" : "MovieStopped");
}
if(_emu->GetSettings()->GetPreferences().PauseOnMovieEnd) {
_emu->Pause();
}
if(!_emu->IsEmulationThread()) {
EmuSettings* settings = _emu->GetSettings();
if(isEndOfMovie && settings->GetPreferences().PauseOnMovieEnd) {
_emu->Pause();
}
_emu->GetCheatManager()->SetCheats(_originalCheats);
_emu->GetCheatManager()->SetCheats(_originalCheats);
Serializer backup(0, false, false);
backup.LoadFrom(_emuSettingsBackup);
backup.Stream(*settings, "", -1);
}
_playing = false;
}
_emu->GetControlManager()->UnregisterInputProvider(this);
if(_emu->GetControlManager()) {
_emu->GetControlManager()->UnregisterInputProvider(this);
}
}
bool MesenMovie::SetInput(BaseControlDevice *device)
@ -59,6 +72,7 @@ bool MesenMovie::SetInput(BaseControlDevice *device)
_deviceIndex = 0;
}
} else {
//End of input data reached (movie end)
_emu->GetMovieManager()->Stop();
}
return true;
@ -114,40 +128,33 @@ bool MesenMovie::Play(VirtualFile &file)
_deviceIndex = 0;
ParseSettings(settingsData);
auto emuLock = _emu->AcquireLock();
_emu->Lock();
_emu->GetBatteryManager()->SetBatteryProvider(shared_from_this());
_emu->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
ApplySettings();
ParseSettings(settingsData);
//TODO
//Disable auto-configure input option (otherwise the movie file's input types are ignored)
//bool autoConfigureInput = _console->GetSettings()->CheckFlag(EmulationFlags::AutoConfigureInput);
//_console->GetSettings()->ClearFlags(EmulationFlags::AutoConfigureInput);
BaseControlManager *controlManager = _emu->GetControlManager();
if(controlManager) {
//ControlManager can be empty if no game is loaded
controlManager->SetPollCounter(0);
if(LoadInt(_settings, MovieKeys::MovieFormatVersion, 0) < 2) {
MessageManager::DisplayMessage("Movies", "MovieIncompatibleVersion");
return false;
}
//bool gameLoaded = LoadGame();
//TODO
//_console->GetSettings()->SetFlagState(EmulationFlags::AutoConfigureInput, autoConfigureInput);
/*if(!gameLoaded) {
_console->Unlock();
if(!ApplySettings(settingsData)) {
return false;
}*/
}
_emu->GetBatteryManager()->SetBatteryProvider(shared_from_this());
_emu->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
_emu->PowerCycle();
//Re-apply settings - power cycling can alter some (e.g auto-configure input types, etc.)
ApplySettings(settingsData);
_originalCheats = _emu->GetCheatManager()->GetCheats();
controlManager->UpdateControlDevices();
if(!_forTest) {
_emu->PowerCycle();
} else {
BaseControlManager *controlManager = _emu->GetControlManager();
if(_forTest) {
//TODO to validate test behavior
controlManager->RegisterInputProvider(this);
}
@ -156,17 +163,14 @@ bool MesenMovie::Play(VirtualFile &file)
stringstream saveStateData;
if(_reader->GetStream("SaveState.mss", saveStateData)) {
if(!_emu->GetSaveStateManager()->LoadState(saveStateData, true)) {
_emu->Resume();
return false;
} else {
_emu->GetControlManager()->SetPollCounter(0);
}
}
controlManager->UpdateControlDevices();
controlManager->SetPollCounter(0);
_playing = true;
_emu->Unlock();
return true;
}
@ -203,66 +207,30 @@ void MesenMovie::ParseSettings(stringstream &data)
}
}
bool MesenMovie::LoadGame()
{
/*string mesenVersion = LoadString(_settings, MovieKeys::MesenVersion);
string gameFile = LoadString(_settings, MovieKeys::GameFile);
string sha1Hash = LoadString(_settings, MovieKeys::Sha1);
//string patchFile = LoadString(_settings, MovieKeys::PatchFile);
//string patchFileSha1 = LoadString(_settings, MovieKeys::PatchFileSha1);
//string patchedRomSha1 = LoadString(_settings, MovieKeys::PatchedRomSha1);
if(_console->GetSettings()->CheckFlag(EmulationFlags::AllowMismatchingSaveState) && _console->GetRomInfo().RomName == gameFile) {
//Loaded game has the right name, and we don't want to validate the hash values
_console->PowerCycle();
return true;
}
HashInfo hashInfo;
hashInfo.Sha1 = sha1Hash;
VirtualFile romFile = _console->FindMatchingRom(gameFile, hashInfo);
bool gameLoaded = false;
if(romFile.IsValid()) {
VirtualFile patchFile(_movieFile.GetFilePath(), "PatchData.dat");
if(patchFile.IsValid()) {
gameLoaded = _console->Initialize(romFile, patchFile);
} else {
gameLoaded = _console->Initialize(romFile);
}
}
return gameLoaded;*/
return true;
}
void MesenMovie::ApplySettings()
bool MesenMovie::ApplySettings(istream& settingsData)
{
EmuSettings* settings = _emu->GetSettings();
EmulationConfig emuConfig = settings->GetEmulationConfig();
settingsData.clear();
settingsData.seekg(0, std::ios::beg);
Serializer s(0, false, true);
s.LoadFrom(settingsData);
//TODO
/*InputConfig inputConfig = settings->GetInputConfig();
ConsoleType consoleType = {};
s.Stream(consoleType, "emu.consoleType", -1);
inputConfig.Controllers[0].Type = FromString(LoadString(_settings, MovieKeys::Controller1), ControllerTypeNames, ControllerType::None);
inputConfig.Controllers[1].Type = FromString(LoadString(_settings, MovieKeys::Controller2), ControllerTypeNames, ControllerType::None);
inputConfig.Controllers[2].Type = FromString(LoadString(_settings, MovieKeys::Controller3), ControllerTypeNames, ControllerType::None);
inputConfig.Controllers[3].Type = FromString(LoadString(_settings, MovieKeys::Controller4), ControllerTypeNames, ControllerType::None);
inputConfig.Controllers[4].Type = FromString(LoadString(_settings, MovieKeys::Controller5), ControllerTypeNames, ControllerType::None);
emuConfig.Region = FromString(LoadString(_settings, MovieKeys::Region), ConsoleRegionNames, ConsoleRegion::Ntsc);
*/
//TODO
/*
if(!_forTest) {
emuConfig.RamPowerOnState = FromString(LoadString(_settings, MovieKeys::RamPowerOnState), RamStateNames, RamState::AllOnes);
if(consoleType != _emu->GetConsoleType()) {
return false;
}
emuConfig.PpuExtraScanlinesAfterNmi = LoadInt(_settings, MovieKeys::ExtraScanlinesAfterNmi);
emuConfig.PpuExtraScanlinesBeforeNmi = LoadInt(_settings, MovieKeys::ExtraScanlinesBeforeNmi);
emuConfig.GsuClockSpeed = LoadInt(_settings, MovieKeys::GsuClockSpeed, 100);*/
//settings->SetEmulationConfig(emuConfig);
//settings->SetInputConfig(inputConfig);
Serializer backup(0, true, false);
backup.Stream(*settings, "", -1);
backup.SaveTo(_emuSettingsBackup);
//Load settings
s.Stream(*settings, "", -1);
return true;
}
uint32_t MesenMovie::LoadInt(std::unordered_map<string, string> &settings, string name, uint32_t defaultValue)
@ -324,12 +292,14 @@ bool MesenMovie::LoadCheat(string cheatData, CheatCode &code)
vector<string> data = StringUtilities::Split(cheatData, ' ');
if(data.size() == 2) {
//TODO
//code.Address = HexUtilities::FromHex(data[0]);
//code.Value = HexUtilities::FromHex(data[1]);
return true;
} else {
MessageManager::Log("[Movie] Invalid cheat definition: " + cheatData);
auto cheatType = magic_enum::enum_cast<CheatType>(data[0]);
if(cheatType.has_value() && data[1].size() <= 15) {
code.Type = cheatType.value();
memcpy(code.Code, data[1].c_str(), data[1].size() + 1);
return true;
}
}
MessageManager::Log("[Movie] Invalid cheat definition: " + cheatData);
return false;
}

View file

@ -13,7 +13,7 @@ struct CheatCode;
class MesenMovie : public IMovie, public INotificationListener, public IBatteryProvider, public std::enable_shared_from_this<MesenMovie>
{
private:
Emulator* _emu;
Emulator* _emu = nullptr;
VirtualFile _movieFile;
unique_ptr<ZipReader> _reader;
@ -23,15 +23,14 @@ private:
vector<vector<string>> _inputData;
vector<string> _cheats;
vector<CheatCode> _originalCheats;
std::unordered_map<string, string> _settings;
stringstream _emuSettingsBackup;
unordered_map<string, string> _settings;
string _filename;
bool _forTest;
bool _forTest = false;
private:
void ParseSettings(stringstream &data);
void ApplySettings();
bool LoadGame();
void Stop();
bool ApplySettings(istream& settingsData);
uint32_t LoadInt(std::unordered_map<string, string> &settings, string name, uint32_t defaultValue = 0);
bool LoadBool(std::unordered_map<string, string> &settings, string name);
@ -45,6 +44,8 @@ public:
virtual ~MesenMovie();
bool Play(VirtualFile &file) override;
void Stop() override;
bool SetInput(BaseControlDevice* device) override;
bool IsPlaying() override;

View file

@ -17,7 +17,7 @@ void MovieManager::Record(RecordMovieOptions options)
{
shared_ptr<MovieRecorder> recorder(new MovieRecorder(_emu));
if(recorder->Record(options)) {
_recorder = recorder;
_recorder.reset(recorder);
}
}
@ -38,7 +38,7 @@ void MovieManager::Play(VirtualFile file, bool forTest)
}
if(player && player->Play(file)) {
_player = player;
_player.reset(player);
if(!forTest) {
MessageManager::DisplayMessage("Movies", "MoviePlaying", file.GetFileName());
}
@ -48,6 +48,10 @@ void MovieManager::Play(VirtualFile file, bool forTest)
void MovieManager::Stop()
{
shared_ptr<IMovie> player = _player.lock();
if(player) {
player->Stop();
}
_player.reset();
_recorder.reset();
}

View file

@ -4,6 +4,7 @@
#include "Shared/Interfaces/IInputProvider.h"
#include "Shared/Movies/MovieTypes.h"
#include "Shared/Movies/MovieRecorder.h"
#include "Utilities/safe_ptr.h"
class VirtualFile;
class Emulator;
@ -11,7 +12,8 @@ class Emulator;
class IMovie : public IInputProvider
{
public:
virtual bool Play(VirtualFile &file) = 0;
virtual bool Play(VirtualFile& file) = 0;
virtual void Stop() = 0;
virtual bool IsPlaying() = 0;
};
@ -19,8 +21,8 @@ class MovieManager
{
private:
Emulator* _emu;
shared_ptr<IMovie> _player;
shared_ptr<MovieRecorder> _recorder;
safe_ptr<IMovie> _player;
safe_ptr<MovieRecorder> _recorder;
public:
MovieManager(Emulator* emu);

View file

@ -16,6 +16,8 @@
#include "Shared/Movies/MovieRecorder.h"
#include "Shared/BatteryManager.h"
#include "Shared/CheatManager.h"
#include "Utilities/Serializer.h"
#include "Utilities/magic_enum.hpp"
MovieRecorder::MovieRecorder(Emulator* emu)
{
@ -72,9 +74,6 @@ bool MovieRecorder::Record(RecordMovieOptions options)
void MovieRecorder::GetGameSettings(stringstream &out)
{
EmuSettings* settings = _emu->GetSettings();
EmulationConfig emuConfig = settings->GetEmulationConfig();
InputConfig inputConfig = settings->GetInputConfig();
WriteString(out, MovieKeys::MesenVersion, settings->GetVersionString());
WriteInt(out, MovieKeys::MovieFormatVersion, MovieRecorder::MovieFormatVersion);
@ -91,43 +90,18 @@ void MovieRecorder::GetGameSettings(stringstream &out)
WriteString(out, MovieKeys::PatchedRomSha1, romFile.GetSha1Hash());
}
ConsoleRegion region = _emu->GetRegion();
switch(region) {
case ConsoleRegion::Auto:
case ConsoleRegion::Ntsc: WriteString(out, MovieKeys::Region, "NTSC"); break;
Serializer s(0, true, true);
ConsoleType consoleType = _emu->GetConsoleType();
s.Stream(consoleType, "emu.consoleType", -1);
s.Stream(*settings, "", -1);
case ConsoleRegion::Pal: WriteString(out, MovieKeys::Region, "PAL"); break;
}
std::stringstream settingsOut;
s.SaveTo(settingsOut, 0);
/*WriteString(out, MovieKeys::Controller1, ControllerTypeNames[(int)inputConfig.Controllers[0].Type]);
WriteString(out, MovieKeys::Controller2, ControllerTypeNames[(int)inputConfig.Controllers[1].Type]);
WriteString(out, MovieKeys::Controller3, ControllerTypeNames[(int)inputConfig.Controllers[2].Type]);
WriteString(out, MovieKeys::Controller4, ControllerTypeNames[(int)inputConfig.Controllers[3].Type]);
WriteString(out, MovieKeys::Controller5, ControllerTypeNames[(int)inputConfig.Controllers[4].Type]);*/
switch(_emu->GetConsoleType()) {
case ConsoleType::Snes: {
SnesConfig snesCfg = settings->GetSnesConfig();
WriteInt(out, MovieKeys::ExtraScanlinesBeforeNmi, snesCfg.PpuExtraScanlinesBeforeNmi);
WriteInt(out, MovieKeys::ExtraScanlinesAfterNmi, snesCfg.PpuExtraScanlinesAfterNmi);
WriteInt(out, MovieKeys::GsuClockSpeed, snesCfg.GsuClockSpeed);
switch(snesCfg.RamPowerOnState) {
case RamState::AllZeros: WriteString(out, MovieKeys::RamPowerOnState, "AllZeros"); break;
case RamState::AllOnes: WriteString(out, MovieKeys::RamPowerOnState, "AllOnes"); break;
case RamState::Random: WriteString(out, MovieKeys::RamPowerOnState, "AllOnes"); break; //TODO: Random memory isn't supported for movies yet
}
break;
}
default:
//TODO
break;
}
out << settingsOut.str();
for(CheatCode &code : _emu->GetCheatManager()->GetCheats()) {
//TODO
out << "Cheat " << string(code.Code) << "\n";
out << "Cheat " << magic_enum::enum_name(code.Type) << " " << string(code.Code) << "\n";
}
}
@ -212,6 +186,8 @@ void MovieRecorder::ProcessNotification(ConsoleNotificationType type, void *para
_emu->GetControlManager()->RegisterInputRecorder(this);
}
}
//TODO history viewer
/*
bool MovieRecorder::CreateMovie(string movieFile, std::deque<RewindData> &data, uint32_t startPosition, uint32_t endPosition)
{

View file

@ -13,7 +13,7 @@ class Emulator;
class MovieRecorder : public INotificationListener, public IInputRecorder, public IBatteryRecorder, public IBatteryProvider, public std::enable_shared_from_this<MovieRecorder>
{
private:
static const uint32_t MovieFormatVersion = 1;
static const uint32_t MovieFormatVersion = 2;
Emulator* _emu;
string _filename;

View file

@ -17,28 +17,6 @@ struct RecordMovieOptions
RecordMovieFrom RecordFrom = RecordMovieFrom::StartWithoutSaveData;
};
const vector<string> ConsoleRegionNames = {
"Auto",
"NTSC",
"PAL"
};
//TODO
const vector<string> ControllerTypeNames = {
"None",
"SnesController",
"SnesMouse",
"SuperScope",
"Multitap",
"NesController"
};
const vector<string> RamStateNames = {
"AllZeros",
"AllOnes",
"Random"
};
namespace MovieKeys
{
constexpr const char* MesenVersion = "MesenVersion";
@ -48,16 +26,4 @@ namespace MovieKeys
constexpr const char* PatchFile = "PatchFile";
constexpr const char* PatchFileSha1 = "PatchFileSHA1";
constexpr const char* PatchedRomSha1 = "PatchedRomSHA1";
constexpr const char* Region = "Region";
constexpr const char* ConsoleType = "ConsoleType";
constexpr const char* Controller1 = "Controller1";
constexpr const char* Controller2 = "Controller2";
constexpr const char* Controller3 = "Controller3";
constexpr const char* Controller4 = "Controller4";
constexpr const char* Controller5 = "Controller5";
constexpr const char* ExtraScanlinesBeforeNmi = "ExtraScanlinesBeforeNmi";
constexpr const char* ExtraScanlinesAfterNmi = "ExtraScanlinesAfterNmi";
constexpr const char* RamPowerOnState = "RamPowerOnState";
constexpr const char* InputPollScanline = "InputPollScanline";
constexpr const char* GsuClockSpeed = "GsuClockSpeed";
};

View file

@ -160,7 +160,6 @@ struct AudioConfig
uint32_t AudioPlayerSilenceDelay = 3;
};
//Update ControllerTypeNames when changing this
enum class ControllerType
{
None,

View file

@ -537,11 +537,8 @@ namespace Mesen.ViewModels
new MainMenuAction() {
ActionType = ActionType.Stop,
IsEnabled = () => IsGameRunning && (RecordApi.MovieRecording() || RecordApi.MoviePlaying()),
OnClick = async () => {
string? filename = await FileDialogHelper.OpenFile(ConfigManager.MovieFolder, wnd, FileDialogHelper.MesenMovieExt);
if(filename != null) {
RecordApi.MovieStop();
}
OnClick = () => {
RecordApi.MovieStop();
}
}
}

View file

@ -61,10 +61,11 @@ namespace Mesen.Windows
new("nes_ntsc", "blargg", "LGPL", "http://slack.net/~ant/"),
new("snes_ntsc", "blargg", "LGPL", "http://slack.net/~ant/"),
new("stb_vorbis", "", "Public domain", "https://github.com/nothings/stb"),
new("emu2413.c (Mitsukata Okazaki)", "Mitsukata Okazaki", "?", ""),
new("emu2413", "Mitsukata Okazaki", "MIT", "https://github.com/digital-sound-antiques/emu2413"),
new("SDD-1 Decomp. (Andreas Naive)", "Andreas Naive", "Public domain", ""),
new("LED Icons", "", "CC BY 4.0", "http://led24.de"),
new("SDL2", "", "zlib", "https://www.libsdl.org/")
new("SDL2", "", "zlib", "https://www.libsdl.org/"),
new("magic_enum", "", "MIT", "https://github.com/Neargye/magic_enum")
};
LibraryList.Sort((a, b) => a.Name.CompareTo(b.Name));

View file

@ -4,10 +4,11 @@
#include "ISerializable.h"
#include "miniz.h"
Serializer::Serializer(uint32_t version, bool forSave)
Serializer::Serializer(uint32_t version, bool forSave, bool useTextFormat)
{
_version = version;
_saving = forSave;
_useTextFormat = useTextFormat;
if(forSave) {
_data.reserve(0x50000);
}
@ -19,6 +20,10 @@ bool Serializer::LoadFrom(istream &file)
return false;
}
if(_useTextFormat) {
return LoadFromTextFormat(file);
}
char value = 0;
file.get(value);
bool isCompressed = value == 1;
@ -95,24 +100,83 @@ bool Serializer::LoadFrom(istream &file)
return _values.size() > 0;
}
bool Serializer::LoadFromTextFormat(istream& file)
{
uint32_t pos = (uint32_t)file.tellg();
file.seekg(0, std::ios::end);
uint32_t stateSize = (uint32_t)file.tellg() - pos;
file.seekg(pos, std::ios::beg);
_data = vector<uint8_t>(stateSize, 0);
file.read((char*)_data.data(), stateSize);
uint32_t size = (uint32_t)_data.size();
uint32_t i = 0;
string key;
while(i < size) {
key.clear();
for(uint32_t j = i; j < size; j++) {
if(_data[j] == ' ') {
key.append((char*)&_data[i], j - i);
break;
} else if(_data[j] < ' ' || _data[j] >= 127) {
//invalid characters in key, state is invalid
return false;
}
}
if(key.empty()) {
//invalid
return false;
}
i += (uint32_t)key.size() + 1;
if(i >= size - 4) {
//invalid
return false;
}
uint32_t valueSize = 0;
for(uint32_t j = i; j < size; j++) {
if(_data[j] == '\n') {
valueSize = j - i;
break;
}
}
if(i + valueSize > size) {
//invalid
return false;
}
_values.emplace(key, SerializeValue(&_data[i], valueSize));
i += valueSize + 1;
}
}
void Serializer::SaveTo(ostream& file, int compressionLevel)
{
bool isCompressed = compressionLevel > 0;
file.put((char)isCompressed);
if(isCompressed) {
unsigned long compressedSize = compressBound((unsigned long)_data.size());
uint8_t* compressedData = new uint8_t[compressedSize];
compress2(compressedData, &compressedSize, (unsigned char*)_data.data(), (unsigned long)_data.size(), compressionLevel);
uint32_t size = (uint32_t)compressedSize;
uint32_t originalSize = (uint32_t)_data.size();
file.write((char*)&originalSize, sizeof(uint32_t));
file.write((char*)&size, sizeof(uint32_t));
file.write((char*)compressedData, compressedSize);
delete[] compressedData;
} else {
if(_useTextFormat) {
file.write((char*)_data.data(), _data.size());
} else {
bool isCompressed = compressionLevel > 0;
file.put((char)isCompressed);
if(isCompressed) {
unsigned long compressedSize = compressBound((unsigned long)_data.size());
uint8_t* compressedData = new uint8_t[compressedSize];
compress2(compressedData, &compressedSize, (unsigned char*)_data.data(), (unsigned long)_data.size(), compressionLevel);
uint32_t size = (uint32_t)compressedSize;
uint32_t originalSize = (uint32_t)_data.size();
file.write((char*)&originalSize, sizeof(uint32_t));
file.write((char*)&size, sizeof(uint32_t));
file.write((char*)compressedData, compressedSize);
delete[] compressedData;
} else {
file.write((char*)_data.data(), _data.size());
}
}
}
@ -132,4 +196,3 @@ void Serializer::PopNamePrefix()
{
_prefixes.pop_back();
}

View file

@ -3,6 +3,7 @@
#include "stdafx.h"
#include "Utilities/ISerializable.h"
#include "Utilities/FastString.h"
#include "Utilities/magic_enum.hpp"
class Serializer;
@ -42,8 +43,11 @@ private:
uint32_t _version = 0;
bool _saving = false;
bool _useTextFormat = false;
private:
bool LoadFromTextFormat(istream& file);
string GetKey(const char* name, int index)
{
string key;
@ -110,8 +114,45 @@ private:
}
}
template<typename T>
void WriteTextFormat(string key, T& value)
{
//Write key
_data.insert(_data.end(), key.begin(), key.end());
_data.push_back(' ');
if constexpr(std::is_enum<T>::value) {
auto enumStr = magic_enum::enum_name(value);
_data.insert(_data.end(), enumStr.begin(), enumStr.end());
} else if constexpr(std::is_same<T, bool>::value) {
string boolStr = value ? "true" : "false";
_data.insert(_data.end(), boolStr.begin(), boolStr.end());
} else {
auto valueStr = std::to_string(value);
_data.insert(_data.end(), valueStr.begin(), valueStr.end());
}
_data.push_back('\n');
}
template<typename T>
void ReadTextFormat(SerializeValue& savedValue, T& value)
{
string textValue(savedValue.DataPtr, savedValue.DataPtr + savedValue.Size);
if constexpr(std::is_enum<T>::value) {
auto enumValue = magic_enum::enum_cast<T>(textValue);
if(enumValue.has_value()) {
value = enumValue.value();
}
} else if constexpr(std::is_same<T, bool>::value) {
value = textValue == "true";
} else {
if(textValue.find_first_not_of("0123456789") == string::npos) {
value = (T)std::stol(textValue);
}
}
}
public:
Serializer(uint32_t version, bool forSave);
Serializer(uint32_t version, bool forSave, bool useTextFormat = false);
uint32_t GetVersion() { return _version; }
bool IsSaving() { return _saving; }
@ -138,23 +179,31 @@ public:
}
if(_saving) {
//Write key
_data.insert(_data.end(), key.begin(), key.end());
_data.push_back(0);
if(_useTextFormat) {
WriteTextFormat(key, value);
} else {
//Write key
_data.insert(_data.end(), key.begin(), key.end());
_data.push_back(0);
//Write value size
WriteValue((uint32_t)sizeof(T));
//Write value size
WriteValue((uint32_t)sizeof(T));
//Write value
WriteValue(value);
//Write value
WriteValue(value);
}
} else {
auto result = _values.find(key);
if(result != _values.end()) {
SerializeValue& savedValue = result->second;
if(savedValue.Size >= sizeof(T)) {
ReadValue(value, savedValue.DataPtr);
if(_useTextFormat) {
ReadTextFormat(savedValue, value);
} else {
value = (T)0;
if(savedValue.Size >= sizeof(T)) {
ReadValue(value, savedValue.DataPtr);
} else {
value = (T)0;
}
}
} else {
value = (T)0;

View file

@ -202,6 +202,7 @@
<ClInclude Include="HQX\hqx.h" />
<ClInclude Include="ISerializable.h" />
<ClInclude Include="KreedSaiEagle\SaiEagle.h" />
<ClInclude Include="magic_enum.hpp" />
<ClInclude Include="md5.h" />
<ClInclude Include="miniz.h" />
<ClInclude Include="AutoResetEvent.h" />

View file

@ -172,6 +172,7 @@
<ClInclude Include="CRC32.h" />
<ClInclude Include="md5.h" />
<ClInclude Include="sha1.h" />
<ClInclude Include="magic_enum.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="xBRZ\xbrz.cpp">

1378
Utilities/magic_enum.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -52,3 +52,15 @@ public:
return _ptr;
}
};
template<typename T>
bool operator==(const safe_ptr<T>& ptr, nullptr_t)
{
return !(bool)ptr;
}
template<typename T>
bool operator!=(const safe_ptr<T>& ptr, nullptr_t)
{
return (bool)ptr;
}