mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-07 14:00:17 +02:00
990 lines
35 KiB
C++
990 lines
35 KiB
C++
#include "Common/Config.h"
|
|
#include "Common/FileFormatCheck.h"
|
|
#include "FEXCore/Config/Config.h"
|
|
#include "Tools/CommonGUI/IMGui.h"
|
|
|
|
#include <FEXCore/Utils/Event.h>
|
|
#include <FEXCore/fextl/string.h>
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <sys/inotify.h>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
static std::chrono::time_point<std::chrono::high_resolution_clock> GlobalTime{};
|
|
|
|
static bool ConfigOpen{};
|
|
static bool ConfigChanged{};
|
|
static int EnvironmentVariableSelected{};
|
|
static int HostEnvironmentVariableSelected{};
|
|
static int NamedRootFSSelected{-1};
|
|
|
|
static fextl::string ConfigFilename{};
|
|
static std::unique_ptr<FEXCore::Config::Layer> LoadedConfig{};
|
|
|
|
static const char EnvironmentPopupName[] = "#New Environment Variable";
|
|
static const char HostEnvironmentPopupName[] = "#New Host Environment Variable";
|
|
static const char SavedPopupAppName[] = "#SavedApp";
|
|
static const char OpenedPopupAppName[] = "#OpenedApp";
|
|
|
|
static bool OpenMsgPopup{};
|
|
static bool SaveMsgIsOpen{};
|
|
static std::string MsgMessage{};
|
|
static const char MsgPopupName[] = "#Msg";
|
|
static std::chrono::high_resolution_clock::time_point MsgTimerStart{};
|
|
|
|
static bool SelectedOpenFile{};
|
|
static bool SelectedSaveFileAs{};
|
|
static ImGuiFs::Dialog DialogSaveAs{};
|
|
static ImGuiFs::Dialog DialogOpen{};
|
|
|
|
// Named rootfs
|
|
static std::vector<std::string> NamedRootFS{};
|
|
static std::mutex NamedRootFSUpdator{};
|
|
|
|
static std::atomic<int> INotifyFD{-1};
|
|
static int INotifyFolderFD{};
|
|
static std::thread INotifyThreadHandle{};
|
|
static std::atomic_bool INotifyShutdown{};
|
|
|
|
void OpenMsgMessagePopup(fextl::string Message) {
|
|
OpenMsgPopup = true;
|
|
MsgMessage = Message;
|
|
MsgTimerStart = std::chrono::high_resolution_clock::now();
|
|
FEX::GUI::HadUpdate();
|
|
}
|
|
|
|
void LoadDefaultSettings() {
|
|
ConfigOpen = true;
|
|
ConfigFilename = {};
|
|
LoadedConfig = std::make_unique<FEX::Config::EmptyMapper>();
|
|
#define OPT_BASE(type, group, enum, json, default) \
|
|
LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_##enum, std::to_string(default));
|
|
#define OPT_STR(group, enum, json, default) \
|
|
LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_##enum, default);
|
|
#define OPT_STRARRAY(group, enum, json, default) // Do nothing
|
|
#include <FEXCore/Config/ConfigValues.inl>
|
|
|
|
// Erase unnamed options which shouldn't be set
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS_INTERPRETER);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_INTERPRETER_INSTALLED);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_FILENAME);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_CONFIG_NAME);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS64BIT_MODE);
|
|
}
|
|
|
|
bool OpenFile(fextl::string Filename, bool LoadDefault = false) {
|
|
std::error_code ec{};
|
|
if (!std::filesystem::exists(Filename, ec)) {
|
|
if (LoadDefault) {
|
|
LoadDefaultSettings();
|
|
ConfigFilename = Filename;
|
|
OpenMsgMessagePopup("Opened with default options: " + Filename);
|
|
return true;
|
|
}
|
|
OpenMsgMessagePopup("Couldn't open: " + Filename);
|
|
return false;
|
|
}
|
|
ConfigOpen = true;
|
|
ConfigFilename = Filename;
|
|
LoadedConfig = FEXCore::Config::CreateMainLayer(&Filename);
|
|
LoadedConfig->Load();
|
|
|
|
// Load default options and only overwrite only if the option didn't exist
|
|
#define OPT_BASE(type, group, enum, json, default) \
|
|
if (!LoadedConfig->OptionExists(FEXCore::Config::ConfigOption::CONFIG_##enum)) { LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_##enum, std::to_string(default)); }
|
|
#define OPT_STR(group, enum, json, default) \
|
|
if (!LoadedConfig->OptionExists(FEXCore::Config::ConfigOption::CONFIG_##enum)) { LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_##enum, default); }
|
|
#define OPT_STRARRAY(group, enum, json, default) // Do nothing
|
|
#include <FEXCore/Config/ConfigValues.inl>
|
|
|
|
// Erase unnamed options which shouldn't be set
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS_INTERPRETER);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_INTERPRETER_INSTALLED);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_FILENAME);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_APP_CONFIG_NAME);
|
|
LoadedConfig->Erase(FEXCore::Config::ConfigOption::CONFIG_IS64BIT_MODE);
|
|
|
|
return true;
|
|
}
|
|
|
|
void LoadNamedRootFSFolder() {
|
|
std::scoped_lock<std::mutex> lk{NamedRootFSUpdator};
|
|
NamedRootFS.clear();
|
|
fextl::string RootFS = FEXCore::Config::GetDataDirectory() + "RootFS/";
|
|
std::error_code ec{};
|
|
if (!std::filesystem::exists(RootFS, ec)) {
|
|
// Doesn't exist, create the the folder as a user convenience
|
|
if (!std::filesystem::create_directories(RootFS, ec)) {
|
|
// Well I guess we failed
|
|
return;
|
|
}
|
|
}
|
|
for (auto &it : std::filesystem::directory_iterator(RootFS)) {
|
|
if (it.is_directory()) {
|
|
NamedRootFS.emplace_back(it.path().filename());
|
|
}
|
|
else if (it.is_regular_file()) {
|
|
// If it is a regular file then we need to check if it is a valid archive
|
|
if (it.path().extension() == ".sqsh" &&
|
|
FEX::FormatCheck::IsSquashFS(it.path().string().c_str())) {
|
|
NamedRootFS.emplace_back(it.path().filename());
|
|
}
|
|
else if (it.path().extension() == ".ero" &&
|
|
FEX::FormatCheck::IsEroFS(it.path().string().c_str())) {
|
|
NamedRootFS.emplace_back(it.path().filename());
|
|
}
|
|
}
|
|
}
|
|
std::sort(NamedRootFS.begin(), NamedRootFS.end());
|
|
}
|
|
|
|
void INotifyThread() {
|
|
while (!INotifyShutdown) {
|
|
constexpr size_t DATA_SIZE = (16 * (sizeof(struct inotify_event) + NAME_MAX + 1));
|
|
char buf[DATA_SIZE];
|
|
int Ret{};
|
|
do {
|
|
fd_set Set{};
|
|
FD_ZERO(&Set);
|
|
FD_SET(INotifyFD, &Set);
|
|
struct timeval tv{};
|
|
// 50 ms
|
|
tv.tv_usec = 50000;
|
|
Ret = select(INotifyFD + 1, &Set, nullptr, nullptr, &tv);
|
|
} while (Ret == 0 && INotifyFD != -1);
|
|
|
|
if (Ret == -1 || INotifyFD == -1) {
|
|
// Just return on error
|
|
INotifyShutdown = true;
|
|
return;
|
|
}
|
|
|
|
// Spin through the events, we don't actually care what they are
|
|
while (read(INotifyFD, buf, DATA_SIZE) > 0);
|
|
|
|
// Now update the named vector
|
|
LoadNamedRootFSFolder();
|
|
|
|
FEX::GUI::HadUpdate();
|
|
}
|
|
}
|
|
|
|
void SetupINotify() {
|
|
if (INotifyFD != -1) {
|
|
// Already setup
|
|
return;
|
|
}
|
|
|
|
INotifyFD = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
|
INotifyShutdown = false;
|
|
|
|
fextl::string RootFS = FEXCore::Config::GetDataDirectory() + "RootFS/";
|
|
INotifyFolderFD = inotify_add_watch(INotifyFD, RootFS.c_str(), IN_CREATE | IN_DELETE);
|
|
if (INotifyFolderFD != -1) {
|
|
INotifyThreadHandle = std::thread(INotifyThread);
|
|
}
|
|
}
|
|
|
|
void ShutdownINotify() {
|
|
close(INotifyFD);
|
|
INotifyFD = -1;
|
|
if (INotifyThreadHandle.joinable()) {
|
|
INotifyThreadHandle.join();
|
|
}
|
|
}
|
|
|
|
void SaveFile(fextl::string Filename) {
|
|
if (SaveMsgIsOpen) {
|
|
// Don't try saving a file while the message is already open.
|
|
// Stops us from spam saving the file to the filesystem.
|
|
return;
|
|
}
|
|
|
|
if (!ConfigOpen) {
|
|
OpenMsgMessagePopup("Can't save file when config isn't open");
|
|
return;
|
|
}
|
|
|
|
FEX::Config::SaveLayerToJSON(Filename, LoadedConfig.get());
|
|
ConfigChanged = false;
|
|
ConfigFilename = Filename;
|
|
OpenMsgMessagePopup("Config Saved to: '" + Filename + "'");
|
|
SaveMsgIsOpen = true;
|
|
|
|
// Output in terminal as well
|
|
printf("Config Saved to: '%s'\n", ConfigFilename.c_str());
|
|
}
|
|
|
|
void CloseConfig() {
|
|
ConfigOpen = false;
|
|
ConfigFilename = {};
|
|
ConfigChanged = false;
|
|
LoadedConfig.reset();
|
|
}
|
|
|
|
void FillCPUConfig() {
|
|
char BlockSize[32]{};
|
|
char EmulatedCPUCores[32]{};
|
|
|
|
if (ImGui::BeginTabItem("CPU")) {
|
|
std::optional<fextl::string*> Value{};
|
|
#ifdef INTERPRETER_ENABLED
|
|
ImGui::Text("Core:");
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_CORE);
|
|
|
|
ImGui::SameLine();
|
|
if (ImGui::RadioButton("Int", Value.has_value() && **Value == "0")) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_CORE, "0");
|
|
ConfigChanged = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::RadioButton("JIT", Value.has_value() && **Value == "1")) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_CORE, "1");
|
|
ConfigChanged = true;
|
|
}
|
|
#endif
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_MAXINST);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(BlockSize, &(*Value)->at(0), 32);
|
|
}
|
|
if (ImGui::InputText("Block Size:", BlockSize, 32, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_MAXINST, BlockSize);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_MULTIBLOCK);
|
|
bool Multiblock = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Multiblock", &Multiblock)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_MULTIBLOCK, Multiblock ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THREADS);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(EmulatedCPUCores, &(*Value)->at(0), 32);
|
|
}
|
|
if (ImGui::InputText("Emulated CPU cores:", EmulatedCPUCores, 32, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THREADS, EmulatedCPUCores);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
|
|
template<FEXCore::Config::ConfigOption Option>
|
|
bool EnvironmentVariableFiller(void *data, int idx, const char** out_text) {
|
|
static char TmpString[256];
|
|
auto Value = LoadedConfig->All(Option);
|
|
if (Value.has_value()) {
|
|
auto List = (*Value);
|
|
auto it = List->begin();
|
|
|
|
// Since this is a list, we don't have a linear allocator that we can just jump to an element
|
|
// Just do a quick spin
|
|
for (int i = 0; i < idx; ++i)
|
|
++it;
|
|
|
|
snprintf(TmpString, 256, "%s", it->c_str());
|
|
*out_text = TmpString;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool NamedRootFSVariableFiller(void *data, int idx, const char** out_text) {
|
|
std::scoped_lock<std::mutex> lk{NamedRootFSUpdator};
|
|
static char TmpString[256];
|
|
if (idx >= 0 && idx < NamedRootFS.size()) {
|
|
// Since this is a list, we don't have a linear allocator that we can just jump to an element
|
|
// Just do a quick spin
|
|
snprintf(TmpString, 256, "%s", NamedRootFS.at(idx).c_str());
|
|
*out_text = TmpString;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
template<FEXCore::Config::ConfigOption Option>
|
|
void DeleteEnvironmentVariable(int idx) {
|
|
auto Value = LoadedConfig->All(Option);
|
|
auto List = (*Value);
|
|
auto it = List->begin();
|
|
|
|
// Since this is a list, we don't have a linear allocator that we can just jump to an element
|
|
// Just do a quick spin
|
|
for (int i = 0; i < idx; ++i)
|
|
++it;
|
|
|
|
List->erase(it);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
void AddNewEnvironmentVariable() {
|
|
char Environment[256]{};
|
|
char HostEnvironment[256]{};
|
|
|
|
if (ImGui::BeginPopup(EnvironmentPopupName)) {
|
|
if (ImGui::InputText("New Environment", Environment, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_ENV, Environment);
|
|
ImGui::CloseCurrentPopup();
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
ImGui::PushID(1);
|
|
if (ImGui::BeginPopup(HostEnvironmentPopupName)) {
|
|
if (ImGui::InputText("New Environment", HostEnvironment, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_HOSTENV, HostEnvironment);
|
|
ImGui::CloseCurrentPopup();
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
void FillEmulationConfig() {
|
|
char RootFS[256]{};
|
|
char ThunkHostPath[256]{};
|
|
char ThunkGuestPath[256]{};
|
|
char ThunkConfigPath[256]{};
|
|
|
|
int NumEnvironmentVariables{};
|
|
int NumHostEnvironmentVariables{};
|
|
int NumRootFSPaths = NamedRootFS.size();
|
|
|
|
if (ImGui::BeginTabItem("Emulation")) {
|
|
auto Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_ROOTFS);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(RootFS, &(*Value)->at(0), 256);
|
|
}
|
|
ImGui::Text("Available named RootFS folders: %d", NumRootFSPaths);
|
|
|
|
if (ImGui::ListBox("Named RootFS folders", &NamedRootFSSelected, NamedRootFSVariableFiller, nullptr, NumRootFSPaths)) {
|
|
strncpy(RootFS, NamedRootFS.at(NamedRootFSSelected).c_str(), 256);
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ROOTFS, RootFS);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
if (ImGui::InputText("RootFS:", RootFS, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
NamedRootFSSelected = -1;
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ROOTFS, RootFS);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THUNKHOSTLIBS);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(ThunkHostPath, &(*Value)->at(0), 256);
|
|
}
|
|
if (ImGui::InputText("Thunk Host library folder:", ThunkHostPath, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THUNKHOSTLIBS, ThunkHostPath);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THUNKGUESTLIBS);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(ThunkGuestPath, &(*Value)->at(0), 256);
|
|
}
|
|
if (ImGui::InputText("Thunk Guest library folder:", ThunkGuestPath, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THUNKGUESTLIBS, ThunkGuestPath);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_THUNKCONFIG);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(ThunkConfigPath, &(*Value)->at(0), 256);
|
|
}
|
|
if (ImGui::InputText("Thunk Config file:", ThunkConfigPath, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_THUNKCONFIG, ThunkConfigPath);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
auto ValueList = LoadedConfig->All(FEXCore::Config::ConfigOption::CONFIG_ENV);
|
|
auto ValueHostList = LoadedConfig->All(FEXCore::Config::ConfigOption::CONFIG_HOSTENV);
|
|
if (ValueList.has_value()) {
|
|
NumEnvironmentVariables = (*ValueList)->size();
|
|
}
|
|
|
|
if (ValueHostList.has_value()) {
|
|
NumHostEnvironmentVariables = (*ValueHostList)->size();
|
|
}
|
|
|
|
ImGui::Text("Number of environment variables: %d", NumEnvironmentVariables);
|
|
|
|
ImGui::ListBox("Environment variables", &EnvironmentVariableSelected, EnvironmentVariableFiller<FEXCore::Config::ConfigOption::CONFIG_ENV>, nullptr, NumEnvironmentVariables);
|
|
|
|
if (ImGui::SmallButton("+")) {
|
|
ImGui::OpenPopup(EnvironmentPopupName);
|
|
}
|
|
|
|
if (NumEnvironmentVariables) {
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("-")) {
|
|
DeleteEnvironmentVariable<FEXCore::Config::ConfigOption::CONFIG_ENV>(EnvironmentVariableSelected);
|
|
EnvironmentVariableSelected = std::max(0, EnvironmentVariableSelected - 1);
|
|
}
|
|
}
|
|
|
|
|
|
ImGui::PushID(1);
|
|
ImGui::Text("Number of Host environment variables: %d", NumHostEnvironmentVariables);
|
|
|
|
ImGui::ListBox("Host Env variables", &HostEnvironmentVariableSelected, EnvironmentVariableFiller<FEXCore::Config::ConfigOption::CONFIG_HOSTENV>, nullptr, NumHostEnvironmentVariables);
|
|
|
|
if (ImGui::SmallButton("+")) {
|
|
ImGui::OpenPopup(HostEnvironmentPopupName);
|
|
}
|
|
|
|
if (NumHostEnvironmentVariables) {
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("-")) {
|
|
DeleteEnvironmentVariable<FEXCore::Config::ConfigOption::CONFIG_HOSTENV>(HostEnvironmentVariableSelected);
|
|
HostEnvironmentVariableSelected = std::max(0, HostEnvironmentVariableSelected - 1);
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
|
|
// Only draws if popup is open
|
|
AddNewEnvironmentVariable();
|
|
|
|
ImGui::Text("Debugging:");
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_O0);
|
|
bool DisablePasses = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Disable Optimization Passes", &DisablePasses)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_O0, DisablePasses ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::Text("Ahead Of Time JIT Options:");
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_AOTIRGENERATE);
|
|
bool AOTGenerate = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Generate", &AOTGenerate)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_AOTIRGENERATE, AOTGenerate ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_AOTIRCAPTURE);
|
|
bool AOTCapture = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Capture", &AOTCapture)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_AOTIRCAPTURE, AOTCapture ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_AOTIRLOAD);
|
|
bool AOTLoad = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Load", &AOTLoad)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_AOTIRLOAD, AOTLoad ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_CACHEOBJECTCODECOMPILATION);
|
|
|
|
ImGui::Text("Cache JIT object code:");
|
|
int CacheJITObjectCode = 0;
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_CACHEOBJECTCODECOMPILATION);
|
|
if (Value.has_value()) {
|
|
if (**Value == "0") {
|
|
CacheJITObjectCode = FEXCore::Config::ConfigObjectCodeHandler::CONFIG_NONE;
|
|
} else if (**Value == "1") {
|
|
CacheJITObjectCode = FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READ;
|
|
} else if (**Value == "2") {
|
|
CacheJITObjectCode = FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READWRITE;
|
|
}
|
|
}
|
|
|
|
bool CacheChanged = false;
|
|
CacheChanged |= ImGui::RadioButton("Off", &CacheJITObjectCode, FEXCore::Config::ConfigObjectCodeHandler::CONFIG_NONE); ImGui::SameLine();
|
|
CacheChanged |= ImGui::RadioButton("Read-only", &CacheJITObjectCode, FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READ); ImGui::SameLine();
|
|
CacheChanged |= ImGui::RadioButton("Read/Write", &CacheJITObjectCode, FEXCore::Config::ConfigObjectCodeHandler::CONFIG_READWRITE);
|
|
|
|
if (CacheChanged) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_CACHEOBJECTCODECOMPILATION, std::to_string(CacheJITObjectCode));
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
|
|
void FillLoggingConfig() {
|
|
char LogFile[256]{};
|
|
char IRDump[256]{};
|
|
|
|
if (ImGui::BeginTabItem("Logging")) {
|
|
auto Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_SILENTLOG);
|
|
bool SilentLog = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Silent Logging", &SilentLog)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_SILENTLOG, SilentLog ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_OUTPUTLOG);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(LogFile, &(*Value)->at(0), 256);
|
|
}
|
|
if (ImGui::InputText("Output log file:", LogFile, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_OUTPUTLOG, LogFile);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_DUMPIR);
|
|
if (Value.has_value() && !(*Value)->empty()) {
|
|
strncpy(IRDump, &(*Value)->at(0), 256);
|
|
}
|
|
if (ImGui::InputText("IR Dump location:", IRDump, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_DUMPIR, IRDump);
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
|
|
void FillHackConfig() {
|
|
if (ImGui::BeginTabItem("Hacks")) {
|
|
auto Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_TSOENABLED);
|
|
bool TSOEnabled = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("TSO Enabled", &TSOEnabled)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOENABLED, TSOEnabled ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_PARANOIDTSO);
|
|
bool ParanoidTSOEnabled = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Paranoid TSO Enabled", &ParanoidTSOEnabled)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_PARANOIDTSO, ParanoidTSOEnabled ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_X87REDUCEDPRECISION);
|
|
bool X87ReducedPrecision = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("X87 Reduced Precision", &X87ReducedPrecision)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_X87REDUCEDPRECISION, X87ReducedPrecision ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::Text("SMC Checks: ");
|
|
int SMCChecks = FEXCore::Config::CONFIG_SMC_MMAN;
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_SMCCHECKS);
|
|
if (Value.has_value()) {
|
|
if (**Value == "0") {
|
|
SMCChecks = FEXCore::Config::CONFIG_SMC_NONE;
|
|
} else if (**Value == "1") {
|
|
SMCChecks = FEXCore::Config::CONFIG_SMC_MTRACK;
|
|
} else if (**Value == "2") {
|
|
SMCChecks = FEXCore::Config::CONFIG_SMC_FULL;
|
|
} else if (**Value == "3") {
|
|
SMCChecks = FEXCore::Config::CONFIG_SMC_MMAN;
|
|
}
|
|
}
|
|
|
|
bool SMCChanged = false;
|
|
SMCChanged |= ImGui::RadioButton("None", &SMCChecks, FEXCore::Config::CONFIG_SMC_NONE); ImGui::SameLine();
|
|
SMCChanged |= ImGui::RadioButton("MTrack (Default)", &SMCChecks, FEXCore::Config::CONFIG_SMC_MTRACK); ImGui::SameLine();
|
|
SMCChanged |= ImGui::RadioButton("Full", &SMCChecks, FEXCore::Config::CONFIG_SMC_FULL);
|
|
SMCChanged |= ImGui::RadioButton("MMan (Deprecated)", &SMCChecks, FEXCore::Config::CONFIG_SMC_MMAN); ImGui::SameLine();
|
|
|
|
if (SMCChanged) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_SMCCHECKS, std::to_string(SMCChecks));
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_ABILOCALFLAGS);
|
|
bool UnsafeLocalFlags = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Unsafe local flags optimization", &UnsafeLocalFlags)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ABILOCALFLAGS, UnsafeLocalFlags ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
Value = LoadedConfig->Get(FEXCore::Config::ConfigOption::CONFIG_ABINOPF);
|
|
bool NoPFCalculation = Value.has_value() && **Value == "1";
|
|
if (ImGui::Checkbox("Disable PF calculation", &NoPFCalculation)) {
|
|
LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_ABINOPF, NoPFCalculation ? "1" : "0");
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
|
|
static const std::map<FEXCore::Config::ConfigOption, std::string> ConfigToNameLookup = {{
|
|
#define OPT_BASE(type, group, enum, json, default) {FEXCore::Config::ConfigOption::CONFIG_##enum, #json},
|
|
#include <FEXCore/Config/ConfigValues.inl>
|
|
}};
|
|
|
|
struct TmpString {
|
|
char Str[256];
|
|
std::string Name;
|
|
};
|
|
static std::vector<std::vector<TmpString>> AdvancedOptions{};
|
|
|
|
void UpdateAdvancedOptionsVector() {
|
|
AdvancedOptions.clear();
|
|
// Push everything in to our vector table that we can modify instead of the map
|
|
auto Options = LoadedConfig->GetOptionMap();
|
|
AdvancedOptions.resize(Options.size());
|
|
|
|
size_t i = 0;
|
|
for (auto &Option : Options) {
|
|
auto ConfigName = ConfigToNameLookup.find(Option.first);
|
|
auto &AdvancedOption = AdvancedOptions.at(i);
|
|
AdvancedOption.resize(Option.second.size());
|
|
size_t j = 0;
|
|
for (auto &OptionList : Option.second) {
|
|
strcpy(AdvancedOption[j].Str, OptionList.c_str());
|
|
AdvancedOption[j].Name = ConfigName->second;
|
|
if (Option.second.size() > 1) {
|
|
AdvancedOption[j].Name += " " + std::to_string(j);
|
|
}
|
|
++j;
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void FillAdvancedConfig() {
|
|
if (ImGui::BeginTabItem("Advanced")) {
|
|
auto Options = LoadedConfig->GetOptionMap();
|
|
|
|
if (ImGui::SmallButton("Refresh") || AdvancedOptions.size() != Options.size()) {
|
|
UpdateAdvancedOptionsVector();
|
|
}
|
|
|
|
if (Options.size()) {
|
|
ImGui::Columns(2);
|
|
size_t i = 0;
|
|
for (auto &Option : Options) {
|
|
auto ConfigName = ConfigToNameLookup.find(Option.first);
|
|
ImGui::Text("%s", ConfigName->second.c_str());
|
|
ImGui::NextColumn();
|
|
|
|
auto &AdvancedOption = AdvancedOptions.at(i);
|
|
size_t j = 0;
|
|
|
|
bool Stop = false;
|
|
for (auto &OptionList : AdvancedOption) {
|
|
//ImGui::Text("%s", OptionList.Str);
|
|
if (ImGui::InputText(OptionList.Name.c_str(), OptionList.Str, sizeof(TmpString), ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
if (Option.second.size() == 1) {
|
|
LoadedConfig->EraseSet(Option.first, OptionList.Str);
|
|
}
|
|
else {
|
|
auto &All = Option.second;
|
|
auto Iter = All.begin();
|
|
std::advance(Iter, j);
|
|
*Iter = OptionList.Str;
|
|
|
|
LoadedConfig->Erase(Option.first);
|
|
for (auto &Value : All) {
|
|
LoadedConfig->Set(Option.first, Value);
|
|
}
|
|
|
|
}
|
|
ConfigChanged = true;
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
ImGui::PushID(OptionList.Name.c_str());
|
|
if (ImGui::SmallButton("-")) {
|
|
if (Option.second.size() == 1) {
|
|
LoadedConfig->Erase(Option.first);
|
|
}
|
|
else {
|
|
auto &All = Option.second;
|
|
auto Iter = All.begin();
|
|
std::advance(Iter, j);
|
|
All.erase(Iter);
|
|
|
|
LoadedConfig->Erase(Option.first);
|
|
for (auto &Value : All) {
|
|
LoadedConfig->Set(Option.first, Value);
|
|
}
|
|
}
|
|
Stop = true;
|
|
ConfigChanged = true;
|
|
UpdateAdvancedOptionsVector();
|
|
}
|
|
ImGui::PopID();
|
|
|
|
++j;
|
|
}
|
|
|
|
ImGui::NextColumn();
|
|
++i;
|
|
if (Stop) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
|
|
void FillConfigWindow() {
|
|
ImGui::BeginTabBar("Config");
|
|
FillCPUConfig();
|
|
FillEmulationConfig();
|
|
FillLoggingConfig();
|
|
FillHackConfig();
|
|
FillAdvancedConfig();
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
bool DrawUI() {
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
auto current_time = std::chrono::high_resolution_clock::now();
|
|
auto Diff = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - GlobalTime);
|
|
io.DeltaTime = Diff.count() > 0 ? Diff.count() : 1.0f/60.0f;
|
|
GlobalTime = current_time;
|
|
|
|
ImGui::NewFrame();
|
|
|
|
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
|
|
// because it would be confusing to have two docking targets within each others.
|
|
ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
|
|
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
ImGui::SetNextWindowPos(viewport->Pos);
|
|
ImGui::SetNextWindowSize(viewport->Size);
|
|
ImGui::SetNextWindowViewport(viewport->ID);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
|
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
|
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
|
ImGui::Begin("DockSpace", nullptr, window_flags);
|
|
ImGui::PopStyleVar(3);
|
|
|
|
struct {
|
|
bool Open{};
|
|
bool OpenDefault{};
|
|
bool OpenAppProfile{};
|
|
bool Save{};
|
|
bool SaveAs{};
|
|
bool SaveDefault{};
|
|
bool Close{};
|
|
bool Quit{};
|
|
} Selected;
|
|
|
|
char AppName[256]{};
|
|
|
|
if (ImGui::BeginMenuBar()) {
|
|
if (ImGui::BeginMenu("File")) {
|
|
ImGui::MenuItem("Open", "CTRL+O", &Selected.Open, true);
|
|
ImGui::MenuItem("Open from default location", "CTRL+SHIFT+O", &Selected.OpenDefault, true);
|
|
ImGui::MenuItem("Open App profile", "CTRL+I", &Selected.OpenAppProfile, true);
|
|
|
|
ImGui::MenuItem("Save", "CTRL+S", &Selected.Save, true);
|
|
ImGui::MenuItem("Save As", "CTRL+SHIFT+S", &Selected.SaveAs, true);
|
|
ImGui::MenuItem("Save As App profile", "CTRL+E", nullptr, true);
|
|
ImGui::MenuItem("Save Default", "CTRL+SHIFT+P", &Selected.SaveDefault, true);
|
|
|
|
ImGui::MenuItem("Close", "CTRL+W", &Selected.Close, true);
|
|
ImGui::MenuItem("Quit", "CTRL+Q", &Selected.Quit, true);
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetFrameHeight());
|
|
|
|
if (ConfigOpen) {
|
|
if (ConfigChanged) {
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.0, 1.0, 0.0, 1.0));
|
|
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0, 1.0, 0.0, 1.0));
|
|
}
|
|
else {
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0, 1.0, 0.0, 1.0));
|
|
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.0, 1.0, 0.0, 1.0));
|
|
}
|
|
}
|
|
else {
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.0, 0.0, 0.0, 1.0));
|
|
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0, 0.0, 0.0, 1.0));
|
|
}
|
|
|
|
ImGui::RadioButton("", true);
|
|
ImGui::PopStyleColor(2);
|
|
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
if (ConfigOpen) {
|
|
if (ImGui::BeginChild("#Config")) {
|
|
FillConfigWindow();
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(SDL_SCANCODE_E) && io.KeyCtrl && !io.KeyShift) {
|
|
ImGui::OpenPopup(SavedPopupAppName);
|
|
}
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + viewport->Size.x / 2, viewport->Pos.y + viewport->Size.y / 2), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
|
if (ImGui::BeginPopupModal(SavedPopupAppName)) {
|
|
ImGui::SetKeyboardFocusHere();
|
|
if (ImGui::InputText("App name", AppName, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
fextl::string AppNameString = AppName;
|
|
fextl::string Filename = FEXCore::Config::GetApplicationConfig(AppNameString, false);
|
|
SaveFile(Filename);
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(SDL_SCANCODE_ESCAPE)) {
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
// Need this frame delay loop since ImGui doesn't allow us to enable a popup near the end of the frame
|
|
if (OpenMsgPopup) {
|
|
ImGui::OpenPopup(MsgPopupName);
|
|
OpenMsgPopup = false;
|
|
}
|
|
|
|
if (Selected.OpenAppProfile ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_I) && io.KeyCtrl && !io.KeyShift)) {
|
|
ImGui::OpenPopup(OpenedPopupAppName);
|
|
}
|
|
|
|
// Center the saved popup in the center of the window
|
|
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + viewport->Size.x / 2, viewport->Pos.y + viewport->Size.y / 2), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
|
|
|
if (ImGui::BeginPopup(MsgPopupName)) {
|
|
ImGui::Text("%s", MsgMessage.c_str());
|
|
if ((std::chrono::high_resolution_clock::now() - MsgTimerStart) >= std::chrono::seconds(2)) {
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
else if (SaveMsgIsOpen) {
|
|
SaveMsgIsOpen = false;
|
|
}
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + viewport->Size.x / 2, viewport->Pos.y + viewport->Size.y / 2), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
|
if (ImGui::BeginPopupModal(OpenedPopupAppName)) {
|
|
|
|
ImGui::SetKeyboardFocusHere();
|
|
if (ImGui::InputText("App name", AppName, 256, ImGuiInputTextFlags_EnterReturnsTrue)) {
|
|
fextl::string AppNameString = AppName;
|
|
fextl::string Filename = FEXCore::Config::GetApplicationConfig(AppNameString, false);
|
|
OpenFile(Filename, false);
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(SDL_SCANCODE_ESCAPE)) {
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
if (Selected.Open ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_O) && io.KeyCtrl && !io.KeyShift)) {
|
|
SelectedOpenFile = true;
|
|
}
|
|
if (Selected.OpenDefault ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_O) && io.KeyCtrl && io.KeyShift)) {
|
|
if (OpenFile(FEXCore::Config::GetConfigFileLocation(), true)) {
|
|
LoadNamedRootFSFolder();
|
|
SetupINotify();
|
|
}
|
|
}
|
|
|
|
if (Selected.Save ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_S) && io.KeyCtrl && !io.KeyShift)) {
|
|
SaveFile(ConfigFilename);
|
|
}
|
|
if (Selected.SaveAs ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_S) && io.KeyCtrl && io.KeyShift)) {
|
|
SelectedSaveFileAs = true;
|
|
}
|
|
|
|
if (Selected.SaveDefault ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_P) && io.KeyCtrl && io.KeyShift)) {
|
|
SaveFile(FEXCore::Config::GetConfigFileLocation());
|
|
}
|
|
if (Selected.Close ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_W) && io.KeyCtrl && !io.KeyShift)) {
|
|
CloseConfig();
|
|
ShutdownINotify();
|
|
}
|
|
|
|
if (Selected.Quit ||
|
|
(ImGui::IsKeyPressed(SDL_SCANCODE_Q) && io.KeyCtrl && !io.KeyShift)) {
|
|
Selected.Quit = true;
|
|
}
|
|
|
|
ImGui::End(); // End dockspace
|
|
|
|
char const *InitialPath;
|
|
char const *File;
|
|
|
|
InitialPath = DialogOpen.chooseFileDialog(SelectedOpenFile, "./", ".json", "#Chose a config to load");
|
|
File = DialogOpen.getChosenPath();
|
|
if (strlen(InitialPath) > 0 && strlen(File) > 0) {
|
|
OpenFile(File);
|
|
}
|
|
|
|
InitialPath = DialogSaveAs.saveFileDialog(SelectedSaveFileAs, "./", "Config.json", ".json", "#Choose where to save a config");
|
|
File = DialogSaveAs.getChosenPath();
|
|
if (strlen(InitialPath) > 0 && strlen(File) > 0) {
|
|
SaveFile(File);
|
|
}
|
|
|
|
SelectedOpenFile = false;
|
|
SelectedSaveFileAs = false;
|
|
|
|
ImGui::Render();
|
|
|
|
// Return true to keep rendering
|
|
return !Selected.Quit;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
fextl::string ImGUIConfig = FEXCore::Config::GetConfigDirectory(false) + "FEXConfig_imgui.ini";
|
|
auto [window, gl_context] = FEX::GUI::SetupIMGui("#FEXConfig", ImGUIConfig);
|
|
|
|
GlobalTime = std::chrono::high_resolution_clock::now();
|
|
|
|
// Attempt to open the config passed in
|
|
if (argc > 1) {
|
|
if (OpenFile(argv[1], true)) {
|
|
LoadNamedRootFSFolder();
|
|
SetupINotify();
|
|
}
|
|
}
|
|
else {
|
|
if (OpenFile(FEXCore::Config::GetConfigFileLocation(), true)) {
|
|
LoadNamedRootFSFolder();
|
|
SetupINotify();
|
|
}
|
|
}
|
|
|
|
FEX::GUI::DrawUI(window, DrawUI);
|
|
|
|
ShutdownINotify();
|
|
|
|
// Cleanup
|
|
FEX::GUI::Shutdown(window, gl_context);
|
|
return 0;
|
|
}
|