|
|
|
|
@@ -1,6 +1,7 @@
|
|
|
|
|
#include <KaizenGui.hpp>
|
|
|
|
|
#include <backend/Core.hpp>
|
|
|
|
|
#include <ImGuiImpl/GUI.hpp>
|
|
|
|
|
#include <ImGuiImpl/ProgressIndicators.hpp>
|
|
|
|
|
#include <ImGuiImpl/StatusBar.hpp>
|
|
|
|
|
#include <resources/gamecontrollerdb.h>
|
|
|
|
|
|
|
|
|
|
@@ -9,6 +10,10 @@ KaizenGui::KaizenGui() noexcept : window("Kaizen", 800, 600), settingsWindow(win
|
|
|
|
|
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
|
|
|
|
|
|
|
|
|
SDL_AddGamepadMapping(gamecontrollerdb_str);
|
|
|
|
|
|
|
|
|
|
std::thread fileWorker(&KaizenGui::FileWorker, this);
|
|
|
|
|
|
|
|
|
|
fileWorker.detach();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KaizenGui::~KaizenGui() {
|
|
|
|
|
@@ -16,7 +21,7 @@ KaizenGui::~KaizenGui() {
|
|
|
|
|
SDL_Quit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void KaizenGui::QueryDevices(SDL_Event event) {
|
|
|
|
|
void KaizenGui::QueryDevices(const SDL_Event &event) {
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
case SDL_EVENT_GAMEPAD_ADDED:
|
|
|
|
|
if (!gamepad) {
|
|
|
|
|
@@ -36,8 +41,8 @@ void KaizenGui::QueryDevices(SDL_Event event) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void KaizenGui::HandleInput(SDL_Event event) {
|
|
|
|
|
n64::Core& core = n64::Core::GetInstance();
|
|
|
|
|
void KaizenGui::HandleInput(const SDL_Event &event) {
|
|
|
|
|
const n64::Core& core = n64::Core::GetInstance();
|
|
|
|
|
n64::PIF &pif = n64::Core::GetMem().mmio.si.pif;
|
|
|
|
|
switch(event.type) {
|
|
|
|
|
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
|
|
|
|
@@ -52,7 +57,7 @@ void KaizenGui::HandleInput(SDL_Event event) {
|
|
|
|
|
|
|
|
|
|
float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
|
|
|
|
|
if (xclamped < 0) {
|
|
|
|
|
xclamped /= float(std::abs(SDL_JOYSTICK_AXIS_MAX));
|
|
|
|
|
xclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MAX));
|
|
|
|
|
} else {
|
|
|
|
|
xclamped /= SDL_JOYSTICK_AXIS_MAX;
|
|
|
|
|
}
|
|
|
|
|
@@ -61,15 +66,15 @@ void KaizenGui::HandleInput(SDL_Event event) {
|
|
|
|
|
|
|
|
|
|
float yclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
|
|
|
|
|
if (yclamped < 0) {
|
|
|
|
|
yclamped /= float(std::abs(SDL_JOYSTICK_AXIS_MIN));
|
|
|
|
|
yclamped /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MIN));
|
|
|
|
|
} else {
|
|
|
|
|
yclamped /= SDL_JOYSTICK_AXIS_MAX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yclamped *= 86;
|
|
|
|
|
|
|
|
|
|
pif.UpdateAxis(0, n64::Controller::Axis::Y, -yclamped);
|
|
|
|
|
pif.UpdateAxis(0, n64::Controller::Axis::X, xclamped);
|
|
|
|
|
pif.UpdateAxis(0, n64::Controller::Axis::Y, static_cast<s8>(-yclamped));
|
|
|
|
|
pif.UpdateAxis(0, n64::Controller::Axis::X, static_cast<s8>( xclamped));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
|
|
|
|
@@ -90,7 +95,7 @@ void KaizenGui::HandleInput(SDL_Event event) {
|
|
|
|
|
case SDL_EVENT_KEY_DOWN:
|
|
|
|
|
case SDL_EVENT_KEY_UP:
|
|
|
|
|
{
|
|
|
|
|
auto keys = SDL_GetKeyboardState(nullptr);
|
|
|
|
|
const auto keys = SDL_GetKeyboardState(nullptr);
|
|
|
|
|
if((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) {
|
|
|
|
|
fileDialogOpen = true;
|
|
|
|
|
}
|
|
|
|
|
@@ -130,15 +135,25 @@ void KaizenGui::HandleInput(SDL_Event event) {
|
|
|
|
|
pif.UpdateButton(0, n64::Controller::Key::DRight, keys[SDL_SCANCODE_L]);
|
|
|
|
|
pif.UpdateButton(0, n64::Controller::Key::LT, keys[SDL_SCANCODE_A]);
|
|
|
|
|
pif.UpdateButton(0, n64::Controller::Key::RT, keys[SDL_SCANCODE_S]);
|
|
|
|
|
pif.UpdateAxis(0, n64::Controller::Axis::Y, keys[SDL_SCANCODE_UP] ? 86 : keys[SDL_SCANCODE_DOWN] ? -86 : 0);
|
|
|
|
|
pif.UpdateAxis(0, n64::Controller::Axis::X, keys[SDL_SCANCODE_LEFT] ? -86 : keys[SDL_SCANCODE_RIGHT] ? 86 : 0);
|
|
|
|
|
|
|
|
|
|
if (keys[SDL_SCANCODE_UP]) pif.UpdateAxis(0, n64::Controller::Axis::Y, 86);
|
|
|
|
|
else pif.UpdateAxis(0, n64::Controller::Axis::Y, 0);
|
|
|
|
|
|
|
|
|
|
if (keys[SDL_SCANCODE_DOWN]) pif.UpdateAxis(0, n64::Controller::Axis::Y, -86);
|
|
|
|
|
else pif.UpdateAxis(0, n64::Controller::Axis::Y, 0);
|
|
|
|
|
|
|
|
|
|
if (keys[SDL_SCANCODE_LEFT]) pif.UpdateAxis(0, n64::Controller::Axis::X, -86);
|
|
|
|
|
else pif.UpdateAxis(0, n64::Controller::Axis::X, 0);
|
|
|
|
|
|
|
|
|
|
if (keys[SDL_SCANCODE_RIGHT]) pif.UpdateAxis(0, n64::Controller::Axis::X, 86);
|
|
|
|
|
else pif.UpdateAxis(0, n64::Controller::Axis::X, 0);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::tuple<std::optional<u64>, std::optional<Util::Error::MemoryAccess>> RenderErrorMessageDetails() {
|
|
|
|
|
std::pair<std::optional<s64>, std::optional<Util::Error::MemoryAccess>> RenderErrorMessageDetails() {
|
|
|
|
|
auto lastPC = Util::Error::GetLastPC();
|
|
|
|
|
if(lastPC.has_value()) {
|
|
|
|
|
ImGui::Text("%s", std::format("Occurred @ PC = {:016X}", Util::Error::GetLastPC().value()).c_str());
|
|
|
|
|
@@ -146,10 +161,10 @@ std::tuple<std::optional<u64>, std::optional<Util::Error::MemoryAccess>> RenderE
|
|
|
|
|
|
|
|
|
|
auto memoryAccess = Util::Error::GetMemoryAccess();
|
|
|
|
|
if(memoryAccess.has_value()) {
|
|
|
|
|
auto memoryAccessV = memoryAccess.value();
|
|
|
|
|
ImGui::Text("%s", std::format("{} {}-bit value @ {:08X}{}", memoryAccessV.is_write ? "Writing" : "Reading",
|
|
|
|
|
(u8)memoryAccessV.size, memoryAccessV.address,
|
|
|
|
|
memoryAccessV.is_write ? std::format(" (value = 0x{:X})", memoryAccessV.written_val) : "")
|
|
|
|
|
const auto [is_write, size, address, written_val] = memoryAccess.value();
|
|
|
|
|
ImGui::Text("%s", std::format("{} {}-bit value @ {:08X}{}", is_write ? "Writing" : "Reading",
|
|
|
|
|
static_cast<u8>(size), address,
|
|
|
|
|
is_write ? std::format(" (value = 0x{:X})", written_val) : "")
|
|
|
|
|
.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -227,7 +242,7 @@ void KaizenGui::RenderUI() {
|
|
|
|
|
settingsWindow.render();
|
|
|
|
|
debugger.render();
|
|
|
|
|
|
|
|
|
|
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
|
|
|
|
const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
|
|
|
|
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
|
|
|
|
|
|
|
|
|
if (ImGui::BeginPopupModal("About Kaizen", &aboutOpen, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
|
|
|
@@ -258,9 +273,9 @@ void KaizenGui::RenderUI() {
|
|
|
|
|
RenderErrorMessageDetails();
|
|
|
|
|
|
|
|
|
|
if(n64::Core::GetInstance().romLoaded && !n64::Core::GetInstance().pause) {
|
|
|
|
|
bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
|
|
|
|
|
bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
|
|
|
|
|
bool chooseAnother = ImGui::Button("Choose another ROM");
|
|
|
|
|
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
|
|
|
|
|
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
|
|
|
|
|
const bool chooseAnother = ImGui::Button("Choose another ROM");
|
|
|
|
|
if(ignore || stop || chooseAnother) {
|
|
|
|
|
Util::Error::SetHandled();
|
|
|
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
|
@@ -306,10 +321,10 @@ void KaizenGui::RenderUI() {
|
|
|
|
|
ImGui::Text(R"(Error message: "%s")", Util::Error::GetError().c_str());
|
|
|
|
|
auto [lastPC, memoryAccess] = RenderErrorMessageDetails();
|
|
|
|
|
|
|
|
|
|
bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
|
|
|
|
|
bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
|
|
|
|
|
bool chooseAnother = ImGui::Button("Choose another ROM");
|
|
|
|
|
bool openInDebugger = lastPC.has_value() ? ImGui::Button("Add breakpoint at this PC and open the debugger") : false;
|
|
|
|
|
const bool ignore = ImGui::Button("Try continuing"); ImGui::SameLine();
|
|
|
|
|
const bool stop = ImGui::Button("Stop emulation"); ImGui::SameLine();
|
|
|
|
|
const bool chooseAnother = ImGui::Button("Choose another ROM");
|
|
|
|
|
const bool openInDebugger = lastPC.has_value() ? ImGui::Button("Add breakpoint at this PC and open the debugger") : false;
|
|
|
|
|
if(ignore || stop || chooseAnother || openInDebugger) {
|
|
|
|
|
Util::Error::SetHandled();
|
|
|
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
|
@@ -346,6 +361,26 @@ void KaizenGui::RenderUI() {
|
|
|
|
|
ImGui::EndMainStatusBar();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldDisplaySpinner) {
|
|
|
|
|
ImGui::SetNextWindowPos({static_cast<float>(width) * 0.5f, static_cast<float>(height) * 0.5f}, 0, ImVec2(0.5f, 0.5f));
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, IM_COL32_BLACK_TRANS);
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
|
|
|
|
|
|
|
|
|
ImGui::Begin("##spinnerContainer", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration);
|
|
|
|
|
|
|
|
|
|
ImGui::Spinner("##spinner", 10.f, 4.f, ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive]));
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
|
|
ImGui::PushFont(nullptr, ImGui::GetStyle().FontSizeBase * 2.f);
|
|
|
|
|
ImGui::Text("Loading \"%s\"...", fs::path(fileToLoad).stem().c_str());
|
|
|
|
|
ImGui::PopFont();
|
|
|
|
|
|
|
|
|
|
ImGui::End();
|
|
|
|
|
|
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::Render();
|
|
|
|
|
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
|
|
|
|
ImGui::UpdatePlatformWindows();
|
|
|
|
|
@@ -354,20 +389,22 @@ void KaizenGui::RenderUI() {
|
|
|
|
|
|
|
|
|
|
if(fileDialogOpen) {
|
|
|
|
|
fileDialogOpen = false;
|
|
|
|
|
const SDL_DialogFileFilter filters[] = {{"All files", "*"}, {"Nintendo 64 executable", "n64;z64;v64"}, {"Nintendo 64 executable archive", "rar;tar;zip;7z"}};
|
|
|
|
|
SDL_ShowOpenFileDialog([](void *userdata, const char * const *filelist, int filter) {
|
|
|
|
|
auto kaizen = (KaizenGui*)userdata;
|
|
|
|
|
constexpr SDL_DialogFileFilter filters[] = {{"All files", "*"}, {"Nintendo 64 executable", "n64;z64;v64"}, {"Nintendo 64 executable archive", "rar;tar;zip;7z"}};
|
|
|
|
|
SDL_ShowOpenFileDialog([](void *userdata, const char * const *filelist, int) {
|
|
|
|
|
const auto kaizen = static_cast<KaizenGui*>(userdata);
|
|
|
|
|
|
|
|
|
|
if (!filelist) {
|
|
|
|
|
panic("An error occured: {}", SDL_GetError());
|
|
|
|
|
return;
|
|
|
|
|
} else if (!*filelist) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!*filelist) {
|
|
|
|
|
warn("The user did not select any file.");
|
|
|
|
|
warn("Most likely, the dialog was canceled.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kaizen->LoadROM(*filelist);
|
|
|
|
|
kaizen->fileToLoad = *filelist;
|
|
|
|
|
kaizen->shouldDisplaySpinner = true;
|
|
|
|
|
}, this, window.getHandle(), filters, 3, nullptr, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -375,17 +412,17 @@ void KaizenGui::RenderUI() {
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if(core.romLoaded) {
|
|
|
|
|
core.parallel.UpdateScreen();
|
|
|
|
|
core.parallel.UpdateScreen<true>();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
core.parallel.UpdateScreen(false);
|
|
|
|
|
core.parallel.UpdateScreen<false>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void KaizenGui::LoadROM(const std::string &path) noexcept {
|
|
|
|
|
n64::Core& core = n64::Core::GetInstance();
|
|
|
|
|
core.LoadROM(path);
|
|
|
|
|
const auto gameNameDB = core.GetMem().rom.gameNameDB;
|
|
|
|
|
const auto gameNameDB = n64::Core::GetMem().rom.gameNameDB;
|
|
|
|
|
SDL_SetWindowTitle(window.getHandle(), ("Kaizen - " + gameNameDB).c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -405,11 +442,14 @@ void KaizenGui::run() {
|
|
|
|
|
case SDL_EVENT_WINDOW_RESTORED:
|
|
|
|
|
minimized = false;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
QueryDevices(e);
|
|
|
|
|
HandleInput(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SDL_GetWindowSize(window.getHandle(), &width, &height);
|
|
|
|
|
|
|
|
|
|
emuThread.run();
|
|
|
|
|
RenderUI();
|
|
|
|
|
}
|
|
|
|
|
|