Qt6 frontend #1
@@ -6,6 +6,7 @@ saves/
|
||||
.vscode/
|
||||
.zed/
|
||||
out/
|
||||
.qtcreator/
|
||||
*.toml
|
||||
*.ini
|
||||
*.z64
|
||||
|
||||
@@ -49,7 +49,6 @@ else()
|
||||
message(FATAL_ERROR "Git not found, please define KAIZEN_GIT_COMMIT_HASH manually.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/cmake/version.hpp.in ${CMAKE_CURRENT_LIST_DIR}/resources/version.hpp)
|
||||
|
||||
include_directories(
|
||||
@@ -171,7 +170,6 @@ qt_add_executable(kaizen
|
||||
src/utils/Options.cpp
|
||||
src/utils/File.cpp)
|
||||
|
||||
|
||||
if (WIN32)
|
||||
set(MIO_LIB mio::mio_full_winapi)
|
||||
else()
|
||||
@@ -179,10 +177,6 @@ else()
|
||||
endif()
|
||||
|
||||
target_link_libraries(kaizen PUBLIC Qt6::Core Qt6::Gui Qt6::Widgets SDL3::SDL3 SDL3::SDL3-static cflags::cflags ${MIO_LIB} parallel-rdp capstone backend)
|
||||
target_compile_definitions(kaizen PUBLIC SDL_MAIN_HANDLED)
|
||||
set_target_properties(kaizen PROPERTIES
|
||||
WIN32_EXECUTABLE ON
|
||||
MACOSX_BUNDLE ON)
|
||||
|
||||
if (SANITIZERS)
|
||||
message("UBSAN AND ASAN: ON")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2023 Simone Coco
|
||||
Copyright 2026 Iris Coco
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
|
||||
+11
-6
@@ -4,7 +4,6 @@
|
||||
#include <rdp_device.hpp>
|
||||
#include <resources/vert.spv.h>
|
||||
#include <resources/frag.spv.h>
|
||||
#include <KaizenGui.hpp>
|
||||
|
||||
using namespace Vulkan;
|
||||
using namespace RDP;
|
||||
@@ -23,7 +22,8 @@ Program *fullscreen_quad_program;
|
||||
|
||||
// Copied and modified from WSI::init_context_from_platform
|
||||
Util::IntrusivePtr<Context> InitVulkanContext(WSIPlatform *platform, const unsigned num_thread_indices,
|
||||
const Context::SystemHandles &system_handles) {
|
||||
const Context::SystemHandles &system_handles,
|
||||
Vulkan::InstanceFactory *instanceFactory) {
|
||||
VK_ASSERT(platform);
|
||||
const auto instance_ext = platform->get_instance_extensions();
|
||||
const auto device_ext = platform->get_device_extensions();
|
||||
@@ -32,6 +32,9 @@ Util::IntrusivePtr<Context> InitVulkanContext(WSIPlatform *platform, const unsig
|
||||
new_context->set_application_info(platform->get_application_info());
|
||||
new_context->set_num_thread_indices(num_thread_indices);
|
||||
new_context->set_system_handles(system_handles);
|
||||
if (instanceFactory) {
|
||||
new_context->set_instance_factory(instanceFactory);
|
||||
}
|
||||
|
||||
if (!new_context->init_instance(instance_ext.data(), instance_ext.size(),
|
||||
CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT)) {
|
||||
@@ -55,14 +58,15 @@ Util::IntrusivePtr<Context> InitVulkanContext(WSIPlatform *platform, const unsig
|
||||
}
|
||||
|
||||
void ParallelRDP::LoadWSIPlatform(const std::shared_ptr<WSIPlatform> &wsi_platform,
|
||||
const std::shared_ptr<WindowInfo> &newWindowInfo) {
|
||||
const std::shared_ptr<WindowInfo> &newWindowInfo,
|
||||
Vulkan::InstanceFactory *instanceFactory) {
|
||||
wsi = std::make_shared<WSI>();
|
||||
wsi->set_backbuffer_srgb(false);
|
||||
wsi->set_platform(wsi_platform.get());
|
||||
wsi->set_present_mode(PresentMode::SyncToVBlank);
|
||||
|
||||
if (constexpr Context::SystemHandles handles;
|
||||
!wsi->init_from_existing_context(InitVulkanContext(wsi_platform.get(), 1, handles))) {
|
||||
!wsi->init_from_existing_context(InitVulkanContext(wsi_platform.get(), 1, handles, instanceFactory))) {
|
||||
panic("Failed to initialize WSI: init_from_existing_context() failed");
|
||||
}
|
||||
|
||||
@@ -78,8 +82,9 @@ void ParallelRDP::LoadWSIPlatform(const std::shared_ptr<WSIPlatform> &wsi_platfo
|
||||
}
|
||||
|
||||
void ParallelRDP::Init(const std::shared_ptr<WSIPlatform> &wsiPlatform,
|
||||
const std::shared_ptr<WindowInfo> &newWindowInfo, const u8 *rdram) {
|
||||
LoadWSIPlatform(wsiPlatform, newWindowInfo);
|
||||
const std::shared_ptr<WindowInfo> &newWindowInfo, Vulkan::InstanceFactory *instanceFactory,
|
||||
const u8 *rdram) {
|
||||
LoadWSIPlatform(wsiPlatform, newWindowInfo, instanceFactory);
|
||||
|
||||
ResourceLayout vertLayout;
|
||||
ResourceLayout fragLayout;
|
||||
|
||||
+4
-2
@@ -15,7 +15,8 @@ class ParallelRDP {
|
||||
virtual ~WindowInfo() = default;
|
||||
};
|
||||
|
||||
void Init(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &, const u8 *);
|
||||
void Init(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &,
|
||||
Vulkan::InstanceFactory *, const u8 *);
|
||||
|
||||
template <bool>
|
||||
void UpdateScreen() const;
|
||||
@@ -29,7 +30,8 @@ class ParallelRDP {
|
||||
std::shared_ptr<WindowInfo> windowInfo;
|
||||
|
||||
private:
|
||||
void LoadWSIPlatform(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &);
|
||||
void LoadWSIPlatform(const std::shared_ptr<Vulkan::WSIPlatform> &, const std::shared_ptr<WindowInfo> &,
|
||||
Vulkan::InstanceFactory *);
|
||||
void DrawFullscreenTexturedQuad(Util::IntrusivePtr<Vulkan::Image>, Util::IntrusivePtr<Vulkan::CommandBuffer>) const;
|
||||
void UpdateScreen(Util::IntrusivePtr<Vulkan::Image>) const;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace n64 {
|
||||
Core::Core() : interpreter(*mem, regs) {
|
||||
const auto selectedCpu = Options::GetInstance().GetValue<std::string>("cpu", "type");
|
||||
const auto selectedCpu = Options::GetCpuType();
|
||||
if (selectedCpu == "interpreter") {
|
||||
cpuType = PlainInterpreter;
|
||||
} else if (selectedCpu == "cached_interpreter") {
|
||||
@@ -88,7 +88,7 @@ void Core::StepRSP(const u32 cpuCycles) {
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Run(const float volumeL, const float volumeR) {
|
||||
void Core::Run() {
|
||||
MMIO &mmio = mem->mmio;
|
||||
|
||||
bool broken = false;
|
||||
@@ -116,7 +116,7 @@ void Core::Run(const float volumeL, const float volumeR) {
|
||||
mmio.mi.InterruptRaise(MI::Interrupt::VI);
|
||||
}
|
||||
|
||||
mmio.ai.Step(frameCycles, volumeL, volumeR);
|
||||
mmio.ai.Step(frameCycles);
|
||||
Scheduler::GetInstance().Tick(frameCycles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ struct Core {
|
||||
void Reset();
|
||||
void LoadROM(const std::string &);
|
||||
void LoadTAS(const fs::path &) const;
|
||||
void Run(float volumeL, float volumeR);
|
||||
void Run();
|
||||
void TogglePause() { pause = !pause; }
|
||||
inline void ToggleBreakpoint(s64 addr) {
|
||||
if (breakpoints.contains(addr)) {
|
||||
|
||||
@@ -31,7 +31,7 @@ void Mem::Reset() {
|
||||
void Mem::LoadSRAM(SaveType save_type, fs::path path) {
|
||||
if (save_type == SAVE_SRAM_256k) {
|
||||
std::error_code err;
|
||||
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
||||
std::string savePath = Options::GetSavesPath();
|
||||
if (!savePath.empty()) {
|
||||
path = savePath / path.filename();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ void Flash::Reset() {
|
||||
void Flash::Load(SaveType saveType, const std::string &path) {
|
||||
if (saveType == SAVE_FLASH_1m) {
|
||||
fs::path flashPath_ = path;
|
||||
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
||||
std::string savePath = Options::GetSavesPath();
|
||||
if (!savePath.empty()) {
|
||||
flashPath_ = savePath / flashPath_.filename();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Core.hpp>
|
||||
#include <log.hpp>
|
||||
#include <Options.hpp>
|
||||
|
||||
namespace n64 {
|
||||
AI::AI() { Reset(); }
|
||||
@@ -35,7 +36,7 @@ auto AI::Read(const u32 addr) const -> u32 {
|
||||
}
|
||||
|
||||
void AI::Write(const u32 addr, const u32 val) {
|
||||
n64::Mem& mem = n64::Core::GetMem();
|
||||
n64::Mem &mem = n64::Core::GetMem();
|
||||
switch (addr) {
|
||||
case 0x04500000:
|
||||
if (dmaCount < 2) {
|
||||
@@ -79,8 +80,8 @@ void AI::Write(const u32 addr, const u32 val) {
|
||||
}
|
||||
}
|
||||
|
||||
void AI::Step(const u32 cpuCycles, const float volumeL, const float volumeR) {
|
||||
n64::Mem& mem = n64::Core::GetMem();
|
||||
void AI::Step(const u32 cpuCycles) {
|
||||
n64::Mem &mem = n64::Core::GetMem();
|
||||
cycles += cpuCycles;
|
||||
while (cycles > dac.period) {
|
||||
if (dmaCount == 0) {
|
||||
@@ -94,8 +95,10 @@ void AI::Step(const u32 cpuCycles, const float volumeL, const float volumeR) {
|
||||
const s16 l = s16(data >> 16);
|
||||
const s16 r = s16(data);
|
||||
|
||||
if (volumeR > 0 && volumeL > 0) {
|
||||
device.PushSample((float)l / std::numeric_limits<s16>::max(), volumeL, (float)r / std::numeric_limits<s16>::max(), volumeR);
|
||||
auto volume = Options::GetVolume();
|
||||
if (volume > 0) {
|
||||
device.PushSample((float)l / std::numeric_limits<s16>::max(), volume,
|
||||
(float)r / std::numeric_limits<s16>::max(), volume);
|
||||
}
|
||||
|
||||
const u32 addrLo = dmaAddr[0] + 4 & 0x1FFF;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <common.hpp>
|
||||
#include <core/mmio/Audio.hpp>
|
||||
#include <array>
|
||||
|
||||
namespace n64 {
|
||||
struct AI {
|
||||
@@ -8,7 +9,7 @@ struct AI {
|
||||
void Reset();
|
||||
auto Read(u32) const -> u32;
|
||||
void Write(u32, u32);
|
||||
void Step(u32, float, float);
|
||||
void Step(u32);
|
||||
bool dmaEnable{};
|
||||
bool dmaAddrCarry{};
|
||||
u8 bitrate{};
|
||||
|
||||
@@ -36,7 +36,7 @@ void PIF::Reset() {
|
||||
void PIF::MaybeLoadMempak() {
|
||||
if (!mempakOpen) {
|
||||
fs::path mempakPath_ = mempakPath;
|
||||
fs::path savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
||||
fs::path savePath = Options::GetSavesPath();
|
||||
if (!savePath.empty()) {
|
||||
if (!fs::exists(savePath))
|
||||
fs::create_directory(savePath);
|
||||
@@ -92,7 +92,7 @@ FORCE_INLINE size_t GetSaveSize(SaveType saveType) {
|
||||
void PIF::LoadEeprom(const SaveType saveType, const std::string &path) {
|
||||
if (saveType == SAVE_EEPROM_16k || saveType == SAVE_EEPROM_4k) {
|
||||
fs::path eepromPath_ = path;
|
||||
std::string savePath = Options::GetInstance().GetValue<std::string>("general", "savePath");
|
||||
std::string savePath = Options::GetSavesPath();
|
||||
if (!savePath.empty()) {
|
||||
eepromPath_ = savePath / eepromPath_.filename();
|
||||
}
|
||||
|
||||
+10
-38
@@ -1,53 +1,25 @@
|
||||
#include <Core.hpp>
|
||||
#include <EmuThread.hpp>
|
||||
#include <KaizenGui.hpp>
|
||||
#include <Core.hpp>
|
||||
|
||||
EmuThread::EmuThread(double &fps, SettingsWindow &settings) noexcept : settings(settings), fps(fps) {}
|
||||
EmuThread::EmuThread() noexcept {}
|
||||
|
||||
void EmuThread::run() const noexcept {
|
||||
n64::Core& core = n64::Core::GetInstance();
|
||||
if(!core.romLoaded) return;
|
||||
|
||||
auto lastSample = std::chrono::high_resolution_clock::now();
|
||||
auto avgFps = 16.667;
|
||||
auto sampledFps = 0;
|
||||
static bool oneSecondPassed = false;
|
||||
|
||||
fps = 1000.0 / avgFps;
|
||||
|
||||
const auto startFrameTime = std::chrono::high_resolution_clock::now();
|
||||
if (!core.pause) {
|
||||
core.Run(settings.getVolumeL(), settings.getVolumeR());
|
||||
}
|
||||
|
||||
const auto endFrameTime = std::chrono::high_resolution_clock::now();
|
||||
using namespace std::chrono_literals;
|
||||
const auto frameTimeMs = std::chrono::duration<double>(endFrameTime - startFrameTime) / 1ms;
|
||||
avgFps += frameTimeMs;
|
||||
|
||||
sampledFps++;
|
||||
|
||||
if (const auto elapsedSinceLastSample = std::chrono::duration<double>(endFrameTime - lastSample) / 1s;
|
||||
elapsedSinceLastSample >= 1.0) {
|
||||
if (!oneSecondPassed) {
|
||||
oneSecondPassed = true;
|
||||
n64::Core &core = n64::Core::GetInstance();
|
||||
if (!core.romLoaded)
|
||||
return;
|
||||
}
|
||||
avgFps /= sampledFps;
|
||||
fps = 1000.0 / avgFps;
|
||||
|
||||
if (!core.pause) {
|
||||
core.Run();
|
||||
}
|
||||
}
|
||||
|
||||
void EmuThread::TogglePause() const noexcept {
|
||||
n64::Core::GetInstance().TogglePause();
|
||||
}
|
||||
void EmuThread::TogglePause() const noexcept { n64::Core::GetInstance().TogglePause(); }
|
||||
|
||||
void EmuThread::Reset() const noexcept {
|
||||
n64::Core::GetInstance().Reset();
|
||||
}
|
||||
void EmuThread::Reset() const noexcept { n64::Core::GetInstance().Reset(); }
|
||||
|
||||
void EmuThread::Stop() const noexcept {
|
||||
n64::Core& core = n64::Core::GetInstance();
|
||||
n64::Core &core = n64::Core::GetInstance();
|
||||
core.Stop();
|
||||
core.rom = {};
|
||||
}
|
||||
|
||||
+31
-338
@@ -2,360 +2,53 @@
|
||||
#include <backend/Core.hpp>
|
||||
#include <QMenuBar>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <resources/gamecontrollerdb.h>
|
||||
|
||||
KaizenGui::KaizenGui() noexcept : vulkanWidget(windowHandle()) {
|
||||
KaizenGui::KaizenGui() noexcept {
|
||||
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
||||
|
||||
hide();
|
||||
vulkanWidget = new RenderWidget();
|
||||
|
||||
setWindowTitle("Kaizen " KAIZEN_VERSION_STR);
|
||||
setMinimumSize(640, 480);
|
||||
setCentralWidget(vulkanWidget);
|
||||
|
||||
auto fileMenu = menuBar()->addMenu("File");
|
||||
auto open = fileMenu->addMenu("Open");
|
||||
auto exit = fileMenu->addMenu("Exit");
|
||||
auto open = fileMenu->addAction("Open");
|
||||
auto exit = fileMenu->addAction("Exit");
|
||||
auto emulationMenu = menuBar()->addMenu("Emulation");
|
||||
auto settingsMenu = emulationMenu->addMenu("Settings");
|
||||
connect(settingsMenu, &QMenu::triggered, this, [&] { settingsWindow.show(); });
|
||||
auto settingsMenu = emulationMenu->addAction("Settings");
|
||||
settingsWindow = new SettingsWindow();
|
||||
connect(settingsMenu, &QAction::triggered, settingsWindow, &SettingsWindow::show);
|
||||
|
||||
emulationMenu->addSeparator();
|
||||
auto pause = emulationMenu->addMenu("Pause");
|
||||
auto reset = emulationMenu->addMenu("Reset");
|
||||
auto stop = emulationMenu->addMenu("Stop");
|
||||
auto pause = emulationMenu->addAction("Pause");
|
||||
auto reset = emulationMenu->addAction("Reset");
|
||||
auto stop = emulationMenu->addAction("Stop");
|
||||
auto helpMenu = menuBar()->addMenu("Help");
|
||||
auto about = helpMenu->addAction("About");
|
||||
connect(about, &QAction::triggered, this, [&] {
|
||||
auto text = std::format("<p>Kaizen is a Nintendo 64 emulator that strives<br>"
|
||||
"to offer a friendly user experience and compatibility.<br>"
|
||||
"Kaizen is licensed under the BSD 3-clause license.<br>"
|
||||
"Nintendo 64 is a registered trademark of Nintendo Co., Ltd.</p><hr>"
|
||||
"Kaizen {}{}",
|
||||
KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
|
||||
|
||||
QMessageBox::about(this, "About", text.c_str());
|
||||
});
|
||||
show();
|
||||
}
|
||||
|
||||
KaizenGui::~KaizenGui() { SDL_Quit(); }
|
||||
|
||||
void KaizenGui::QueryDevices(const SDL_Event &event) {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
if (!gamepad) {
|
||||
const auto index = event.gdevice.which;
|
||||
|
||||
gamepad = SDL_OpenGamepad(index);
|
||||
info("Found controller!");
|
||||
info("Name: {}", SDL_GetGamepadName(gamepad));
|
||||
info("Vendor: {}", SDL_GetGamepadVendor(gamepad));
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
if (gamepad)
|
||||
SDL_CloseGamepad(gamepad);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
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 /= static_cast<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 /= static_cast<float>(std::abs(SDL_JOYSTICK_AXIS_MIN));
|
||||
} else {
|
||||
yclamped /= SDL_JOYSTICK_AXIS_MAX;
|
||||
}
|
||||
|
||||
yclamped *= 86;
|
||||
|
||||
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:
|
||||
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:
|
||||
{
|
||||
const 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();
|
||||
}
|
||||
}
|
||||
|
||||
if (gamepad)
|
||||
break;
|
||||
|
||||
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]);
|
||||
|
||||
float x = 0, y = 0;
|
||||
|
||||
if (keys[SDL_SCANCODE_UP])
|
||||
y = 86;
|
||||
if (keys[SDL_SCANCODE_DOWN])
|
||||
y = -86;
|
||||
if (keys[SDL_SCANCODE_LEFT])
|
||||
x = -86;
|
||||
if (keys[SDL_SCANCODE_RIGHT])
|
||||
x = 86;
|
||||
|
||||
pif.UpdateAxis(0, n64::Controller::Axis::X, x);
|
||||
pif.UpdateAxis(0, n64::Controller::Axis::Y, y);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void KaizenGui::RenderUI() {
|
||||
n64::Core &core = n64::Core::GetInstance();
|
||||
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();
|
||||
|
||||
const 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.");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Kaizen %s%s", KAIZEN_USE_HASH ? "dev build " : "", KAIZEN_VERSION_STR);
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("OK")) {
|
||||
aboutOpen = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (ImGui::BeginMainStatusBar()) {
|
||||
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
|
||||
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).filename().string().c_str());
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::Render();
|
||||
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
||||
ImGui::UpdatePlatformWindows();
|
||||
ImGui::RenderPlatformWindowsDefault();
|
||||
}
|
||||
|
||||
if (fileDialogOpen) {
|
||||
fileDialogOpen = false;
|
||||
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) {
|
||||
auto kaizen = static_cast<KaizenGui *>(userdata);
|
||||
|
||||
if (!filelist) {
|
||||
panic("An error occured: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!*filelist) {
|
||||
warn("The user did not select any file.");
|
||||
warn("Most likely, the dialog was canceled.");
|
||||
return;
|
||||
}
|
||||
|
||||
kaizen->fileToLoad = *filelist;
|
||||
kaizen->shouldDisplaySpinner = true;
|
||||
std::thread fileWorker(&KaizenGui::FileWorker, kaizen);
|
||||
fileWorker.detach();
|
||||
},
|
||||
this, window.getHandle(), filters, 3, nullptr, false);
|
||||
}
|
||||
|
||||
if (minimized)
|
||||
return;
|
||||
|
||||
if (core.romLoaded) {
|
||||
core.parallel.UpdateScreen<true>();
|
||||
return;
|
||||
}
|
||||
|
||||
core.parallel.UpdateScreen<false>();
|
||||
}
|
||||
|
||||
void KaizenGui::LoadROM(const std::string &path) noexcept {
|
||||
n64::Core &core = n64::Core::GetInstance();
|
||||
core.LoadROM(path);
|
||||
const auto gameNameDB = n64::Core::GetMem().rom.gameNameDB;
|
||||
SDL_SetWindowTitle(window.getHandle(), ("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str());
|
||||
}
|
||||
|
||||
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;
|
||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||
minimized = true;
|
||||
break;
|
||||
case SDL_EVENT_WINDOW_RESTORED:
|
||||
minimized = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
QueryDevices(e);
|
||||
HandleInput(e);
|
||||
}
|
||||
|
||||
SDL_GetWindowSize(window.getHandle(), &width, &height);
|
||||
|
||||
emuThread.run();
|
||||
RenderUI();
|
||||
}
|
||||
setWindowTitle(("Kaizen " KAIZEN_VERSION_STR " - " + gameNameDB).c_str());
|
||||
}
|
||||
|
||||
void KaizenGui::LoadTAS(const std::string &path) noexcept { n64::Core::GetInstance().LoadTAS(fs::path(path)); }
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
#pragma once
|
||||
#include <QMainWindow>
|
||||
#include <RenderWidget.hpp>
|
||||
#include <EmuThread.hpp>
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
#include <QMainWindow>
|
||||
|
||||
class KaizenGui final : QMainWindow {
|
||||
public:
|
||||
explicit KaizenGui() noexcept;
|
||||
~KaizenGui();
|
||||
|
||||
double fpsCounter = -1.0;
|
||||
bool fastForward = false;
|
||||
bool unlockFramerate = false;
|
||||
bool minimized = false;
|
||||
|
||||
SettingsWindow settingsWindow;
|
||||
RenderWidget vulkanWidget;
|
||||
SettingsWindow *settingsWindow;
|
||||
RenderWidget *vulkanWidget;
|
||||
EmuThread emuThread;
|
||||
|
||||
SDL_Gamepad *gamepad = nullptr;
|
||||
|
||||
void run();
|
||||
static void LoadTAS(const std::string &path) noexcept;
|
||||
void LoadROM(const std::string &path) noexcept;
|
||||
|
||||
@@ -31,9 +29,6 @@ class KaizenGui final : QMainWindow {
|
||||
bool quit = false;
|
||||
bool shouldDisplaySpinner = false;
|
||||
std::string fileToLoad = "";
|
||||
void RenderUI();
|
||||
void HandleInput(const SDL_Event &event);
|
||||
void QueryDevices(const SDL_Event &event);
|
||||
|
||||
void FileWorker() {
|
||||
if (fileToLoad.empty())
|
||||
|
||||
@@ -1,10 +1,49 @@
|
||||
#include <Core.hpp>
|
||||
#include <KaizenGui.hpp>
|
||||
#include <RenderWidget.hpp>
|
||||
#include <Core.hpp>
|
||||
#include <QGuiApplication>
|
||||
|
||||
RenderWidget::RenderWidget(QWindow *window) {
|
||||
wsiPlatform = std::make_shared<QtWSIPlatform>(window);
|
||||
windowInfo = std::make_shared<QtPrdpWindowInfo>(window);
|
||||
enum class CompositorCategory { Windows, MacOS, XCB, Wayland };
|
||||
|
||||
static CompositorCategory GetOSCompositorCategory() {
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("windows"))
|
||||
return CompositorCategory::Windows;
|
||||
if (platform_name == QStringLiteral("xcb"))
|
||||
return CompositorCategory::XCB;
|
||||
if (platform_name == QStringLiteral("wayland") || platform_name == QStringLiteral("wayland-egl"))
|
||||
return CompositorCategory::Wayland;
|
||||
if (platform_name == QStringLiteral("cocoa") || platform_name == QStringLiteral("ios"))
|
||||
return CompositorCategory::MacOS;
|
||||
|
||||
warn("Unknown Qt platform!");
|
||||
return CompositorCategory::Windows;
|
||||
}
|
||||
|
||||
RenderWidget::RenderWidget() {
|
||||
setAttribute(Qt::WA_NativeWindow);
|
||||
setAttribute(Qt::WA_PaintOnScreen);
|
||||
if (GetOSCompositorCategory() == CompositorCategory::Wayland) {
|
||||
setAttribute(Qt::WA_DontCreateNativeAncestors);
|
||||
}
|
||||
|
||||
if (GetOSCompositorCategory() == CompositorCategory::MacOS) {
|
||||
windowHandle()->setSurfaceType(QWindow::MetalSurface);
|
||||
} else {
|
||||
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
|
||||
}
|
||||
|
||||
if (!Vulkan::Context::init_loader(nullptr)) {
|
||||
panic("Could not initialize Vulkan ICD");
|
||||
}
|
||||
|
||||
qtVkInstanceFactory = std::make_unique<QtInstanceFactory>();
|
||||
|
||||
windowHandle()->setVulkanInstance(&qtVkInstanceFactory->handle);
|
||||
windowHandle()->create();
|
||||
|
||||
wsiPlatform = std::make_shared<QtWSIPlatform>(windowHandle());
|
||||
windowInfo = std::make_shared<QtPrdpWindowInfo>(windowHandle());
|
||||
n64::Core &core = n64::Core::GetInstance();
|
||||
core.parallel.Init(wsiPlatform, windowInfo, core.GetMem().GetRDRAMPtr());
|
||||
core.parallel.Init(wsiPlatform, windowInfo, qtVkInstanceFactory.get(), core.GetMem().GetRDRAMPtr());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#undef signals
|
||||
#include <ParallelRDPWrapper.hpp>
|
||||
#include <QVulkanWindow>
|
||||
#include <QMainWindow>
|
||||
@@ -8,12 +9,47 @@ struct InputSettings;
|
||||
namespace n64 {
|
||||
struct Core;
|
||||
}
|
||||
class QtPrdpWindowInfo;
|
||||
class QtWSIPlatform;
|
||||
|
||||
struct QtInstanceFactory : Vulkan::InstanceFactory {
|
||||
VkInstance create_instance(const VkInstanceCreateInfo *info) override {
|
||||
handle.setApiVersion({1, 3, 0});
|
||||
QByteArrayList exts;
|
||||
for (int i = 0; i < info->enabledExtensionCount; i++) {
|
||||
exts.push_back(QByteArray::fromStdString(info->ppEnabledExtensionNames[i]));
|
||||
}
|
||||
QByteArrayList layers;
|
||||
for (int i = 0; i < info->enabledLayerCount; i++) {
|
||||
layers.push_back(QByteArray::fromStdString(info->ppEnabledLayerNames[i]));
|
||||
}
|
||||
handle.setExtensions(exts);
|
||||
handle.setLayers(layers);
|
||||
handle.create();
|
||||
|
||||
return handle.vkInstance();
|
||||
}
|
||||
|
||||
QVulkanInstance handle;
|
||||
};
|
||||
|
||||
class RenderWidget final : public QWidget {
|
||||
QVulkanInstance inst;
|
||||
|
||||
public:
|
||||
explicit RenderWidget();
|
||||
|
||||
std::unique_ptr<QtInstanceFactory> qtVkInstanceFactory;
|
||||
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
|
||||
std::shared_ptr<QtWSIPlatform> wsiPlatform;
|
||||
};
|
||||
|
||||
|
||||
class QtPrdpWindowInfo final : public ParallelRDP::WindowInfo {
|
||||
public:
|
||||
explicit QtPrdpWindowInfo(QWindow *window) : window(window) {}
|
||||
CoordinatePair get_window_size() override {
|
||||
return CoordinatePair{static_cast<float>(window->size().width()), static_cast<float>(window->size().height())};
|
||||
return CoordinatePair{static_cast<float>(window->width()), static_cast<float>(window->height())};
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -27,12 +63,11 @@ class QtWSIPlatform final : public Vulkan::WSIPlatform {
|
||||
|
||||
std::vector<const char *> get_instance_extensions() override {
|
||||
auto vec = std::vector<const char *>();
|
||||
u32 extCount;
|
||||
auto extensions = window->vulkanInstance()->extensions();
|
||||
|
||||
vec.resize(extensions.size());
|
||||
|
||||
for (u32 i = 0; i < extCount; i++) {
|
||||
for (u32 i = 0; i < extensions.size(); i++) {
|
||||
vec[i] = extensions[i];
|
||||
}
|
||||
|
||||
@@ -40,7 +75,7 @@ class QtWSIPlatform final : public Vulkan::WSIPlatform {
|
||||
}
|
||||
|
||||
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice pDevice) override {
|
||||
return (surface = QVulkanInstance::surfaceForWindow(window));
|
||||
return QVulkanInstance::surfaceForWindow(window);
|
||||
}
|
||||
|
||||
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override {}
|
||||
@@ -61,16 +96,7 @@ class QtWSIPlatform final : public Vulkan::WSIPlatform {
|
||||
VkApplicationInfo appInfo{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_3};
|
||||
|
||||
QWindow *window{};
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
private:
|
||||
bool gamepadConnected = false;
|
||||
};
|
||||
|
||||
class RenderWidget final : QVulkanWindow {
|
||||
public:
|
||||
explicit RenderWidget(QWindow *);
|
||||
|
||||
std::shared_ptr<ParallelRDP::WindowInfo> windowInfo;
|
||||
std::shared_ptr<QtWSIPlatform> wsiPlatform;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <GeneralSettings.hpp>
|
||||
#include <Options.hpp>
|
||||
#include <log.hpp>
|
||||
#include <QSettings>
|
||||
#include <QFileDialog>
|
||||
#include <QCoreApplication>
|
||||
#include <log.hpp>
|
||||
|
||||
GeneralSettings::GeneralSettings() {
|
||||
QSettings settings;
|
||||
@@ -18,10 +18,10 @@ GeneralSettings::GeneralSettings() {
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
selectedFolderLabel->setText(dir);
|
||||
Options::SetSavesPath(dir.toStdString());
|
||||
settings.setValue("general/saves_path", dir);
|
||||
settings.setValue("general/savesPath", dir);
|
||||
});
|
||||
|
||||
h = new QHBoxLayout(this);
|
||||
h = new QHBoxLayout();
|
||||
|
||||
h->addWidget(description);
|
||||
h->addWidget(selectedFolderLabel);
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
|
||||
SettingsWindow::SettingsWindow() {
|
||||
hide();
|
||||
QSettings settings;
|
||||
|
||||
general = new GeneralSettings();
|
||||
cpu = new CPUSettings();
|
||||
audio = new AudioSettings();
|
||||
categories = new QTabWidget(this);
|
||||
categories->addTab(&general, "General");
|
||||
categories->addTab(&cpu, "MIPS VR4300i");
|
||||
categories->addTab(&audio, "Audio");
|
||||
categories->addTab(general, "General");
|
||||
categories->addTab(cpu, "MIPS VR4300i");
|
||||
categories->addTab(audio, "Audio");
|
||||
|
||||
v = new QVBoxLayout(this);
|
||||
v->addWidget(categories);
|
||||
|
||||
@@ -7,14 +7,13 @@
|
||||
#include <AudioSettings.hpp>
|
||||
#include <CPUSettings.hpp>
|
||||
|
||||
class SettingsWindow final : QWidget {
|
||||
class SettingsWindow final : public QWidget {
|
||||
QTabWidget *categories;
|
||||
GeneralSettings general;
|
||||
AudioSettings audio;
|
||||
CPUSettings cpu;
|
||||
GeneralSettings *general;
|
||||
AudioSettings *audio;
|
||||
CPUSettings *cpu;
|
||||
QVBoxLayout *v;
|
||||
|
||||
public:
|
||||
SettingsWindow();
|
||||
void show() { QWidget::show(); }
|
||||
};
|
||||
|
||||
+5
-14
@@ -1,23 +1,14 @@
|
||||
#include <QCoreApplication>
|
||||
#include <KaizenGui.hpp>
|
||||
#include <cflags.hpp>
|
||||
#include <QCoreApplication>
|
||||
#include <QApplication>
|
||||
|
||||
int main(const int argc, char **argv) {
|
||||
int main(int argc, char **argv) {
|
||||
QCoreApplication::setOrganizationName("kaizen");
|
||||
QCoreApplication::setOrganizationDomain("irco.sh");
|
||||
QCoreApplication::setApplicationName("Kaizen");
|
||||
QApplication app(argc, argv);
|
||||
|
||||
KaizenGui kaizenGui;
|
||||
cflags::cflags flags;
|
||||
flags.add_string_callback(
|
||||
'\0', "rom", [&kaizenGui](const std::string &v) { kaizenGui.LoadROM(v); }, "Rom to launch from command-line");
|
||||
flags.add_string_callback(
|
||||
'\0', "movie", [](const std::string &v) { KaizenGui::LoadTAS(v); }, "Mupen Movie to replay");
|
||||
|
||||
if (!flags.parse(argc, argv))
|
||||
return -1;
|
||||
|
||||
kaizenGui.run();
|
||||
|
||||
return 0;
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user