From d91258bf08da4485fe8e31ad0e4ed6028c6b0424 Mon Sep 17 00:00:00 2001 From: CocoSimone Date: Sun, 18 Dec 2022 02:38:32 +0100 Subject: [PATCH] Game list --- src/frontend/Settings.cpp | 158 -------------------------- src/frontend/imgui/GameList.cpp | 154 +++++++++++++++++++++++++ src/frontend/imgui/GameList.hpp | 18 +++ src/frontend/imgui/Settings.cpp | 124 ++++++++++++++++++++ src/frontend/{ => imgui}/Settings.hpp | 8 +- src/frontend/imgui/Window.cpp | 14 ++- src/frontend/imgui/Window.hpp | 2 + src/frontend/imgui/utilities.hpp | 21 ++++ src/util.hpp | 14 +++ 9 files changed, 348 insertions(+), 165 deletions(-) delete mode 100644 src/frontend/Settings.cpp create mode 100644 src/frontend/imgui/GameList.cpp create mode 100644 src/frontend/imgui/GameList.hpp create mode 100644 src/frontend/imgui/Settings.cpp rename src/frontend/{ => imgui}/Settings.hpp (67%) create mode 100644 src/frontend/imgui/utilities.hpp diff --git a/src/frontend/Settings.cpp b/src/frontend/Settings.cpp deleted file mode 100644 index 0261745c..00000000 --- a/src/frontend/Settings.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include -#include -#include -#include - -using namespace std::filesystem; - -Settings::Settings(n64::Core& core) { - auto modes = std::fstream::in | std::fstream::out; - auto fileExists = exists("resources/settings.json"); - if(!fileExists) { - modes |= std::fstream::trunc; - } - std::fstream settingsFile{"resources/settings.json", modes}; - if(fileExists) { - settings = json::parse(settingsFile); - auto entryCpuType = settings["cpu"]["type"]; - if(!entryCpuType.empty()) { - cpuType = entryCpuType.get(); - if(cpuType == "dynarec") { - core.cpuType = n64::CpuType::Dynarec; - } else if(cpuType == "interpreter") { - core.cpuType = n64::CpuType::Interpreter; - } else { - util::panic("Unrecognized cpu type: {}\n", cpuType); - } - } else { - settingsFile.clear(); - settings["cpu"]["type"] = "interpreter"; - settingsFile << settings; - core.cpuType = n64::CpuType::Interpreter; - } - - auto volumeREntry = settings["audio"]["volumeR"]; - if(!volumeREntry.empty()) { - auto value = volumeREntry.get(); - volumeR = value; - } else { - settingsFile.clear(); - settings["audio"]["volumeR"] = 0.5; - settingsFile << settings; - volumeR = 0.5; - } - auto volumeLEntry = settings["audio"]["volumeL"]; - if(!volumeLEntry.empty()) { - auto value = volumeLEntry.get(); - volumeL = value; - } else { - settingsFile.clear(); - settings["audio"]["volumeL"] = 0.5; - settingsFile << settings; - volumeL = 0.5; - } - auto lockChannelsEntry = settings["audio"]["lockChannels"]; - if(!lockChannelsEntry.empty()) { - auto value = lockChannelsEntry.get(); - lockChannels = value; - } else { - settingsFile.clear(); - settings["audio"]["lockChannels"] = true; - settingsFile << settings; - lockChannels = true; - } - } else { - settings["cpu"]["type"] = "interpreter"; - settings["audio"]["volumeR"] = 0.5; - settings["audio"]["volumeL"] = 0.5; - settings["audio"]["lockChannels"] = true; - - core.cpuType = n64::CpuType::Interpreter; - volumeR = 0.5; - volumeL = 0.5; - lockChannels = true; - - settingsFile << settings; - } - - settingsFile.close(); -} - -Settings::~Settings() { - auto modes = std::fstream::out; - auto fileExists = exists("resources/settings.json"); - if(fileExists) { - modes |= std::fstream::trunc; - - std::fstream settingsFile{"resources/settings.json", modes}; - - settings["cpu"]["type"] = cpuType; - settings["audio"]["volumeR"] = volumeR; - settings["audio"]["volumeL"] = volumeL; - settings["audio"]["lockChannels"] = lockChannels; - settingsFile << settings; - settingsFile.close(); - } -} - -void Settings::RenderWidget(bool& show) { - if(show) { - ImGui::OpenPopup("Settings"); - if(ImGui::BeginPopupModal("Settings", &show)) { - static enum { CPU, Audio } category = CPU; - if(ImGui::Button("CPU")) { - category = CPU; - } - ImGui::SameLine(); - if(ImGui::Button("Audio")) { - category = Audio; - } - ImGui::Separator(); - if(category == Audio) { - ImGui::Checkbox("Lock channels", &lockChannels); - ImGui::SliderFloat("Volume L", &volumeL, 0, 1, "%.2f", ImGuiSliderFlags_NoInput); - if (!lockChannels) { - ImGui::SliderFloat("Volume R", &volumeR, 0, 1, "%.2f", ImGuiSliderFlags_NoInput); - } else { - volumeR = volumeL; - ImGui::BeginDisabled(); - ImGui::SliderFloat("Volume R", &volumeR, 0, 1, "%.2f", ImGuiSliderFlags_NoInput); - ImGui::EndDisabled(); - } - } else if(category == CPU) { - const char* items[] = { "JIT", "Interpreter" }; - static int currentIndex = [this]() { - if(cpuType == "dynarec") return 0; - else if(cpuType == "interpreter") return 1; - return 0; - }(); - - if(ImGui::BeginCombo("CPU type", items[currentIndex])) { - for (int n = 0; n < 2; n++) { - const bool is_selected = (currentIndex == n); - if (ImGui::Selectable(items[n], is_selected)) { - currentIndex = n; - } - - if (is_selected) { - ImGui::SetItemDefaultFocus(); - } - } - - if(currentIndex == 0) { - cpuType = "dynarec"; - } - if(currentIndex == 1) { - cpuType = "interpreter"; - } - ImGui::EndCombo(); - } - } - ImGui::EndPopup(); - } - - SetLockChannels(lockChannels); - SetVolumeL(volumeL); - SetVolumeR(volumeR); - } -} \ No newline at end of file diff --git a/src/frontend/imgui/GameList.cpp b/src/frontend/imgui/GameList.cpp new file mode 100644 index 00000000..d76828a5 --- /dev/null +++ b/src/frontend/imgui/GameList.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace nlohmann; +using namespace std::filesystem; + +inline std::string CountryCodeToStr(u8 code) { + switch(code) { + case 0x37: return "Beta"; + case 0x41: return "Asian (NTSC)"; + case 0x42: return "Brazilian"; + case 0x43: return "Chinese"; + case 0x44: return "German"; + case 0x45: return "North America"; + case 0x46: return "French"; + case 0x47: return "Gateway 64 (NTSC)"; + case 0x48: return "Dutch"; + case 0x49: return "Italian"; + case 0x4A: return "Japanese"; + case 0x4B: return "Korean"; + case 0x4C: return "Gateway 64 (PAL)"; + case 0x4E: return "Canadian"; + case 0x50: return "European (basic spec.)"; + case 0x53: return "Spanish"; + case 0x55: return "Australian"; + case 0x57: return "Scandinavian"; + case 0x58: case 0x59: return "European"; + default: return "Unrecognized"; + } +} + +GameList::GameList(const std::string& path) { + if(!path.empty()) { + std::for_each(std::execution::par_unseq, begin(recursive_directory_iterator{path}), end(recursive_directory_iterator{path}), [&](const auto& p) { + if(p.path().extension() == ".n64" || p.path().extension() == ".z64" || p.path().extension() == ".v64" || + p.path().extension() == ".N64" || p.path().extension() == ".Z64" || p.path().extension() == ".V64") { + std::ifstream file(p.path().string(), std::ios::binary); + file.unsetf(std::ios::skipws); + + if(!file.is_open()) { + util::panic("Unable to open {}!", path); + } + + file.seekg(0, std::ios::end); + auto size = file.tellg(); + auto sizeAdjusted = util::NextPow2(size); + file.seekg(0, std::ios::beg); + + std::vector cart{}; + + std::fill(cart.begin(), cart.end(), 0); + cart.resize(sizeAdjusted); + cart.insert(cart.begin(), std::istream_iterator(file), std::istream_iterator()); + + file.close(); + + u32 crc, dummy; + util::SwapN64Rom(sizeAdjusted, cart.data(), crc, dummy); + + u8 countryCode = 0; + countryCode = cart[0x3D]; + + std::ifstream gameDbFile("resources/game_db.json"); + json gameDb = json::parse(gameDbFile); + auto entry = gameDb[fmt::format("{:08x}", crc)]["name"]; + + if(!entry.empty()) { + gamesList.push_back({entry.get(), CountryCodeToStr(countryCode), fmt::format("{:.2f} MiB", float(size) / 1024 / 1024), p.path().string()}); + } else { + gamesList.push_back({p.path().stem().string(), CountryCodeToStr(countryCode), fmt::format("{:.2f} MiB", float(size) / 1024 / 1024), p.path().string()}); + } + } + }); + } +} + +bool GameList::RenderWidget(bool showMainMenuBar, float mainMenuBarHeight, std::string& rom) { + const auto windowSize = ImGui::GetIO().DisplaySize; + if (showMainMenuBar) { + ImGui::SetNextWindowPos(ImVec2(0, mainMenuBarHeight)); + ImGui::SetNextWindowSize(ImVec2(windowSize.x, windowSize.y - mainMenuBarHeight)); + } else { + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(windowSize); + } + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f); + + ImGui::Begin( + "Games list", + nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoBringToFrontOnFocus + ); + + static ImGuiTableFlags flags = + ImGuiTableFlags_Resizable + | ImGuiTableFlags_RowBg + | ImGuiTableFlags_BordersOuterV + | ImGuiTableFlags_SizingStretchProp; + + bool toOpen = false; + if (ImGui::BeginTable("Games List", 3, flags)) { + ImGui::TableSetupColumn("Title"); + ImGui::TableSetupColumn("Region"); + ImGui::TableSetupColumn("Size"); + ImGui::TableHeadersRow(); + + for (int row = 0; row < gamesList.size(); row++) { + GameInfo entry = gamesList[row]; + + ImGui::TableNextRow(ImGuiTableRowFlags_None); + ImGui::PushID(row); + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.0f, 0.5f)); + ImGui::TableSetColumnIndex(0); + + if (ImGui::Selectable(entry.name.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0.0f, 20.f))) { + toOpen = true; + rom = entry.path; + } + + ImGui::TableSetColumnIndex(1); + + if (ImGui::Selectable(entry.region.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0.0f, 20.f))) { + toOpen = true; + rom = entry.path; + } + + ImGui::TableSetColumnIndex(2); + + if (ImGui::Selectable(entry.size.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0.0f, 20.f))) { + toOpen = true; + rom = entry.path; + } + + ImGui::PopStyleVar(); + ImGui::PopID(); + } + + ImGui::EndTable(); + } + + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + + return toOpen; +} \ No newline at end of file diff --git a/src/frontend/imgui/GameList.hpp b/src/frontend/imgui/GameList.hpp new file mode 100644 index 00000000..bf022a15 --- /dev/null +++ b/src/frontend/imgui/GameList.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +struct GameInfo { + std::string name, region, size, path; +}; + +struct GameList { + GameList(const std::string&); + ~GameList() = default; + + bool RenderWidget(bool, float, std::string&); + + std::vector GetGamesList() const { return gamesList; } +private: + std::vector gamesList{}; +}; diff --git a/src/frontend/imgui/Settings.cpp b/src/frontend/imgui/Settings.cpp new file mode 100644 index 00000000..5c309bca --- /dev/null +++ b/src/frontend/imgui/Settings.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include + +using namespace std::filesystem; + +Settings::Settings(n64::Core& core) { + auto modes = std::fstream::in | std::fstream::out; + auto fileExists = exists("resources/settings.json"); + if(!fileExists) { + modes |= std::fstream::trunc; + } + std::fstream settingsFile{"resources/settings.json", modes}; + if(fileExists) { + settings = json::parse(settingsFile); + auto entryCpuType = settings["cpu"]["type"]; + if(!entryCpuType.empty()) { + cpuType = entryCpuType.get(); + if(cpuType == "dynarec") { + core.cpuType = n64::CpuType::Dynarec; + } else if(cpuType == "interpreter") { + core.cpuType = n64::CpuType::Interpreter; + } else { + util::panic("Unrecognized cpu type: {}\n", cpuType); + } + } else { + settingsFile.clear(); + settings["cpu"]["type"] = "interpreter"; + settingsFile << settings; + core.cpuType = n64::CpuType::Interpreter; + } + + checkjsonentry(volumeR, float, "audio", "volumeR", 0.5); + checkjsonentry(volumeL, float, "audio", "volumeL", 0.5); + checkjsonentry(lockChannels, bool, "audio", "lockChannels", true); + checkjsonentry(gamesDir, std::string, "general", "gamesDir", ""); + } else { + settings["general"]["gamesDir"] = ""; + settings["cpu"]["type"] = "interpreter"; + settings["audio"]["volumeR"] = 0.5; + settings["audio"]["volumeL"] = 0.5; + settings["audio"]["lockChannels"] = true; + + core.cpuType = n64::CpuType::Interpreter; + volumeR = 0.5; + volumeL = 0.5; + lockChannels = true; + gamesDir = ""; + + settingsFile << settings; + } + + settingsFile.close(); +} + +Settings::~Settings() { + auto modes = std::fstream::out; + auto fileExists = exists("resources/settings.json"); + if(fileExists) { + modes |= std::fstream::trunc; + + std::fstream settingsFile{"resources/settings.json", modes}; + + settings["general"]["gamesDir"] = gamesDir; + settings["cpu"]["type"] = cpuType; + settings["audio"]["volumeR"] = volumeR; + settings["audio"]["volumeL"] = volumeL; + settings["audio"]["lockChannels"] = lockChannels; + settingsFile << settings; + settingsFile.close(); + } +} + +void Settings::RenderWidget(bool& show) { + if(show) { + ImGui::OpenPopup("Settings"); + if(ImGui::BeginPopupModal("Settings", &show)) { + const char *categories[] = {"General", "CPU", "Audio"}; + enum Category { General, CPU, Audio }; + static int category = General; + CreateComboList("", &category, categories, 3); + ImGui::Separator(); + switch (category) { + case General: + ImGui::Text("Games directory: %s", gamesDir.c_str()); + ImGui::SameLine(); + if(ImGui::Button("Select...")) { + nfdchar_t *outpath; + nfdresult_t result = NFD_PickFolderN(&outpath, nullptr); + if (result == NFD_OKAY) { + gamesDir = outpath; + NFD_FreePath(outpath); + } + } + break; + case CPU: { + const char *cpuTypes[] = {"JIT", "Interpreter"}; + static int currentType = 0; + if (cpuType == "interpreter") currentType = 1; + + if (CreateComboList("Core type", ¤tType, cpuTypes, 2)) { + if(currentType == 0) cpuType = "dynarec"; + if(currentType == 1) cpuType = "interpreter"; + } + } break; + case Audio: + ImGui::Checkbox("Lock channels", &lockChannels); + ImGui::SliderFloat("Volume L", &volumeL, 0, 1, "%.2f", ImGuiSliderFlags_NoInput); + if (!lockChannels) { + ImGui::SliderFloat("Volume R", &volumeR, 0, 1, "%.2f", ImGuiSliderFlags_NoInput); + } else { + volumeR = volumeL; + ImGui::BeginDisabled(); + ImGui::SliderFloat("Volume R", &volumeR, 0, 1, "%.2f", ImGuiSliderFlags_NoInput); + ImGui::EndDisabled(); + } + break; + } + ImGui::EndPopup(); + } + } +} \ No newline at end of file diff --git a/src/frontend/Settings.hpp b/src/frontend/imgui/Settings.hpp similarity index 67% rename from src/frontend/Settings.hpp rename to src/frontend/imgui/Settings.hpp index 367023b6..1139dd12 100644 --- a/src/frontend/Settings.hpp +++ b/src/frontend/imgui/Settings.hpp @@ -12,15 +12,11 @@ struct Settings { float GetVolumeR() const { return volumeR; }; bool GetLockChannels() const { return lockChannels; } std::string GetCpuType() const { return cpuType; } - - void SetVolumeL(float v) { volumeL = v; }; - void SetVolumeR(float v) { volumeR = v; }; - void SetLockChannels(bool v) { lockChannels = v; } - void SetCpuType(std::string v) { cpuType = v; } + std::string GetGamesDir() const { return gamesDir; } void RenderWidget(bool& show); private: - std::string cpuType = "interpreter"; + std::string cpuType = "interpreter", gamesDir = ""; float volumeL = 0.0, volumeR = 0.0; bool lockChannels = true; json settings; diff --git a/src/frontend/imgui/Window.cpp b/src/frontend/imgui/Window.cpp index 2a498f49..c12ecfeb 100644 --- a/src/frontend/imgui/Window.cpp +++ b/src/frontend/imgui/Window.cpp @@ -7,7 +7,7 @@ VkInstance instance{}; -Window::Window(n64::Core& core) : settings(core) { +Window::Window(n64::Core& core) : settings(core), gameList(settings.GetGamesDir()) { InitSDL(); InitParallelRDP(core.mem.GetRDRAM(), window); InitImgui(); @@ -167,6 +167,7 @@ void Window::LoadROM(n64::Core& core, const std::string &path) { gameName = std::filesystem::path(path).stem().string(); } + util::UpdateRPC(util::Playing, gameName); windowTitle = "Gadolinium - " + gameName; shadowWindowTitle = windowTitle; @@ -187,10 +188,13 @@ void Window::Render(n64::Core& core) { windowTitle = shadowWindowTitle; } + static bool renderGameList = true; static bool showSettings = false; bool showMainMenuBar = windowID == SDL_GetWindowID(SDL_GetMouseFocus()); + static float mainMenuBarHeight = 0; if(showMainMenuBar) { ImGui::BeginMainMenuBar(); + mainMenuBarHeight = ImGui::GetWindowSize().y; if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Open", "O")) { nfdchar_t *outpath; @@ -200,6 +204,7 @@ void Window::Render(n64::Core& core) { LoadROM(core, outpath); util::UpdateRPC(util::Playing, gameName); NFD_FreePath(outpath); + renderGameList = false; } } if (ImGui::MenuItem("Dump RDRAM")) { @@ -219,8 +224,10 @@ void Window::Render(n64::Core& core) { if (ImGui::BeginMenu("Emulation")) { if (ImGui::MenuItem("Reset")) { LoadROM(core, core.rom); + renderGameList = false; } if (ImGui::MenuItem("Stop")) { + renderGameList = true; windowTitle = "Gadolinium"; core.rom.clear(); util::UpdateRPC(util::Idling); @@ -247,6 +254,11 @@ void Window::Render(n64::Core& core) { ImGui::EndMainMenuBar(); } + static std::string rom{}; + if(renderGameList && gameList.RenderWidget(showMainMenuBar, mainMenuBarHeight, rom)) { + LoadROM(core, rom); + renderGameList = false; + } settings.RenderWidget(showSettings); ImGui::PopFont(); diff --git a/src/frontend/imgui/Window.hpp b/src/frontend/imgui/Window.hpp index b9070620..2e669c20 100644 --- a/src/frontend/imgui/Window.hpp +++ b/src/frontend/imgui/Window.hpp @@ -7,6 +7,7 @@ #include #include #include +#include struct Window { explicit Window(n64::Core& core); @@ -17,6 +18,7 @@ struct Window { ImFont *uiFont{}, *codeFont{}; u32 windowID{}; Settings settings; + GameList gameList; void LoadROM(n64::Core& core, const std::string& path); private: SDL_Window* window{}; diff --git a/src/frontend/imgui/utilities.hpp b/src/frontend/imgui/utilities.hpp new file mode 100644 index 00000000..022aa624 --- /dev/null +++ b/src/frontend/imgui/utilities.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +inline bool CreateComboList(const char* label, int* index, const char** items, int items_count) { + if (ImGui::BeginCombo(label, items[*index])) { + for (int n = 0; n < items_count; n++) { + const bool is_selected = (*index == n); + if (ImGui::Selectable(items[n], is_selected)) { + *index = n; + } + + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/util.hpp b/src/util.hpp index 9ba1018e..c3fe1766 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -302,4 +302,18 @@ inline void UpdateRPC(State state, const std::string& game = "") { inline void ClearRPC() { Discord_ClearPresence(); } + +#define checkjsonentry(name, type, param1, param2, defaultVal) \ + do { \ + auto name##Entry = settings[param1][param2]; \ + if(!name##Entry.empty()) { \ + auto value = name##Entry.get(); \ + name = value; \ + } else { \ + settingsFile.clear(); \ + settings[param1][param2] = defaultVal; \ + settingsFile << settings; \ + name = defaultVal; \ + } \ + } while(0) } \ No newline at end of file