fixed thread mutex locking issues, fixed ManageVarByBool return address not getting the correct return addr (because of stacked function calls inside ManageVarByBool), fixed some player variables list issues

This commit is contained in:
EricPlayZ
2025-02-23 23:25:22 +02:00
parent 88857a7da8
commit 83a7efed93
16 changed files with 102 additions and 83 deletions

View File

@ -2,6 +2,7 @@
#include <unordered_map>
#include <string>
#include <mutex>
#include <shared_mutex>
#include <variant>
#include <type_traits>
#include <EGSDK\Exports.h>
@ -34,7 +35,8 @@ namespace EGSDK::Engine {
VarType GetType() const;
void SetType(VarType type);
protected:
static std::recursive_mutex mutex;
static std::mutex writingMutex;
static std::shared_mutex readingMutex;
private:
static std::unordered_map<const VarBase*, std::string> varNames;
static std::unordered_map<const VarBase*, VarType> varTypes;

View File

@ -2,6 +2,7 @@
#include <string>
#include <any>
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
#include <intrin.h>
#include <variant>
@ -27,7 +28,7 @@ namespace EGSDK::Engine {
static VarMapT defaultVars;
static VarMapT defaultCustomVars;
static std::optional<VarRef<VarMapT, VarT>> GetVarRef(VarT* var);
static std::optional<VarRef<VarMapT, VarT>> GetVarRefFromPtr(VarT* var);
static std::optional<VarRef<VarMapT, VarT>> GetVarRef(const char* name);
static std::optional<VarRef<VarMapT, VarT>> GetCustomVarRef(const char* name);
static std::optional<VarRef<VarMapT, VarT>> GetDefaultVarRef(const char* name);
@ -41,13 +42,14 @@ namespace EGSDK::Engine {
static void ManageVarByBool(const char* name, T valueIfTrue, T valueIfFalse, bool boolVal, bool usePreviousVal = true) {
auto playerVar = GetVarRef(name);
if (playerVar)
_ManageByBool(&*playerVar, valueIfTrue, valueIfFalse, boolVal, usePreviousVal);
_ManageByBool(_ReturnAddress(), &*playerVar, valueIfTrue, valueIfFalse, boolVal, usePreviousVal);
}
private:
static std::unordered_map<std::string, std::any> prevVarValueMap;
static std::unordered_map<std::string, bool> prevBoolValueMap;
static std::unordered_map<std::string, uint64_t> varOwnerMap;
static std::recursive_mutex mutex;
static std::mutex writingMutex;
static std::shared_mutex readingMutex;
static std::optional<VarRef<VarMapT, VarT>> _GetVarRef(const char* name, VarMapT& map);
@ -66,15 +68,16 @@ namespace EGSDK::Engine {
auto defVar = GetDefaultVarRef(name);
if constexpr (std::is_same_v<T, std::string>) {
std::string valueStr = Utils::Values::to_string(value);
switch (var->GetType()) {
case VarType::Float:
_SetValueFromList<float>(var, std::stof(Utils::Values::to_string(value)));
_SetValueFromList<float>(var, std::stof(valueStr));
return;
case VarType::Int:
_SetValueFromList<int>(var, std::stof(Utils::Values::to_string(value)));
_SetValueFromList<int>(var, std::stof(valueStr));
return;
case VarType::Bool:
_SetValueFromList<bool>(var, std::stof(Utils::Values::to_string(value)));
_SetValueFromList<bool>(var, !_strcmpi(valueStr.c_str(), "true"));
return;
default:
break;
@ -93,12 +96,12 @@ namespace EGSDK::Engine {
var->SetValue(value);
}
template <AllowedVarTypes T>
static void _ManageByBool(VarRef<VarMapT, VarT>* var, T valueIfTrue, T valueIfFalse, bool boolVal, bool usePreviousVal = true) {
static void _ManageByBool(void* returnAddr, VarRef<VarMapT, VarT>* var, T valueIfTrue, T valueIfFalse, bool boolVal, bool usePreviousVal = true) {
if (!var)
return;
uint64_t caller = reinterpret_cast<uint64_t>(_ReturnAddress());
std::lock_guard lock(mutex);
uint64_t caller = reinterpret_cast<uint64_t>(returnAddr);
std::shared_lock lock(readingMutex);
const char* name = var->GetName();

View File

@ -3,6 +3,7 @@
#include <vector>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <functional>
#include <string>
#include <type_traits>
@ -32,13 +33,14 @@ namespace EGSDK::Engine {
template <typename Callable, typename... Args>
void ForEach(Callable&& func, Args&&... args) {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
for (const auto& name : varsOrdered)
func(vars.at(name), std::forward<Args>(args)...);
}
protected:
std::unordered_map<std::string, std::unique_ptr<VarT>> vars;
std::vector<std::string> varsOrdered;
mutable std::recursive_mutex mutex;
mutable std::mutex writingMutex;
mutable std::shared_mutex readingMutex;
};
}

View File

@ -34,15 +34,16 @@ namespace EGSDK::Engine {
return;
if constexpr (std::is_same_v<T, std::string>) {
std::string valueStr = Utils::Values::to_string(value);
switch (ptr->GetType()) {
case VarType::Float:
SetValue<float>(std::stof(Utils::Values::to_string(value)));
SetValue<float>(std::stof(valueStr));
return;
case VarType::Int:
SetValue<int>(std::stof(Utils::Values::to_string(value)));
SetValue<int>(std::stof(valueStr));
return;
case VarType::Bool:
SetValue<bool>(std::stof(Utils::Values::to_string(value)));
SetValue<bool>(!_strcmpi(valueStr.c_str(), "true"));
return;
default:
break;
@ -72,6 +73,6 @@ namespace EGSDK::Engine {
}
protected:
const char* name;
VarT* ptr;
VarT* ptr = nullptr;
};
}

View File

@ -48,6 +48,7 @@ namespace EGSDK::GamePH {
class EGameSDK_API PlayerVariables : public Engine::VarManagerBase<PlayerVarMap, PlayerVar> {
public:
using Base = VarManagerBase<PlayerVarMap, PlayerVar>;
using Base::GetVarRefFromPtr;
using Base::GetVarRef;
using Base::GetCustomVarRef;
using Base::GetDefaultVarRef;

View File

@ -6,7 +6,7 @@ namespace EGSDK::Engine {
CVar::CVar(const std::string& name) : VarBase(name) {}
CVar::CVar(const std::string& name, VarType type) : VarBase(name, type) {}
VarValueType& CVar::GetValue() {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
auto it = varValues.find(this);
if (it == varValues.end()) {
switch (GetType()) {
@ -47,7 +47,7 @@ namespace EGSDK::Engine {
}
}
void CVar::SetValue(const VarValueType& value) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
auto& varData = varValues[this];
std::visit([&](auto&& val) {
@ -70,7 +70,7 @@ namespace EGSDK::Engine {
}, value);
}
void CVar::AddValuePtr(uint64_t* ptr) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
varValues[this].valuePtrs.push_back(ptr);
}
@ -88,7 +88,7 @@ namespace EGSDK::Engine {
}
std::unique_ptr<CVar>& CVarMap::try_emplace(std::unique_ptr<CVar> cVar) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
const std::string& name = cVar->GetName();
auto [it, inserted] = vars.try_emplace(name, std::move(cVar));
if (inserted) {
@ -99,12 +99,12 @@ namespace EGSDK::Engine {
return it->second;
}
CVar* CVarMap::Find(uint32_t valueOffset) const {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
auto it = varsByValueOffset.find(valueOffset);
return it == varsByValueOffset.end() ? nullptr : it->second;
}
void CVarMap::Erase(const std::string& name) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
auto it = vars.find(name);
if (it == vars.end())
return;
@ -118,7 +118,7 @@ namespace EGSDK::Engine {
}
bool CVarMap::none_of(uint32_t valueOffset) {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
return varsByValueOffset.find(valueOffset) == varsByValueOffset.end();
}

View File

@ -3,36 +3,35 @@
namespace EGSDK::Engine {
std::unordered_map<const VarBase*, std::string> VarBase::varNames{};
std::unordered_map<const VarBase*, VarType> VarBase::varTypes{};
std::recursive_mutex VarBase::mutex{};
std::mutex VarBase::writingMutex{};
std::shared_mutex VarBase::readingMutex{};
VarBase::VarBase(const std::string& name, VarType type) {
std::lock_guard lock(mutex);
SetName(name);
SetType(type);
}
VarBase::~VarBase() {
std::lock_guard lock(mutex);
varNames.erase(this);
varTypes.erase(this);
}
const char* VarBase::GetName() const {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
auto it = varNames.find(this);
return it != varNames.end() ? it->second.c_str() : nullptr;
}
void VarBase::SetName(const std::string& newName) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
varNames[this] = newName;
}
VarType VarBase::GetType() const {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
auto it = varTypes.find(this);
return it != varTypes.end() ? it->second : VarType::NONE;
}
void VarBase::SetType(VarType newType) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
varTypes[this] = newType;
}
}

View File

@ -21,10 +21,12 @@ namespace EGSDK::Engine {
std::unordered_map<std::string, uint64_t> VarManagerBase<VarMapT, VarT>::varOwnerMap{};
template <typename VarMapT, typename VarT>
std::recursive_mutex VarManagerBase<VarMapT, VarT>::mutex{};
std::mutex VarManagerBase<VarMapT, VarT>::writingMutex{};
template <typename VarMapT, typename VarT>
std::shared_mutex VarManagerBase<VarMapT, VarT>::readingMutex{};
template <typename VarMapT, typename VarT>
std::optional<VarRef<VarMapT, VarT>> VarManagerBase<VarMapT, VarT>::GetVarRef(VarT* var) {
std::optional<VarRef<VarMapT, VarT>> VarManagerBase<VarMapT, VarT>::GetVarRefFromPtr(VarT* var) {
return var ? std::optional<VarRef<VarMapT, VarT>>(VarRef<VarMapT, VarT>(var)) : std::nullopt;
}
template <typename VarMapT, typename VarT>
@ -61,7 +63,7 @@ namespace EGSDK::Engine {
template <typename VarMapT, typename VarT>
bool VarManagerBase<VarMapT, VarT>::AreAllCustomVarsManagedByBool() {
bool allManagedByBool = true;
vars.ForEach([&allManagedByBool](const std::unique_ptr<VarT>& varPtr) {
customVars.ForEach([&allManagedByBool](const std::unique_ptr<VarT>& varPtr) {
if (!_IsManagedByBool(varPtr->GetName())) {
allManagedByBool = false;
return;
@ -72,7 +74,7 @@ namespace EGSDK::Engine {
template <typename VarMapT, typename VarT>
bool VarManagerBase<VarMapT, VarT>::_IsManagedByBool(const char* name) {
std::lock_guard<std::recursive_mutex> lock(mutex);
std::shared_lock lock(readingMutex);
return (prevBoolValueMap.find(name) != prevBoolValueMap.end()) && prevBoolValueMap[name];
}
template <typename VarMapT, typename VarT>

View File

@ -5,11 +5,11 @@
namespace EGSDK::Engine {
template <typename VarT>
VarMapBase<VarT>::VarMapBase() : vars(), varsOrdered(), mutex() {}
VarMapBase<VarT>::VarMapBase() : vars(), varsOrdered(), writingMutex(), readingMutex() {}
template <typename VarT>
std::unique_ptr<VarT>& VarMapBase<VarT>::try_emplace(std::unique_ptr<VarT> var) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
const std::string& name = var->GetName();
auto [it, inserted] = vars.try_emplace(name, std::move(var));
if (inserted)
@ -19,14 +19,14 @@ namespace EGSDK::Engine {
template <typename VarT>
VarT* VarMapBase<VarT>::Find(const std::string& name) const {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
auto it = vars.find(name);
return (it != vars.end()) ? it->second.get() : nullptr;
}
template <typename VarT>
void VarMapBase<VarT>::Erase(const std::string& name) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
auto it = vars.find(name);
if (it == vars.end())
return;
@ -38,25 +38,25 @@ namespace EGSDK::Engine {
template <typename VarT>
bool VarMapBase<VarT>::empty() const {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
return vars.empty();
}
template <typename VarT>
bool VarMapBase<VarT>::none_of(const std::string& name) const {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
return vars.find(name) == vars.end();
}
template <typename VarT>
size_t VarMapBase<VarT>::size() {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
return vars.size();
}
template <typename VarT>
void VarMapBase<VarT>::reserve(size_t count) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
vars.reserve(count);
varsOrdered.reserve(count);
}

View File

@ -15,7 +15,7 @@ namespace EGSDK::GamePH {
PlayerVar::PlayerVar(const std::string& name) : VarBase(name) {}
PlayerVar::PlayerVar(const std::string& name, Engine::VarType type) : Engine::VarBase(name, type) {}
Engine::VarValueType PlayerVar::GetValue() {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
switch (GetType()) {
case Engine::VarType::String:
return reinterpret_cast<const char*>(reinterpret_cast<uint64_t>(this->strValue.data) & 0x1FFFFFFFFFFFFFFF);
@ -28,7 +28,7 @@ namespace EGSDK::GamePH {
}
}
Engine::VarValueType PlayerVar::GetDefaultValue() {
std::lock_guard lock(mutex);
std::shared_lock lock(readingMutex);
switch (GetType()) {
case Engine::VarType::String:
return reinterpret_cast<const char*>(reinterpret_cast<uint64_t>(this->defaultStrValue.data) & 0x1FFFFFFFFFFFFFFF);
@ -41,7 +41,7 @@ namespace EGSDK::GamePH {
}
}
void PlayerVar::SetValue(const Engine::VarValueType& value) {
std::lock_guard lock(mutex);
std::lock_guard lock(writingMutex);
std::visit([&](auto&& val) {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, std::string>) {

View File

@ -2,6 +2,7 @@
#include <functional>
#include <vector>
#include <mutex>
#include <shared_mutex>
namespace EGT::ImGui_impl {
class DeferredActions {
@ -13,6 +14,7 @@ namespace EGT::ImGui_impl {
static void Process();
private:
static std::vector<std::function<void()>> actions;
static std::mutex mutex;
static std::mutex writingMutex;
static std::shared_mutex readingMutex;
};
}

View File

@ -2,6 +2,7 @@
#include <functional>
#include <vector>
#include <mutex>
#include <shared_mutex>
namespace EGT::ImGui_impl {
class NextFrameTask {
@ -16,7 +17,8 @@ namespace EGT::ImGui_impl {
Task task{};
};
static std::vector<TaskEntry>& GetTaskQueue();
static std::mutex& GetMutex();
static std::vector<TaskEntry> taskQueue;
static std::mutex writingMutex;
static std::shared_mutex readingMutex;
};
}

View File

@ -2,25 +2,26 @@
namespace EGT::ImGui_impl {
std::vector<std::function<void()>> DeferredActions::actions{};
std::mutex DeferredActions::mutex{};
std::mutex DeferredActions::writingMutex{};
std::shared_mutex DeferredActions::readingMutex{};
void DeferredActions::Add(const std::function<void()>& action) {
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard lock(writingMutex);
actions.push_back(action);
}
void DeferredActions::Clear() {
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard lock(writingMutex);
actions.clear();
}
bool DeferredActions::HasPendingActions() {
std::lock_guard<std::mutex> lock(mutex);
std::shared_lock lock(readingMutex);
return !actions.empty();
}
void DeferredActions::Process() {
std::vector<std::function<void()>> actionsToProcess;
{
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard lock(writingMutex);
actionsToProcess.swap(actions);
}

View File

@ -1,31 +1,24 @@
#include <EGT\ImGui_impl\NextFrameTask.h>
namespace EGT::ImGui_impl {
std::vector<NextFrameTask::TaskEntry> NextFrameTask::taskQueue;
std::mutex NextFrameTask::writingMutex{};
std::shared_mutex NextFrameTask::readingMutex{};
void NextFrameTask::AddTask(const Task& task, int delayFrames) {
std::lock_guard<std::mutex> lock(GetMutex());
GetTaskQueue().emplace_back(TaskEntry{ delayFrames, task });
std::lock_guard lock(writingMutex);
taskQueue.emplace_back(TaskEntry{ delayFrames, task });
}
void NextFrameTask::ExecuteTasks() {
std::lock_guard<std::mutex> lock(GetMutex());
auto& queue = GetTaskQueue();
for (auto it = queue.begin(); it != queue.end();) {
std::lock_guard lock(writingMutex);
for (auto it = taskQueue.begin(); it != taskQueue.end();) {
if (it->framesRemaining <= 0) {
it->task();
it = queue.erase(it);
it = taskQueue.erase(it);
} else {
--it->framesRemaining;
++it;
}
}
}
std::vector<NextFrameTask::TaskEntry>& NextFrameTask::GetTaskQueue() {
static std::vector<TaskEntry> taskQueue;
return taskQueue;
}
std::mutex& NextFrameTask::GetMutex() {
static std::mutex mutex;
return mutex;
}
}

View File

@ -36,7 +36,7 @@ namespace EGT::Menu {
return lowerKey.find(lowerFilter) != std::string::npos;
}
static void RestoreVariableToDefault(const std::unique_ptr<EGSDK::Engine::CVar>& cVarPtr) {
auto cVar = EGSDK::Engine::CVars::GetVarRef(cVarPtr.get());
auto cVar = EGSDK::Engine::CVars::GetVarRefFromPtr(cVarPtr.get());
ImGui_impl::DeferredActions::Add([cVar]() mutable {
switch (cVar->GetType()) {
@ -63,7 +63,7 @@ namespace EGT::Menu {
});
}
static void RenderRendererCVar(const std::unique_ptr<EGSDK::Engine::CVar>& cVarPtr) {
auto cVar = EGSDK::Engine::CVars::GetVarRef(cVarPtr.get());
auto cVar = EGSDK::Engine::CVars::GetVarRefFromPtr(cVarPtr.get());
ImGui::BeginDisabled(cVar->IsManagedByBool());
switch (cVar->GetType()) {

View File

@ -91,7 +91,9 @@ namespace EGT::Menu {
return;
EGSDK::GamePH::PlayerVariables::customVars.ForEach([](std::unique_ptr<EGSDK::GamePH::PlayerVar>& customPlayerVarPtr) {
auto customPlayerVar = EGSDK::GamePH::PlayerVariables::GetVarRef(customPlayerVarPtr.get());
auto customPlayerVar = EGSDK::GamePH::PlayerVariables::GetVarRefFromPtr(customPlayerVarPtr.get());
if (!customPlayerVar)
return;
if (customPlayerVar->IsManagedByBool())
return;
@ -213,16 +215,20 @@ namespace EGT::Menu {
if (value.empty())
continue;
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRef(name.c_str());
if (playerVar)
playerVar->SetValue(value);
ImGui_impl::DeferredActions::Add([name, value]() {
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRef(name.c_str());
if (playerVar && playerVar->GetType() != EGSDK::Engine::VarType::String)
playerVar->SetValueFromList(value);
});
}
file.close();
ImGui::OpenPopup("Loaded player variables!");
}
static void RestoreVariableToDefault(const std::unique_ptr<EGSDK::GamePH::PlayerVar>& playerVarPtr) {
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRef(playerVarPtr.get());
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRef(playerVarPtr->GetName());
if (!playerVar)
return;
ImGui_impl::DeferredActions::Add([playerVar]() mutable {
switch (playerVar->GetType()) {
@ -243,11 +249,12 @@ namespace EGT::Menu {
(restoreVarsToSavedVarsEnabled ? EGSDK::GamePH::PlayerVariables::vars : EGSDK::GamePH::PlayerVariables::customVars).ForEach([](std::unique_ptr<EGSDK::GamePH::PlayerVar>& playerVarPtr) {
RestoreVariableToDefault(playerVarPtr);
});
ImGui::OpenPopup("Restored player variables!");
}
static void SaveVariableAsDefault(const std::unique_ptr<EGSDK::GamePH::PlayerVar>& playerVarPtr) {
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRef(playerVarPtr.get());
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRefFromPtr(playerVarPtr.get());
if (!playerVar)
return;
switch (playerVar->GetType()) {
case EGSDK::Engine::VarType::String:
break; // TO IMPLEMENT
@ -299,7 +306,9 @@ namespace EGT::Menu {
ImGui::PopStyleColor();
}
static void RenderPlayerVariable(const std::unique_ptr<EGSDK::GamePH::PlayerVar>& playerVarPtr) {
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRef(playerVarPtr.get());
auto playerVar = EGSDK::GamePH::PlayerVariables::GetVarRefFromPtr(playerVarPtr.get());
if (!playerVar)
return;
ImGui::BeginDisabled(playerVar->IsManagedByBool());
switch (playerVar->GetType()) {
@ -352,8 +361,10 @@ namespace EGT::Menu {
if (ImGui::Button("Save variables to file", "Saves current player variables to chosen file inside the file dialog"))
ImGuiFileDialog::Instance()->OpenDialog("ChooseSCRPath", "Choose Folder", nullptr, { saveSCRPath.empty() ? "." : saveSCRPath });
ImGui::SameLine();
if (ImGui::Button("Load variables from file", "Loads player variables from chosen file inside the file dialog"))
ImGuiFileDialog::Instance()->OpenDialog("ChooseSCRLoadPath", "Choose File", ".scr", { loadSCRFilePath.empty() ? "." : loadSCRFilePath });
if (ImGui::Button("Load variables from file", "Loads player variables from chosen file inside the file dialog")) {
std::filesystem::path _loadSCRFilePath = loadSCRFilePath;
ImGuiFileDialog::Instance()->OpenDialog("ChooseSCRLoadPath", "Choose File", ".scr", { !_loadSCRFilePath.empty() && std::filesystem::is_directory(_loadSCRFilePath.parent_path()) ? _loadSCRFilePath.parent_path().string() : "." });
}
ImGui::Checkbox("Restore variables to saved variables", &restoreVarsToSavedVarsEnabled, "Sets whether or not \"Restore variables to default\" should restore variables to the ones saved by \"Save current variables as default\"");
ImGui::Checkbox("Debug Mode", &debugEnabled, "Shows text boxes alongside player variables, which will show the address in memory of each variable");
@ -399,7 +410,7 @@ namespace EGT::Menu {
ImGui::DisplaySimplePopupMessage("Failed saving player variables.", "There was an error opening a handle to the file \"%s\\player_variables.scr\"! The file is most likely already open inanother program. Please close it!", saveSCRPath.c_str());
ImGui::DisplaySimplePopupMessage("Saved player variables!", "Player variables have been saved to \"%s\\player_variables.scr\"!", saveSCRPath.c_str());
ImGui::DisplaySimplePopupMessage("Failed loading player variables.", "There was an error opening the file \"%s\"! The file is most likely already open in another program. Please closeit!", loadSCRFilePath.c_str());
ImGui::DisplaySimplePopupMessage("Failed loading player variables.", "There was an error opening the file \"%s\"! The file is most likely already open in another program. Please close it!", loadSCRFilePath.c_str());
ImGui::DisplaySimplePopupMessage("Loaded player variables!", "Player variables have been loaded from \"%s\"!", loadSCRFilePath.c_str());
ImGui::DisplaySimplePopupMessage("Restored player variables!", "All player variables have been restored to default values!");