switch to INI for settings (easier), make settings a singleton thingy, slightly simplify core pause/stop/reset. Drop support for remappable inputs for now
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,7 +6,7 @@ saves/
|
||||
.vscode/
|
||||
out/
|
||||
*.toml
|
||||
*.ini
|
||||
imgui.ini
|
||||
*.z64
|
||||
*.n64
|
||||
*.v64
|
||||
|
||||
2168
resources/gamecontrollerdb.h
Normal file
2168
resources/gamecontrollerdb.h
Normal file
File diff suppressed because it is too large
Load Diff
8
resources/options.ini
Normal file
8
resources/options.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[general]
|
||||
savepath=
|
||||
[audio]
|
||||
volumel=0.74
|
||||
volumer=0.74
|
||||
lock=true
|
||||
[cpu]
|
||||
type=interpreter
|
||||
@@ -8,16 +8,16 @@ Core::Core() : cpu(std::make_unique<Interpreter>(parallel)) {}
|
||||
void Core::Stop() {
|
||||
pause = true;
|
||||
romLoaded = false;
|
||||
rom = {};
|
||||
cpu->Reset();
|
||||
}
|
||||
|
||||
bool Core::LoadTAS(const fs::path &path) const { return cpu->GetMem().mmio.si.pif.movie.Load(path); }
|
||||
|
||||
void Core::LoadROM(const std::string &rom_) {
|
||||
pause = true;
|
||||
Stop();
|
||||
rom = rom_;
|
||||
cpu->Reset();
|
||||
romLoaded = true;
|
||||
|
||||
std::string archive_types[] = {".zip", ".7z", ".rar", ".tar"};
|
||||
|
||||
@@ -37,6 +37,7 @@ void Core::LoadROM(const std::string &rom_) {
|
||||
cpu->GetMem().LoadSRAM(cpu->GetMem().saveType, rom);
|
||||
cpu->GetMem().mmio.si.pif.Execute();
|
||||
pause = false;
|
||||
romLoaded = true;
|
||||
}
|
||||
|
||||
void Core::Run(float volumeL, float volumeR) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#include <AudioSettings.hpp>
|
||||
|
||||
AudioSettings::AudioSettings(nlohmann::json &settings) : settings(settings) {
|
||||
lockChannels.setChecked(JSONGetField<bool>(settings, "audio", "lock"));
|
||||
volumeL.setValue(JSONGetField<float>(settings, "audio", "volumeL") * 100);
|
||||
volumeR.setValue(JSONGetField<float>(settings, "audio", "volumeR") * 100);
|
||||
AudioSettings::AudioSettings() {
|
||||
lockChannels.setChecked(Options::GetInstance().GetValue<bool>("audio", "lock"));
|
||||
volumeL.setValue(Options::GetInstance().GetValue<float>("audio", "volumeL") * 100);
|
||||
volumeR.setValue(Options::GetInstance().GetValue<float>("audio", "volumeR") * 100);
|
||||
}
|
||||
|
||||
bool AudioSettings::render() {
|
||||
if(lockChannels.render()) {
|
||||
auto isChecked = lockChannels.isChecked();
|
||||
JSONSetField(settings, "audio", "lock", isChecked);
|
||||
Options::GetInstance().SetValue("audio", "lock", isChecked);
|
||||
if (isChecked) {
|
||||
volumeR.setValue(volumeL.getValue());
|
||||
}
|
||||
@@ -21,10 +21,10 @@ bool AudioSettings::render() {
|
||||
|
||||
if(volumeL.render()) {
|
||||
float valueL = volumeL.getValue();
|
||||
JSONSetField(settings, "audio", "volumeL", float(valueL) / 100.f);
|
||||
Options::GetInstance().SetValue("audio", "volumeL", float(valueL) / 100.f);
|
||||
if (lockChannels.isChecked()) {
|
||||
volumeR.setValue(valueL);
|
||||
JSONSetField(settings, "audio", "volumeR", float(valueL) / 100.f);
|
||||
Options::GetInstance().SetValue("audio", "volumeR", float(valueL) / 100.f);
|
||||
}
|
||||
|
||||
modified = true;
|
||||
@@ -34,7 +34,7 @@ bool AudioSettings::render() {
|
||||
|
||||
if(volumeR.render()) {
|
||||
if (!lockChannels.isChecked()) {
|
||||
JSONSetField(settings, "audio", "volumeR", float(volumeR.getValue()) / 100.f);
|
||||
Options::GetInstance().SetValue("audio", "volumeR", float(volumeR.getValue()) / 100.f);
|
||||
}
|
||||
|
||||
modified = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#include <JSONUtils.hpp>
|
||||
#include <Options.hpp>
|
||||
#include <ImGuiImpl/Checkbox.hpp>
|
||||
#include <ImGuiImpl/Slider.hpp>
|
||||
|
||||
@@ -9,9 +9,8 @@ class AudioSettings final {
|
||||
public:
|
||||
gui::SliderFloat volumeL{"Volume L", 0.f, 100.f, 0.f};
|
||||
gui::SliderFloat volumeR{"Volume R", 0.f, 100.f, 0.f};
|
||||
explicit AudioSettings(nlohmann::json &);
|
||||
explicit AudioSettings();
|
||||
bool render();
|
||||
void setModified(bool v) { modified = v; }
|
||||
bool getModified() { return modified; }
|
||||
nlohmann::json &settings;
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@ include_directories(
|
||||
../../external/imgui/backends
|
||||
../../external/nfd/src/include
|
||||
../../external/cflags/include
|
||||
../../external/mINI/src/
|
||||
ImGuiImpl/
|
||||
)
|
||||
|
||||
@@ -109,6 +110,7 @@ add_subdirectory(../../external/unarr unarr)
|
||||
add_subdirectory(../../external/SDL SDL)
|
||||
add_subdirectory(../../external/cflags cflags)
|
||||
add_subdirectory(../../external/imgui imgui)
|
||||
add_subdirectory(../../external/mINI mINI)
|
||||
add_subdirectory(../../external/nfd nfd)
|
||||
set(CAPSTONE_ARCHITECTURE_DEFAULT OFF)
|
||||
set(CAPSTONE_MIPS_SUPPORT ON)
|
||||
@@ -127,10 +129,10 @@ add_executable(kaizen
|
||||
SettingsWindow.cpp
|
||||
CPUSettings.hpp
|
||||
CPUSettings.cpp
|
||||
JSONUtils.hpp
|
||||
AudioSettings.hpp
|
||||
AudioSettings.cpp
|
||||
NativeWindow.hpp
|
||||
../utils/Options.cpp
|
||||
Debugger.hpp
|
||||
Debugger.cpp)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <CPUSettings.hpp>
|
||||
#include <JSONUtils.hpp>
|
||||
#include <Options.hpp>
|
||||
#include <log.hpp>
|
||||
|
||||
CPUSettings::CPUSettings(nlohmann::json &settings) : settings(settings) {
|
||||
if (JSONGetField<std::string>(settings, "cpu_type") == "jit") {
|
||||
CPUSettings::CPUSettings() {
|
||||
if (Options::GetInstance().GetValue<std::string>("cpu", "type") == "jit") {
|
||||
cpuTypes.setCurrentIndex(1);
|
||||
} else {
|
||||
cpuTypes.setCurrentIndex(0);
|
||||
@@ -13,7 +13,7 @@ CPUSettings::CPUSettings(nlohmann::json &settings) : settings(settings) {
|
||||
bool CPUSettings::render() {
|
||||
if(cpuTypes.render()) {
|
||||
if(cpuTypes.getCurrentIndex() == 0) {
|
||||
JSONSetField(settings, "cpu_type", "interpreter");
|
||||
Options::GetInstance().SetValue<std::string>("cpu", "type", "interpreter");
|
||||
} else {
|
||||
Util::panic("JIT should not be selectable??");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#include <JSONUtils.hpp>
|
||||
#include <Options.hpp>
|
||||
#include <ImGuiImpl/Combobox.hpp>
|
||||
|
||||
class CPUSettings final {
|
||||
@@ -7,8 +7,7 @@ class CPUSettings final {
|
||||
bool modified = false;
|
||||
public:
|
||||
bool render();
|
||||
explicit CPUSettings(nlohmann::json &);
|
||||
explicit CPUSettings();
|
||||
void setModified(bool v) { modified = v; }
|
||||
bool getModified() { return modified; }
|
||||
nlohmann::json &settings;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ EmuThread::EmuThread(const std::shared_ptr<n64::Core> &core, double &fps, Render
|
||||
renderWidget(renderWidget), core(core), settings(settings), fps(fps) {}
|
||||
|
||||
void EmuThread::run() noexcept {
|
||||
isRunning = true;
|
||||
if(!core->romLoaded) return;
|
||||
|
||||
auto lastSample = std::chrono::high_resolution_clock::now();
|
||||
auto avgFps = 16.667;
|
||||
@@ -49,15 +49,11 @@ void EmuThread::TogglePause() const noexcept {
|
||||
}
|
||||
|
||||
void EmuThread::Reset() const noexcept {
|
||||
core->pause = true;
|
||||
core->Stop();
|
||||
core->LoadROM(core->rom);
|
||||
core->pause = false;
|
||||
}
|
||||
|
||||
void EmuThread::Stop() const noexcept {
|
||||
Util::RPC::GetInstance().Update(Util::RPC::Idling);
|
||||
core->rom = {};
|
||||
core->pause = true;
|
||||
core->Stop();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
void Reset() const noexcept;
|
||||
void Stop() const noexcept;
|
||||
|
||||
bool interruptionRequested = false, isRunning = false, parallelRDPInitialized = false;
|
||||
bool interruptionRequested = false, parallelRDPInitialized = false;
|
||||
std::shared_ptr<n64::Core> core;
|
||||
SettingsWindow &settings;
|
||||
double& fps;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <common.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static FORCE_INLINE nlohmann::json JSONOpenOrCreate(const std::string &path) {
|
||||
auto fileExists = fs::exists(path);
|
||||
|
||||
std::fstream file;
|
||||
nlohmann::json json;
|
||||
|
||||
if (fileExists) {
|
||||
file = std::fstream(path, std::fstream::in | std::fstream::out);
|
||||
json = nlohmann::json::parse(file);
|
||||
file.close();
|
||||
return json;
|
||||
}
|
||||
|
||||
file = std::fstream(path, std::fstream::in | std::fstream::out | std::fstream::trunc);
|
||||
json["general"]["savePath"] = "";
|
||||
json["audio"]["volumeL"] = 0.5;
|
||||
json["audio"]["volumeR"] = 0.5;
|
||||
json["audio"]["lock"] = true;
|
||||
json["cpu_type"] = "interpreter";
|
||||
|
||||
file << json;
|
||||
file.close();
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static FORCE_INLINE void JSONSetField(nlohmann::json &json, const std::string &key, const std::string &field,
|
||||
const T &value) {
|
||||
json[key][field] = value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static FORCE_INLINE T JSONGetField(nlohmann::json &json, const std::string &key, const std::string &field) {
|
||||
return json[key][field].get<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static FORCE_INLINE void JSONSetField(nlohmann::json &json, const std::string &key, const T &value) {
|
||||
json[key] = value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static FORCE_INLINE T JSONGetField(nlohmann::json &json, const std::string &key) {
|
||||
return json[key].get<T>();
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <backend/Core.hpp>
|
||||
#include <ImGuiImpl/StatusBar.hpp>
|
||||
#include <ImGuiImpl/GUI.hpp>
|
||||
#include <resources/gamecontrollerdb.h>
|
||||
|
||||
bool HandleInput(void *userdata, SDL_Event *event);
|
||||
bool QueryDevices(void *userdata, SDL_Event *event);
|
||||
@@ -10,6 +11,7 @@ bool QueryDevices(void *userdata, SDL_Event *event);
|
||||
KaizenGui::KaizenGui() noexcept : window("Kaizen", 800, 600), core(std::make_shared<n64::Core>()), vulkanWidget(core, window.getHandle()), emuThread(core, fpsCounter, vulkanWidget, settingsWindow) {
|
||||
gui::Initialize(core->parallel.wsi, window.getHandle());
|
||||
|
||||
SDL_AddGamepadMapping(gamecontrollerdb_str);
|
||||
SDL_AddEventWatch(HandleInput, this);
|
||||
SDL_AddEventWatch(QueryDevices, this);
|
||||
|
||||
@@ -59,6 +61,7 @@ KaizenGui::KaizenGui() noexcept : window("Kaizen", 800, 600), core(std::make_sha
|
||||
if(result == NFD_CANCEL) return;
|
||||
if(result == NFD_ERROR)
|
||||
Util::panic("Error: {}", NFD::GetError());
|
||||
|
||||
LoadROM(path.get());
|
||||
}},
|
||||
{"Exit", [&]() {
|
||||
@@ -233,7 +236,6 @@ void KaizenGui::LoadROM(const std::string &path) noexcept {
|
||||
|
||||
void KaizenGui::run() {
|
||||
while(!quit) {
|
||||
emuThread.run();
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
ImGui_ImplSDL3_ProcessEvent(&e);
|
||||
@@ -245,6 +247,7 @@ void KaizenGui::run() {
|
||||
}
|
||||
}
|
||||
|
||||
emuThread.run();
|
||||
RenderUI();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ private:
|
||||
class SDLWSIPlatform final : public Vulkan::WSIPlatform {
|
||||
public:
|
||||
explicit SDLWSIPlatform(const std::shared_ptr<n64::Core> &core, SDL_Window* window) : window(window), core(core) {}
|
||||
~SDLWSIPlatform() {
|
||||
if(gamepadConnected)
|
||||
SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
~SDLWSIPlatform() = default;
|
||||
|
||||
std::vector<const char *> get_instance_extensions() override {
|
||||
auto vec = std::vector<const char *>();
|
||||
@@ -71,7 +68,6 @@ public:
|
||||
VkSurfaceKHR surface;
|
||||
private:
|
||||
std::shared_ptr<n64::Core> core;
|
||||
SDL_Gamepad *gamepad{};
|
||||
bool gamepadConnected = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
#include <fmt/core.h>
|
||||
#include <nfd.hpp>
|
||||
#include <log.hpp>
|
||||
#include <JSONUtils.hpp>
|
||||
#include <Options.hpp>
|
||||
|
||||
std::string savePath;
|
||||
|
||||
SettingsWindow::SettingsWindow() : settings{JSONOpenOrCreate("resources/settings.json")}, cpuSettings{settings}, audioSettings{settings} {
|
||||
SettingsWindow::SettingsWindow() {
|
||||
savesFolder.setName(fmt::format("Save path: {}",
|
||||
JSONGetField<std::string>(settings, "general", "savePath")));
|
||||
Options::GetInstance().GetValue<std::string>("general", "savePath")));
|
||||
|
||||
tabs.addTab({"General", [&]() {
|
||||
if(savesFolder.render()) {
|
||||
@@ -22,7 +22,7 @@ SettingsWindow::SettingsWindow() : settings{JSONOpenOrCreate("resources/settings
|
||||
Util::panic("Error: {}", NFD::GetError());
|
||||
|
||||
savesFolder.setName(fmt::format("Save path: {}", outPath.get()));
|
||||
JSONSetField(settings, "general", "savePath", outPath.get());
|
||||
Options::GetInstance().SetValue<std::string>("general", "savePath", outPath.get());
|
||||
apply.setEnabled(true);
|
||||
}
|
||||
}});
|
||||
@@ -48,9 +48,7 @@ SettingsWindow::SettingsWindow() : settings{JSONOpenOrCreate("resources/settings
|
||||
|
||||
if(apply.render()) {
|
||||
apply.setEnabled(false);
|
||||
std::ofstream file("resources/settings.json");
|
||||
file << settings;
|
||||
file.close();
|
||||
Options::GetInstance().Apply();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
class SettingsWindow final {
|
||||
gui::PushButton apply{"Apply", "", false}, savesFolder{"Open", "Save path: {}"};
|
||||
nlohmann::json settings;
|
||||
CPUSettings cpuSettings;
|
||||
AudioSettings audioSettings;
|
||||
public:
|
||||
|
||||
31
src/utils/Options.cpp
Normal file
31
src/utils/Options.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include <Options.hpp>
|
||||
|
||||
template <>
|
||||
void Options::SetValue<std::string>(const std::string &key, const std::string &field, const std::string &value) {
|
||||
structure[key][field] = value;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Options::SetValue<float>(const std::string &key, const std::string &field, const float &value) {
|
||||
structure[key][field] = fmt::format("{:.2f}", value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void Options::SetValue<bool>(const std::string &key, const std::string &field, const bool &value) {
|
||||
structure[key][field] = value ? "true" : "false";
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string Options::GetValue<std::string>(const std::string &key, const std::string &field) {
|
||||
return structure[key][field];
|
||||
}
|
||||
|
||||
template<>
|
||||
float Options::GetValue<float>(const std::string &key, const std::string &field) {
|
||||
return std::stof(structure[key][field]);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool Options::GetValue<bool>(const std::string &key, const std::string &field) {
|
||||
return structure[key][field] == "true" ? true : false;
|
||||
}
|
||||
@@ -2,48 +2,44 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <common.hpp>
|
||||
#include <mini/ini.h>
|
||||
#include <log.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static FORCE_INLINE mINI::INIStructure JSONOpenOrCreate(const std::string &path) {
|
||||
auto fileExists = fs::exists(path);
|
||||
|
||||
mINI::INIStructure options;
|
||||
mINI::INIFile file(path);
|
||||
|
||||
struct Options {
|
||||
Options() : file{"resources/options.ini"} {
|
||||
auto fileExists = fs::exists("resources/options.ini");
|
||||
if(fileExists) {
|
||||
file.read(options);
|
||||
return options;
|
||||
file.read(structure);
|
||||
return;
|
||||
}
|
||||
|
||||
options["general"]["savePath"] = "";
|
||||
options["audio"]["volumeL"] = 0.5;
|
||||
options["audio"]["volumeR"] = 0.5;
|
||||
options["audio"]["lock"] = true;
|
||||
options["cpu"]["type"] = "interpreter";
|
||||
structure["general"]["savePath"] = "";
|
||||
structure["audio"]["volumeL"] = "0.5";
|
||||
structure["audio"]["volumeR"] = "0.5";
|
||||
structure["audio"]["lock"] = "true";
|
||||
structure["cpu"]["type"] = "interpreter";
|
||||
|
||||
file.write(options);
|
||||
if(!file.generate(structure))
|
||||
Util::panic("Couldn't generate settings' INI!");
|
||||
}
|
||||
|
||||
return options;
|
||||
static Options &GetInstance() {
|
||||
static Options instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static FORCE_INLINE void JSONSetField(mINI::INIStructure &options, const std::string &key, const std::string &field,
|
||||
const T &value) {
|
||||
options[key][field] = value;
|
||||
}
|
||||
|
||||
void SetValue(const std::string &key, const std::string &field, const T &value);
|
||||
template <typename T>
|
||||
static FORCE_INLINE T JSONGetField(mINI::INIStructure &options, const std::string &key, const std::string &field) {
|
||||
return options[key][field];
|
||||
}
|
||||
T GetValue(const std::string &key, const std::string &field);
|
||||
|
||||
template <typename T>
|
||||
static FORCE_INLINE void JSONSetField(mINI::INIStructure &options, const std::string &key, const T &value) {
|
||||
options[key] = value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static FORCE_INLINE T JSONGetField(mINI::INIStructure &options, const std::string &key) {
|
||||
return options[key];
|
||||
void Apply() {
|
||||
if(!file.write(structure))
|
||||
Util::panic("Could not modify options on disk!");
|
||||
}
|
||||
private:
|
||||
mINI::INIFile file;
|
||||
mINI::INIStructure structure;
|
||||
};
|
||||
Reference in New Issue
Block a user