#include #include #include #include #include KaizenGui::KaizenGui() noexcept : window("Kaizen", 800, 600), settingsWindow(window), core(std::make_shared(static_cast(settingsWindow.cpuSettings.GetCPUType()))), vulkanWidget(core, window.getHandle()), emuThread(core, fpsCounter, vulkanWidget, settingsWindow), debugger(core) { gui::Initialize(core->parallel.wsi, window.getHandle()); SDL_InitSubSystem(SDL_INIT_GAMEPAD); SDL_AddGamepadMapping(gamecontrollerdb_str); } KaizenGui::~KaizenGui() { gui::Cleanup(); SDL_Quit(); } void KaizenGui::QueryDevices(SDL_Event event) { switch (event.type) { case SDL_EVENT_GAMEPAD_ADDED: { const auto index = event.gdevice.which; gamepad = SDL_OpenGamepad(index); info("Found controller!"); } break; case SDL_EVENT_GAMEPAD_REMOVED: SDL_CloseGamepad(gamepad); break; default: break; } } void KaizenGui::HandleInput(SDL_Event event) { n64::PIF &pif = core->cpu->GetMem().mmio.si.pif; switch(event.type) { case SDL_EVENT_GAMEPAD_AXIS_MOTION: if(!gamepad) break; { pif.UpdateButton(0, n64::Controller::Key::Z, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX); pif.UpdateButton(0, n64::Controller::Key::CUp, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) <= -127); pif.UpdateButton(0, n64::Controller::Key::CDown, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) >= 127); pif.UpdateButton(0, n64::Controller::Key::CLeft, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) <= -127); pif.UpdateButton(0, n64::Controller::Key::CRight, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) >= 127); float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX); if (xclamped < 0) { xclamped /= float(std::abs(SDL_JOYSTICK_AXIS_MAX)); } else { xclamped /= SDL_JOYSTICK_AXIS_MAX; } xclamped *= 86; float yclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY); if (yclamped < 0) { yclamped /= 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); } break; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: if(!gamepad) break; pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH)); pif.UpdateButton(0, n64::Controller::Key::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST)); pif.UpdateButton(0, n64::Controller::Key::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START)); pif.UpdateButton(0, n64::Controller::Key::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP)); pif.UpdateButton(0, n64::Controller::Key::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)); pif.UpdateButton(0, n64::Controller::Key::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT)); pif.UpdateButton(0, n64::Controller::Key::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT)); pif.UpdateButton(0, n64::Controller::Key::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)); pif.UpdateButton(0, n64::Controller::Key::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)); break; case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: if(gamepad) break; { auto keys = SDL_GetKeyboardState(nullptr); if((keys[SDL_SCANCODE_LCTRL] || keys[SDL_SCANCODE_RCTRL]) && keys[SDL_SCANCODE_O]) { fileDialogOpen = true; } fastForward = keys[SDL_SCANCODE_SPACE]; if(!unlockFramerate) core->parallel.SetFramerateUnlocked(fastForward); if(core->romLoaded) { if(keys[SDL_SCANCODE_P]) { emuThread.TogglePause(); } if(keys[SDL_SCANCODE_R]) { emuThread.Reset(); } if(keys[SDL_SCANCODE_Q]) { emuThread.Stop(); } } pif.UpdateButton(0, n64::Controller::Key::Z, keys[SDL_SCANCODE_Z]); pif.UpdateButton(0, n64::Controller::Key::CUp, keys[SDL_SCANCODE_HOME]); pif.UpdateButton(0, n64::Controller::Key::CDown, keys[SDL_SCANCODE_END]); pif.UpdateButton(0, n64::Controller::Key::CLeft, keys[SDL_SCANCODE_DELETE]); pif.UpdateButton(0, n64::Controller::Key::CRight, keys[SDL_SCANCODE_PAGEDOWN]); pif.UpdateButton(0, n64::Controller::Key::A, keys[SDL_SCANCODE_X]); pif.UpdateButton(0, n64::Controller::Key::B, keys[SDL_SCANCODE_C]); pif.UpdateButton(0, n64::Controller::Key::Start, keys[SDL_SCANCODE_RETURN]); pif.UpdateButton(0, n64::Controller::Key::DUp, keys[SDL_SCANCODE_I]); pif.UpdateButton(0, n64::Controller::Key::DDown, keys[SDL_SCANCODE_K]); pif.UpdateButton(0, n64::Controller::Key::DLeft, keys[SDL_SCANCODE_J]); 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); } break; default: break; } } void KaizenGui::RenderUI() { gui::StartFrame(); if(ImGui::BeginMainMenuBar()) { if(ImGui::BeginMenu("File")) { if(ImGui::MenuItem("Open", "Ctrl-O")) { fileDialogOpen = true; } if(ImGui::MenuItem("Exit")) { quit = true; emuThread.Stop(); } ImGui::EndMenu(); } if(ImGui::BeginMenu("Emulation")) { ImGui::BeginDisabled(!core->romLoaded); if(ImGui::MenuItem(core->pause ? "Resume" : "Pause", "P")) { emuThread.TogglePause(); } if(ImGui::MenuItem("Reset", "R")) { emuThread.Reset(); } if(ImGui::MenuItem("Stop", "Q")) { emuThread.Stop(); core->romLoaded = false; } if(ImGui::Checkbox("Unlock framerate", &unlockFramerate)) { core->parallel.SetFramerateUnlocked(unlockFramerate); } if(ImGui::MenuItem("Open Debugger")) { debugger.Open(); } ImGui::EndDisabled(); if(ImGui::MenuItem("Options")) { settingsWindow.isOpen = true; } ImGui::EndMenu(); } if(ImGui::BeginMenu("Help")) { if(ImGui::MenuItem("About")) { aboutOpen = true; } ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } if(settingsWindow.isOpen) { ImGui::OpenPopup("Settings", ImGuiPopupFlags_None); } if(aboutOpen) { ImGui::OpenPopup("About Kaizen"); } settingsWindow.render(); debugger.render(); ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); if (ImGui::BeginPopupModal("About Kaizen", &aboutOpen, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Kaizen is a Nintendo 64 emulator that strives"); ImGui::Text("to offer a friendly user experience and compatibility."); ImGui::Text("Kaizen is licensed under the BSD 3-clause license."); ImGui::Text("Nintendo 64 is a registered trademark of Nintendo Co., Ltd."); if(ImGui::Button("OK")) { aboutOpen = false; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } if(ImGui::BeginMainStatusBar()) { ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate); ImGui::EndMainStatusBar(); } ImGui::Render(); if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable){ ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); } 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) { KaizenGui* kaizen = (KaizenGui*)userdata; if (!filelist) { panic("An error occured: {}", SDL_GetError()); return; } else if (!*filelist) { warn("The user did not select any file."); warn("Most likely, the dialog was canceled."); return; } kaizen->LoadROM(*filelist); }, this, window.getHandle(), filters, 3, nullptr, false); } if(core->romLoaded) { core->parallel.UpdateScreen(*core.get()); return; } core->parallel.UpdateScreen(*core.get(), false); } void KaizenGui::LoadROM(const std::string &path) noexcept { emuThread.core->LoadROM(path); const auto gameNameDB = emuThread.core->cpu->GetMem().rom.gameNameDB; } void KaizenGui::run() { while(!quit) { SDL_Event e; while (SDL_PollEvent(&e)) { ImGui_ImplSDL3_ProcessEvent(&e); switch(e.type) { case SDL_EVENT_QUIT: quit = true; emuThread.Stop(); break; } QueryDevices(e); HandleInput(e); } emuThread.run(); RenderUI(); } } void KaizenGui::LoadTAS(const std::string &path) const noexcept { if (emuThread.core->LoadTAS(fs::path(path))) { const auto gameNameDB = emuThread.core->cpu->GetMem().rom.gameNameDB; const auto movieName = fs::path(path).stem().string(); return; } panic("Could not load TAS movie {}!", path); }