#include #include #include #include #include #include #include #include #include #include #include #include void KaizenGui::populateRomsList(const std::string &romsPath) { centralWidget->setCurrentWidget(romPathNotSet); if (!romsPath.empty()) { int i = 0; centralWidget->setCurrentWidget(romsList); for (const auto &file : fs::recursive_directory_iterator{romsPath}) { if (!file.is_regular_file()) continue; auto filename = file.path().string(); bool isPlain = std::ranges::any_of(std::array{".n64", ".z64", ".v64"}, [&](const std::string &ext) { return file.path().extension() == ext; }); bool isArchive = std::ranges::any_of(std::array{".zip", ".7z", ".rar", ".tar"}, [&](const std::string &ext) { return file.path().extension() == ext; }); if (!isArchive && !isPlain) continue; auto rom = n64::Mem::LoadROM(isArchive, filename); auto regions = n64::GameDB::match(rom); if (rom.gameNameDB.empty()) rom.gameNameDB = fs::path(filename).stem(); romsListPaths.push_back(filename); romsList->insertRow(i); romsList->setItem( i, 0, new QTableWidgetItem(std::format("{} ({}) (Rev {})", rom.gameNameDB, n64::GameDB::regionCodeToReadable(rom.header.countryCode), rom.header.version) .c_str())); romsList->setItem(i, 1, new QTableWidgetItem(regions.c_str())); romsList->setItem(i, 2, new QTableWidgetItem("Never")); romsList->setItem(i, 3, new QTableWidgetItem("0h 0m 0s")); i++; } } currentHomeWidget = centralWidget->currentWidget(); } KaizenGui::KaizenGui() noexcept : QMainWindow(nullptr), settings(QSettings::UserScope) { SDL_InitSubSystem(SDL_INIT_GAMEPAD); SDL_AddGamepadMapping(gamecontrollerdb_str); hide(); romsList->verticalHeader()->hide(); romsList->setSizePolicy({QSizePolicy::Maximum, QSizePolicy::Maximum}); romsList->setSelectionMode(QAbstractItemView::SingleSelection); romsList->setSelectionBehavior(QAbstractItemView::SelectRows); romsList->setEditTriggers(QAbstractItemView::NoEditTriggers); romsList->setSortingEnabled(true); romsList->setColumnCount(4); romsList->setHorizontalHeaderItem(0, new QTableWidgetItem("Name")); romsList->setHorizontalHeaderItem(1, new QTableWidgetItem("Regions")); romsList->setHorizontalHeaderItem(2, new QTableWidgetItem("Last played")); romsList->setHorizontalHeaderItem(3, new QTableWidgetItem("Time played")); vulkanWidget = new RenderWidget(); vulkanWidget->hide(); centralWidget->addWidget(vulkanWidget); centralWidget->addWidget(romsList); centralWidget->addWidget(romPathNotSet); populateRomsList(Options::GetRomsPath()); connect(romsList, &QTableWidget::cellDoubleClicked, this, [&](int row, int) { auto fileToLoad = fs::path(Options::GetRomsPath()) / romsListPaths[row]; std::println("{}", fileToLoad.string()); LoadROM(fileToLoad.string()); }); installEventFilter(this); restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("windowState").toByteArray()); cpuTypeLabel = new QLabel("Interpreter"); if (Options::GetCpuType() == n64::CachedInterpreter) cpuTypeLabel->setText("Cached Interpreter"); idleSkipLabel = new QLabel("Idle skipping"); if (!Options::GetIdleSkip() || Options::GetCpuType() == n64::PlainInterpreter) idleSkipLabel->hide(); fpsLabel = new QLabel("Not running"); statusBar()->addWidget(fpsLabel); statusBar()->addWidget(cpuTypeLabel); statusBar()->addWidget(idleSkipLabel); setWindowTitle("Kaizen " KAIZEN_VERSION_STR); setMinimumSize(640, 480); setCentralWidget(centralWidget); setAcceptDrops(true); SDLeventsTimer = new QTimer(); SDLeventsTimer->setInterval(1); statusBarTimer = new QTimer(); statusBarTimer->setInterval(500); connect(SDLeventsTimer, &QTimer::timeout, this, [&] { SDL_Event e; while (SDL_PollEvent(&e)) { if (e.type == SDL_EVENT_GAMEPAD_ADDED) { if (!gamepad) { const auto index = e.gdevice.which; gamepad = SDL_OpenGamepad(index); warn("Found controller!"); warn("Name: {}", SDL_GetGamepadName(gamepad)); warn("Vendor: {}", SDL_GetGamepadVendor(gamepad)); } } if (e.type == SDL_EVENT_GAMEPAD_REMOVED) { if (gamepad) { SDL_CloseGamepad(gamepad); gamepad = nullptr; } } } }); SDLeventsTimer->start(); connect(statusBarTimer, &QTimer::timeout, this, [&] { pause->setText("Pause"); fpsLabel->setText(std::format("FPS: {:.2f}", 1000.f / elapsed).c_str()); if (core.pause) { pause->setText("Resume"); fpsLabel->setText("Paused"); } if (!core.romLoaded) { pause->setText("Pause"); pause->setDisabled(true); reset->setDisabled(true); stop->setDisabled(true); fpsLabel->setText("Not running"); } }); statusBarTimer->start(); auto fileMenu = menuBar()->addMenu("File"); auto open = fileMenu->addAction("Open"); open->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_O)); connect(open, &QAction::triggered, this, [&] { auto originPath = Options::GetRomsPath().empty() ? QDir::currentPath() : Options::GetRomsPath().c_str(); auto fileToLoad = QFileDialog::getOpenFileName(this, "Select a Nintendo 64 ROM", originPath, "N64 ROM (*.z64 *.n64 *.v64)") .toStdString(); if (!fileToLoad.empty()) LoadROM(fileToLoad); }); auto exit = fileMenu->addAction("Exit"); connect(exit, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, STOP); }); connect(exit, &QAction::triggered, this, &QMainWindow::close); auto emulationMenu = menuBar()->addMenu("Emulation"); auto settingsMenu = emulationMenu->addAction("Settings"); settingsWindow = new SettingsWindow(); connect(settingsMenu, &QAction::triggered, settingsWindow, &SettingsWindow::show); connect(settingsWindow->general, &GeneralSettings::romFolderSelected, this, [&] { populateRomsList(Options::GetRomsPath()); }); connect(settingsWindow->cpu, &CPUSettings::cpuTypeChanged, this, [&] { core.cpuType = Options::GetCpuType(); cpuTypeLabel->setText("Cached Interpreter"); idleSkipLabel->setVisible(Options::GetCpuType() == n64::CachedInterpreter); if (Options::GetCpuType() == n64::PlainInterpreter) cpuTypeLabel->setText("Interpreter"); }); connect(settingsWindow->cpu, &CPUSettings::idleSkipChanged, this, [&] { core.idleSkip = Options::GetIdleSkip(); idleSkipLabel->setVisible(Options::GetIdleSkip()); }); emulationMenu->addSeparator(); auto unlockFramerate = emulationMenu->addAction("Unlock framerate"); unlockFramerate->setCheckable(true); connect(unlockFramerate, &QAction::triggered, this, [&](bool checked) { unlockFrameratePressed = checked; }); pause->setDisabled(true); pause->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_P)); emulationMenu->addAction(pause); connect(pause, &QAction::triggered, this, [&] { if (!core.pause) Scheduler::GetInstance().EnqueueRelative(0, PAUSE); else core.TogglePause(); }); reset->setDisabled(true); reset->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_R)); emulationMenu->addAction(reset); connect(reset, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, RESET); }); stop->setDisabled(true); stop->setShortcut(QKeyCombination(Qt::CTRL, Qt::Key_X)); emulationMenu->addAction(stop); connect(stop, &QAction::triggered, this, [&] { Scheduler::GetInstance().EnqueueRelative(0, STOP); centralWidget->setCurrentWidget(currentHomeWidget); }); auto helpMenu = menuBar()->addMenu("Help"); auto about = helpMenu->addAction("About"); connect(about, &QAction::triggered, this, [&] { auto text = std::format("

Kaizen is a Nintendo 64 emulator that strives
" "to offer a friendly user experience and compatibility.
" "Kaizen is licensed under the BSD 3-clause license.
" "Nintendo 64 is a registered trademark of Nintendo Co., Ltd.


" "Kaizen {}{}", KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR); QMessageBox::about(this, "About", text.c_str()); }); show(); emuThread = QThread::create([&] { core.parallel.Init(vulkanWidget->wsiPlatform, vulkanWidget->windowInfo, vulkanWidget->qtVkInstanceFactory.get(), core.GetMem().GetRDRAMPtr()); while (!emuThread->isInterruptionRequested()) { if (!core.romLoaded || core.pause) continue; updateKeys(); updateAxis(); core.parallel.SetFramerateUnlocked(unlockFrameratePressed); auto timeStart = SDL_GetTicks(); core.Run(); core.parallel.UpdateScreen(); elapsed = SDL_GetTicks() - timeStart; } }); emuThread->start(); } KaizenGui::~KaizenGui() { cleanup(); } void KaizenGui::updateKeys() { auto &pif = core.mem->mmio.si.pif; if (gamepad) { pif.UpdateButton(0, n64::Controller::Z, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) == SDL_JOYSTICK_AXIS_MAX); pif.UpdateButton(0, n64::Controller::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH)); pif.UpdateButton(0, n64::Controller::B, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_WEST)); pif.UpdateButton(0, n64::Controller::LT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)); pif.UpdateButton(0, n64::Controller::RT, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)); pif.UpdateButton(0, n64::Controller::Start, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START)); pif.UpdateButton(0, n64::Controller::DUp, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP)); pif.UpdateButton(0, n64::Controller::DDown, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN)); pif.UpdateButton(0, n64::Controller::DLeft, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT)); pif.UpdateButton(0, n64::Controller::DRight, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT)); pif.UpdateButton(0, n64::Controller::CUp, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) == SDL_JOYSTICK_AXIS_MIN); pif.UpdateButton(0, n64::Controller::CDown, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY) == SDL_JOYSTICK_AXIS_MAX); pif.UpdateButton(0, n64::Controller::CLeft, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) == SDL_JOYSTICK_AXIS_MIN); pif.UpdateButton(0, n64::Controller::CRight, SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX) == SDL_JOYSTICK_AXIS_MAX); return; } pif.UpdateButton(0, n64::Controller::Z, pressedKeys.test(0)); pif.UpdateButton(0, n64::Controller::A, pressedKeys.test(1)); pif.UpdateButton(0, n64::Controller::B, pressedKeys.test(2)); pif.UpdateButton(0, n64::Controller::LT, pressedKeys.test(3)); pif.UpdateButton(0, n64::Controller::RT, pressedKeys.test(4)); pif.UpdateButton(0, n64::Controller::Start, pressedKeys.test(5)); pif.UpdateButton(0, n64::Controller::DUp, pressedKeys.test(6)); pif.UpdateButton(0, n64::Controller::DDown, pressedKeys.test(7)); pif.UpdateButton(0, n64::Controller::DLeft, pressedKeys.test(8)); pif.UpdateButton(0, n64::Controller::DRight, pressedKeys.test(9)); pif.UpdateButton(0, n64::Controller::CUp, pressedKeys.test(10)); pif.UpdateButton(0, n64::Controller::CDown, pressedKeys.test(11)); pif.UpdateButton(0, n64::Controller::CLeft, pressedKeys.test(12)); pif.UpdateButton(0, n64::Controller::CRight, pressedKeys.test(13)); } void KaizenGui::updateAxis() { auto &pif = core.mem->mmio.si.pif; if (gamepad) { float xclamped = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX); if (xclamped < 0) { xclamped /= static_cast(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 /= static_cast(std::abs(SDL_JOYSTICK_AXIS_MIN)); } else { yclamped /= SDL_JOYSTICK_AXIS_MAX; } yclamped *= 86; pif.UpdateAxis(0, n64::Controller::Axis::Y, static_cast(-yclamped)); pif.UpdateAxis(0, n64::Controller::Axis::X, static_cast(xclamped)); return; } s16 x = 0, y = 0; if (pressedKeys.test(14)) // up y += 86; if (pressedKeys.test(15)) // down y -= 86; if (pressedKeys.test(16)) // left x -= 86; if (pressedKeys.test(17)) // right x += 86; core.mem->mmio.si.pif.UpdateAxis(0, n64::Controller::X, x); core.mem->mmio.si.pif.UpdateAxis(0, n64::Controller::Y, y); } void KaizenGui::LoadROM(const std::string &path) noexcept { core.LoadROM(path); pause->setEnabled(true); reset->setEnabled(true); stop->setEnabled(true); centralWidget->setCurrentWidget(vulkanWidget); setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + core.mem->rom.gameNameDB).c_str()); } void KaizenGui::LoadTAS(const std::string &path) noexcept { core.LoadTAS(fs::path(path)); } void KaizenGui::cleanup() { SDL_Quit(); core.Stop(); emuThread->requestInterruption(); emuThread->quit(); delete emuThread; delete vulkanWidget; delete settingsWindow; settings.setValue("geometry", saveGeometry()); settings.setValue("windowState", saveState()); } void KaizenGui::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { auto file = event->mimeData()->urls()[0].toLocalFile().toStdString(); LoadROM(file); event->acceptProposedAction(); } } void KaizenGui::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) event->acceptProposedAction(); } void KaizenGui::keyPressEvent(QKeyEvent *event) { for (int i = 0; i < settingsWindow->input->mapping.size(); i++) { if (settingsWindow->input->mapping[i] == event->key()) pressedKeys.set(i); } } void KaizenGui::keyReleaseEvent(QKeyEvent *event) { for (int i = 0; i < settingsWindow->input->mapping.size(); i++) { if (settingsWindow->input->mapping[i] == event->key()) pressedKeys.reset(i); } } bool KaizenGui::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (!settingsWindow->hasFocus()) return keyPressEvent(keyEvent), true; } if (event->type() == QEvent::KeyRelease) { QKeyEvent *keyEvent = static_cast(event); if (!settingsWindow->hasFocus()) return keyReleaseEvent(keyEvent), true; } return QMainWindow::eventFilter(obj, event); }