Skip to content

Commit

Permalink
Add settings backup functionality
Browse files Browse the repository at this point in the history
- Simplified JSON Parser
- Save/Load functions to send/receive settings to/from the hook DLL
  • Loading branch information
Foifur committed Feb 18, 2025
1 parent 0d8b4f7 commit 2eda93d
Show file tree
Hide file tree
Showing 6 changed files with 374 additions and 15 deletions.
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1897,7 +1897,10 @@ if(USE_VANGUARD)
target_sources(${PROJECT_NAME} PRIVATE
core/Vanguard/VanguardHelpers.h
core/Vanguard/VanguardHelpers.cpp
core/Vanguard/VanguardClientInitializer.h)
core/Vanguard/VanguardClientInitializer.h
core/Vanguard/VanguardJsonParser.h
core/Vanguard/VanguardJsonParser.cpp
core/Vanguard/VanguardEmuSettings.h)
endif()

if(${CMAKE_GENERATOR} MATCHES "^Xcode.*|^Visual Studio.*")
Expand Down
67 changes: 67 additions & 0 deletions core/Vanguard/VanguardEmuSettings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#pragma once
#include "cfg/option.h"
#include "emulator.h"
#include "Vanguard/VanguardJsonParser.h"
#include <string>
#include <variant>
#include <format>

#define VAR_NAME(Variable) (#Variable)

using SettingsTypes = std::variant<bool, int, float>;
using SettingsArray = std::vector< std::pair<std::string, SettingsTypes>>;

class VanguardSettings
{
public:
SettingsArray array;
std::pair<std::string, bool> Vsync;
std::pair<std::string, bool> FloatVMUs;
std::pair<std::string, int> Sh4Clock;

// save the current value of required settings
void SaveSettings()
{
save_setting<bool>(VAR_NAME(Vsync), Vsync, &config::VSync);
save_setting<bool>(VAR_NAME(FloatVMUs), FloatVMUs, &config::FloatVMUs);
save_setting<int>(VAR_NAME(Sh4Clock), Sh4Clock, &config::Sh4Clock);

}

// load the settings values sent from the dll hook into the emulator's settings
void LoadSettings(JsonParser::JsonValue settings)
{
load_setting<bool>(&config::VSync, (*settings.json)[VAR_NAME(Vsync)]);
load_setting<bool>(&config::FloatVMUs, (*settings.json)[VAR_NAME(FloatVMUs)]);
load_setting<int>(&config::Sh4Clock, (*settings.json)[VAR_NAME(Sh4Clock)]);

::SaveSettings();
}

// Visits the variant value in a pair, determines the correct data type and returns it as a string
std::string to_string(SettingsTypes var)
{
return std::visit([](auto arg) {return std::format("{}", arg); }, var);
}

private:
// saves the name and value of the setting to a pair, then push it onto the array
template<typename T>
void save_setting(std::string name, std::pair<std::string, SettingsTypes> variable, config::Option<T>* setting)
{
variable.first = name;
variable.second = *setting;
array.push_back(variable);
}

// loads the value of the parsed setting based on the requested data type
template<typename T>
void load_setting(config::Option<T>* setting, JsonParser::JsonValue value)
{
if (typeid(T) == typeid(bool))
*setting = value.b;

else if (typeid(T) == typeid(int))
*setting = value.i;
}
};
126 changes: 112 additions & 14 deletions core/Vanguard/VanguardHelpers.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
#include "..\core\Vanguard\VanguardHelpers.h"
#include "..\core\Vanguard\VanguardClientInitializer.h"
#include "Vanguard/VanguardHelpers.h"
#include "Vanguard/VanguardClientInitializer.h"
#include "Vanguard/VanguardJsonParser.h"
#include "Vanguard/VanguardEmuSettings.h"

#include "..\core\hw\sh4\sh4_mem.h"
#include "..\core\hw\pvr\pvr_mem.h"
#include "..\core\hw\aica\aica_if.h"
#include "..\core\hw\flashrom\nvmem.h"
#include "..\core\hw\pvr\elan.h"
#include "..\core\emulator.h"
#include "..\core\ui\gui_util.h"
#include "hw\sh4\sh4_mem.h"
#include "hw\pvr\pvr_mem.h"
#include "hw\aica\aica_if.h"
#include "hw\flashrom\nvmem.h"
#include "hw\pvr\elan.h"
#include "emulator.h"
#include "ui\gui_util.h"

#include <codecvt>
#include <sstream>

void FormatJsonData(VanguardSettings& settings, std::ostringstream& json_string);

unsigned char Vanguard_peekbyte(long long addr, int selection)
{
Expand Down Expand Up @@ -163,16 +169,62 @@ void Vanguard_forceStop()
std::string VanguardClient::system_core = "EMPTY";
char* Vanguard_getSystemCore()
{
return VanguardClient::system_core.data();
// store the output as a string, then convert it to char*
std::string tmp = VanguardClient::system_core;

std::vector<char> _output(tmp.begin(), tmp.end());
_output.push_back('\0');

char* output = (char*)LocalAlloc(LMEM_FIXED, _output.size() + 1);
if (!output)
return NULL;

memcpy(output, _output.data(), _output.size() + 1);

return output;
}

// Saves all required emulator settings and returns it to the hook DLL to store with the savestate
char* Vanguard_saveEmuSettings()
{
// create a new settings class and store all values
VanguardSettings _settings;
_settings.SaveSettings();

// write the json data to a stringstream
std::ostringstream out;
FormatJsonData(_settings, out);

// store the output as a string, then convert it to char*
std::string tmp = out.str();

std::vector<char> _output(tmp.begin(), tmp.end());
_output.push_back('\0');

char* output = (char*)LocalAlloc(LMEM_FIXED, _output.size() + 1);
if (!output)
return NULL;

memcpy(output, _output.data(), _output.size() + 1);
return output;
}

// Loads all required emulator settings sent by the hook DLL before loading the savestate
void Vanguard_loadEmuSettings(BSTR settings)
{
JsonParser::JsonValue parsed_settings = JsonParser::ParseJson(settings);

VanguardSettings _settings;
_settings.LoadSettings(parsed_settings);
}

//converts a BSTR received from the Vanguard client to std::string
std::string BSTRToString(BSTR string)
{
std::wstring ws(string, SysStringLen(string));
//std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
std::string converted_string = _bstr_t(string);
return converted_string;
std::wstring ws(string, SysStringLen(string));
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
std::string converted_string = converter.to_bytes(ws);
return converted_string;
}

std::string getDirectory()
Expand All @@ -182,3 +234,49 @@ std::string getDirectory()
std::string::size_type pos = std::string(buffer).find_last_of("\\/");
return std::string(buffer).substr(0, pos);
}

// formats the saved settings into a JSON format
void FormatJsonData(VanguardSettings& settings, std::ostringstream& json_string)
{
// beginning of json string
json_string << "{\n";

// iterate through all settings
for (int i = 0; i < settings.array.size(); i++)
{
// flycast stores bools as "yes" and "no" so we need this hack
if (std::holds_alternative<bool>(settings.array[i].second))
{
if (settings.to_string(settings.array[i].second).find("false"))
{
json_string << " \"" << settings.array[i].first
<< "\": " << "yes";
}
else
{
json_string << " \"" << settings.array[i].first
<< "\": " << "no";
}
}
else
{
json_string << " \"" << settings.array[i].first
<< "\": " << settings.to_string(settings.array[i].second);
}

// if the value is a whole number float, add ".0" so the parser understands
if (std::holds_alternative<float>(settings.array[i].second))
{
json_string << ".0";
}

// only add a comma if there are more values to be parsed
if (i + 1 < settings.array.size())
json_string << ",\n";
else
json_string << "\n";
}

// end of json string
json_string << "}";
}
4 changes: 4 additions & 0 deletions core/Vanguard/VanguardHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ EXPORT void Vanguard_forceStop();

EXPORT char* Vanguard_getSystemCore();

EXPORT char* Vanguard_saveEmuSettings();

EXPORT void Vanguard_loadEmuSettings(BSTR settings);

class VanguardClient
{
public:
Expand Down
150 changes: 150 additions & 0 deletions core/Vanguard/VanguardJsonParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// pulled and modified from https://github.com/UponTheSky/ray-tracing-in-series/blob/json-parser-devto/ray_tracer/utils/json_parser.cpp //
// //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


#include "Vanguard/VanguardJsonParser.h"
#include "Vanguard/VanguardHelpers.h"
#include <sstream>
#include <algorithm>

using namespace JsonParser;

JsonValue JsonParser::ParseJson(const BSTR& data)
{
// 1. read the text data
std::string text;
ReadData(data, text);

// 2. parse the text with the helper function and return
text_it start = text.begin();
return ParseJsonHelper(text, start);
}

void JsonParser::ReadData(const BSTR& data, std::string& output)
{
std::string line;

std::string converted_data = BSTRToString(data);

std::stringstream json_data;
json_data << converted_data;

while (std::getline(json_data, line))
{
output.append(line);
}
}


JsonValue JsonParser::ParsePrimitive(const std::string& text, text_it start, text_it end)
{
std::string substr = text.substr(start - text.begin(), end - start);

// flycast stores "yes" and "no" instead of "true" and "false" for...some reason
if (substr.find("yes") != std::string::npos)
{
return {.b = true};
}
else if (substr.find("no") != std::string::npos)
{
return {.b = false};
}
else if (substr.find(".") != std::string::npos)
{
return {.d = std::stod(substr)};
}
else
{
return {.i = std::stoi(substr)};
}
}

std::pair<std::string, JsonValue> JsonParser::RetriveKeyValuePair(const std::string& text,
text_it& it)
{
if (it == text.end())
throw std::invalid_argument("string is empty");

// ignore white spaces & line breaks
while (*it == ' ' || *it == '\n')
{
it++;
}

text_it curr_it;
std::string key;
JsonValue value;
// if hit a double quote for the first time, it is a key
if (*it == '\"')
{
curr_it = ++it;
while (*it != '\"')
{
it++;
}

key = text.substr(curr_it - text.begin(), it - curr_it);
if (*(++it) == ':')
it++;
else
throw std::invalid_argument("did not find ':' designating a value to the key");
it++;
}

// now we need to have its corresponding value
while (*it == ' ' || *it == '\n')
{
it++;
}

if (*it == '{')
{
// another json format
value = ParseJsonHelper(text, it);
}
else
{
// primitive value(double or int)
curr_it = it;
while (*it != ',' && *it != '\n' && *it != '}')
{
it++;
}
value = ParsePrimitive(text, curr_it, it);
}

// after parsing the value, check whether the current iterator points to a comma
if (*it == ',')
{
it++;
}

return std::make_pair(key, value);
}

JsonValue JsonParser::ParseJsonHelper(const std::string& text, text_it& it)
{
if (*it != '{') // must start with the left curly bracket
throw std::invalid_argument("string does not start with '{'");
it++;

std::map<std::string, JsonValue>* json_map = new std::map<std::string, JsonValue>;

do
{
const auto [key, value] = RetriveKeyValuePair(text, it);
(*json_map)[key] = value;

while (*it == ' ' || *it == '\n')
{
it++;
}
} while (*it != '}');

it++; // after '}'

return {.json = json_map};
}
Loading

0 comments on commit 2eda93d

Please sign in to comment.