#include "imgui_layer.h" #include #include #include #include #include #ifdef __EMSCRIPTEN__ #include #endif #include "../third_party/imgui/imgui.h" #include "../third_party/imgui/backends/imgui_impl_sdl2.h" #include "../third_party/imgui/backends/imgui_impl_sdlrenderer2.h" #include "../psx/log.h" static bool g_imgui_initialized = false; static SDL_Window* g_window = nullptr; static SDL_Renderer* g_renderer = nullptr; extern "C" PSXE_API void imgui_layer_init(SDL_Window* window, SDL_Renderer* renderer) { if (!window || !renderer) return; if (g_imgui_initialized) { if (window == g_window && renderer == g_renderer) return; imgui_layer_shutdown(); } IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.IniFilename = nullptr; // avoid disk writes ImGui::StyleColorsDark(); g_window = window; g_renderer = renderer; ImGui_ImplSDL2_InitForSDLRenderer(g_window, g_renderer); ImGui_ImplSDLRenderer2_Init(g_renderer); g_imgui_initialized = true; } extern "C" PSXE_API void imgui_layer_shutdown(void) { if (!g_imgui_initialized) return; ImGui_ImplSDLRenderer2_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); g_window = nullptr; g_renderer = nullptr; g_imgui_initialized = false; } extern "C" PSXE_API void imgui_layer_handle_event(const SDL_Event* event) { if (!g_imgui_initialized || !event) return; ImGui_ImplSDL2_ProcessEvent(event); } extern "C" PSXE_API void imgui_layer_new_frame(void) { if (!g_imgui_initialized) return; ImGui_ImplSDLRenderer2_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); } extern "C" PSXE_API void imgui_layer_render_overlay(const char* os_name, float fps, float frame_ms, int dropped_frames, int paused, int tex_w, int tex_h, int out_w, int out_h) { if (!g_imgui_initialized) return; const ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground; ImGui::SetNextWindowPos(ImVec2(10.0f, 10.0f), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(240.0f, 115.0f), ImGuiCond_Always); ImGui::Begin("Overlay", nullptr, flags); ImGui::Text("FPS: %.1f", fps); ImGui::Text("Frame: %.2f ms", frame_ms); ImGui::Text("Dropped: %d", dropped_frames); ImGui::Text("Paused: %s", paused ? "yes" : "no"); ImGui::Text("Tex: %dx%d Out: %dx%d", tex_w, tex_h, out_w, out_h); ImGui::Text("OS : %s", os_name ? os_name : "unknown"); ImGui::End(); } extern "C" PSXE_API void imgui_layer_flush(void) { if (!g_imgui_initialized) return; ImGui::Render(); ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), g_renderer); } #if defined(IMGUI_FRONTEND) && !defined(__DLL_BUILD) && !defined(IOS_TARGET) && !defined(__ANDROID__) extern "C" { #include "config.h" PSXE_API int psxe_run_configured(psxe_config_t* cfg, void* external_window, void* external_renderer); } #include #include #include #include #include #include static void sync_buffer(std::array& buf, const std::string& value) { buf.fill(0); size_t copy_len = std::min(value.size(), buf.size() - 1); memcpy(buf.data(), value.c_str(), copy_len); buf[copy_len] = '\0'; } static void center_next_window(const ImVec2& size) { ImGuiIO& io = ImGui::GetIO(); ImVec2 center = ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f); ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(size, ImGuiCond_FirstUseEver); } static std::string read_readme() { std::ifstream file("README.md"); if (!file.is_open()) return std::string(); std::ostringstream ss; ss << file.rdbuf(); return ss.str(); } static int map_upscale_index(int height) { switch (height) { case 720: return 1; case 1080: return 2; case 1440: return 3; case 2160: return 4; default: return 0; } } static int upscale_value_from_index(int idx) { switch (idx) { case 1: return 720; case 2: return 1080; case 3: return 1440; case 4: return 2160; default: return 480; } } static std::string display_aspect_to_string(int aspect) { switch (aspect) { case 1: return "square"; case 2: return "wide16x9"; default: return "classic"; } } static int display_aspect_from_string(const std::string& value) { if (value == "square") return 1; if (value == "wide16x9") return 2; return 0; } static std::string upscale_label_from_height(int height) { switch (height) { case 720: return "720p"; case 1080: return "1080p"; case 1440: return "1440p"; case 2160: return "2160p"; default: return "480p"; } } #ifdef __EMSCRIPTEN__ static std::string g_wasm_pending_bios; static std::string g_wasm_pending_cd; extern "C" void psxe_wasm_on_file(const char* path, int is_bios) { if (!path) return; if (is_bios) { g_wasm_pending_bios = path; } else { g_wasm_pending_cd = path; } } extern "C" void psxe_wasm_pick_file(int is_bios) { EM_ASM({ const isBios = $0 === 1; const accept = isBios ? '.bin' : '.cue,.bin,.iso,.img'; const input = document.createElement('input'); input.type = 'file'; input.accept = accept; input.style.display = 'none'; document.body.appendChild(input); input.addEventListener('change', async function() { const file = input.files[0]; if (!file) { document.body.removeChild(input); return; } const data = new Uint8Array(await file.arrayBuffer()); const mountPath = isBios ? '/bios.bin' : '/cdrom.bin'; FS.writeFile(mountPath, data); Module.ccall('psxe_wasm_on_file', null, ['string','number'], [mountPath, isBios ? 1 : 0]); document.body.removeChild(input); }); input.click(); }, is_bios); } #endif static bool write_settings_file( const std::string& path, const std::string& bios_search, const std::string& model, const std::string& bios_override, const std::string& region, bool texture_scale_mode, bool debug_panel, bool stretch_mode, int display_aspect, int upscale_height ) { std::ofstream out(path, std::ios::trunc); if (!out.is_open()) { log_error("Failed to write settings file at %s", path.c_str()); return false; } out << "# Settings file generated by ARMSX frontend\n\n" << "psxe_version = \"" << STR(REP_VERSION) << "\"\n\n" << "[bios]\n" << " search_path = \"" << bios_search << "\"\n" << " preferred_model = \"" << model << "\"\n" << " override_file = \"" << bios_override << "\"\n\n" << "[console]\n" << " region = \"" << region << "\"\n\n" << "[video]\n" << " texture_scale_mode = " << (texture_scale_mode ? "true" : "false") << "\n" << " debug_panel = " << (debug_panel ? "true" : "false") << "\n" << " stretch_mode = " << (stretch_mode ? "true" : "false") << "\n" << " display_aspect = \"" << display_aspect_to_string(display_aspect) << "\"\n" << " wide_upscale = \"" << upscale_label_from_height(upscale_height) << "\"\n"; return true; } struct FilePickerState { bool open = false; std::string cwd; std::string selection; }; static std::string get_current_directory() { char buf[PATH_MAX]; if (getcwd(buf, sizeof(buf))) return std::string(buf); return std::string("."); } static bool is_directory(const std::string& path) { struct stat st; if (stat(path.c_str(), &st) == 0) return S_ISDIR(st.st_mode); return false; } static bool list_directory(const std::string& cwd, std::vector>& entries) { DIR* dir = opendir(cwd.c_str()); if (!dir) return false; struct dirent* dent; while ((dent = readdir(dir)) != NULL) { std::string name = dent->d_name; if (name == ".") continue; // include .. for navigation std::string full = cwd + "/" + name; bool dir_flag = (dent->d_type == DT_DIR) || ((dent->d_type == DT_UNKNOWN) && is_directory(full)); entries.emplace_back(full, dir_flag); } closedir(dir); std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { if (a.second != b.second) return a.second && !b.second; return a.first < b.first; }); return true; } static bool file_picker_popup(const char* popup_id, FilePickerState& state, std::string& target_path) { if (state.open) ImGui::OpenPopup(popup_id); bool changed = false; ImGui::SetNextWindowSize(ImVec2(420.0f, 320.0f), ImGuiCond_Appearing); if (ImGui::BeginPopupModal(popup_id, nullptr, ImGuiWindowFlags_NoResize)) { if (state.cwd.empty()) state.cwd = get_current_directory(); ImGui::TextWrapped("Current folder:\n%s", state.cwd.c_str()); if (ImGui::Button("Up")) { size_t pos = state.cwd.find_last_of("/\\"); if (pos != std::string::npos) { state.cwd = state.cwd.substr(0, pos ? pos : 1); state.selection.clear(); } else { state.cwd = "/"; } } ImGui::SameLine(); if (ImGui::Button("Refresh")) { // simple redraw } ImGui::BeginChild("browser", ImVec2(420, 260), true); std::vector> entries; if (!list_directory(state.cwd, entries)) { log_error("File picker failed to list dir %s", state.cwd.c_str()); } else { for (const auto& entry : entries) { const std::string& full = entry.first; std::string label; if (full.rfind(state.cwd, 0) == 0 && full.size() >= state.cwd.size()) { label = full.substr(state.cwd.size()); if (!label.empty() && (label[0] == '/' || label[0] == '\\')) label = label.substr(1); } if (label.empty()) label = full; bool is_dir = entry.second; std::string display = is_dir ? "[DIR] " + label : label; if (ImGui::Selectable(display.c_str(), state.selection == full, ImGuiSelectableFlags_AllowDoubleClick)) { state.selection = full; if (ImGui::IsMouseDoubleClicked(0)) { if (is_dir) { state.cwd = full; state.selection.clear(); } else { target_path = state.selection; changed = true; ImGui::CloseCurrentPopup(); } } } } } ImGui::EndChild(); if (ImGui::Button("Select")) { if (!state.selection.empty()) { if (is_directory(state.selection)) { state.cwd = state.selection; state.selection.clear(); } else { target_path = state.selection; changed = true; ImGui::CloseCurrentPopup(); } } } ImGui::SameLine(); if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } state.open = false; return changed; } extern "C" PSXE_API int imgui_frontend_main(int argc, const char* argv[]) { psxe_config_t* cfg = psxe_cfg_create(); if (!cfg) { log_fatal("Failed to allocate frontend config"); return 1; } psxe_cfg_init(cfg); psxe_cfg_load_defaults(cfg); psxe_cfg_load(cfg, argc, argv); std::string bios_path = cfg->bios ? cfg->bios : ""; std::string cdrom_path = cfg->cd_path ? cfg->cd_path : ""; std::string exp_path = cfg->exp_path ? cfg->exp_path : ""; std::string exe_path = cfg->exe ? cfg->exe : ""; std::string region = cfg->region ? cfg->region : "ntsc"; std::string model = cfg->model ? cfg->model : "scph1001"; std::string settings_path = cfg->settings_path ? cfg->settings_path : "settings.toml"; std::string bios_search = cfg->bios_search ? cfg->bios_search : "bios"; bool quiet = cfg->quiet != 0; bool use_args = cfg->use_args != 0; bool texture_scale_mode = cfg->texture_scale_mode != 0; bool debug_panel = cfg->debug_panel != 0; bool stretch_mode = cfg->stretch_mode != 0; int display_aspect = cfg->display_aspect; int upscale_height = cfg->upscale_height ? cfg->upscale_height : 480; int log_level = std::clamp(cfg->log_level, 0, (int)LOG_FATAL); int scale = cfg->scale ? cfg->scale : 1; std::array bios_buf{}; std::array cdrom_buf{}; std::array exp_buf{}; std::array exe_buf{}; std::array region_buf{}; std::array model_buf{}; std::array settings_buf{}; std::array bios_search_buf{}; sync_buffer(bios_buf, bios_path); sync_buffer(cdrom_buf, cdrom_path); sync_buffer(exp_buf, exp_path); sync_buffer(exe_buf, exe_path); sync_buffer(region_buf, region); sync_buffer(model_buf, model); sync_buffer(settings_buf, settings_path); sync_buffer(bios_search_buf, bios_search); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { log_error("SDL_Init failed for frontend: %s", SDL_GetError()); psxe_cfg_destroy(cfg); return 1; } SDL_Window* window = SDL_CreateWindow( "ARMSX Frontend", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 960, 640, SDL_WINDOW_RESIZABLE ); if (!window) { log_error("Failed to create SDL window for frontend: %s", SDL_GetError()); psxe_cfg_destroy(cfg); SDL_Quit(); return 1; } SDL_Renderer* renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC ); if (!renderer) { log_error("Failed to create SDL renderer for frontend: %s", SDL_GetError()); SDL_DestroyWindow(window); psxe_cfg_destroy(cfg); SDL_Quit(); return 1; } imgui_layer_init(window, renderer); bool running = true; bool settings_dirty = false; bool start_requested = false; bool show_settings = false; bool show_about = false; bool show_bios_popup = false; bool show_cd_popup = false; FilePickerState bios_picker{}; FilePickerState cd_picker{}; const std::string readme_text = read_readme(); while (running) { SDL_Event event; while (SDL_PollEvent(&event)) { imgui_layer_handle_event(&event); if (event.type == SDL_QUIT) { running = false; } } imgui_layer_new_frame(); ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Always); ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always); ImGuiWindowFlags root_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_MenuBar; ImGui::Begin("FrontendRoot", nullptr, root_flags); if (ImGui::BeginMenuBar()) { if (ImGui::MenuItem("Start")) { if (!cdrom_path.empty() && bios_path.empty()) { SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Missing BIOS", "A BIOS is required before booting a CDROM image.", window ); } else { start_requested = true; running = false; } } if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Load BIOS")) { #ifdef __EMSCRIPTEN__ psxe_wasm_pick_file(1); #else bios_picker.open = true; bios_picker.selection.clear(); bios_picker.cwd.clear(); show_bios_popup = true; show_cd_popup = false; show_settings = false; show_about = false; #endif } if (ImGui::MenuItem("Load CDROM")) { #ifdef __EMSCRIPTEN__ psxe_wasm_pick_file(0); #else cd_picker.open = true; cd_picker.selection.clear(); cd_picker.cwd.clear(); show_cd_popup = true; show_bios_popup = false; show_settings = false; show_about = false; #endif } ImGui::Separator(); if (ImGui::MenuItem("Quit")) { running = false; } ImGui::EndMenu(); } if (ImGui::MenuItem("Settings")) { show_settings = true; show_about = false; show_bios_popup = false; show_cd_popup = false; } if (ImGui::MenuItem("About")) { show_about = true; show_settings = false; show_bios_popup = false; show_cd_popup = false; } ImGui::EndMenuBar(); } ImGui::End(); if (show_bios_popup || bios_picker.open #ifdef __EMSCRIPTEN__ || !g_wasm_pending_bios.empty() #endif ) { if (show_bios_popup) ImGui::OpenPopup("Load BIOS"); ImGui::SetNextWindowSize(ImVec2(420.0f, 210.0f), ImGuiCond_Appearing); if (ImGui::BeginPopupModal("Load BIOS", &show_bios_popup, ImGuiWindowFlags_NoResize)) { if (ImGui::InputText("Path", bios_buf.data(), bios_buf.size())) { bios_path = bios_buf.data(); } #ifndef __EMSCRIPTEN__ if (ImGui::Button("Browse...")) { bios_picker.open = true; } #else if (ImGui::Button("Browse...")) { psxe_wasm_pick_file(1); } #endif ImGui::SameLine(); if (ImGui::Button("Use BIOS")) { bios_path = bios_buf.data(); settings_dirty = true; show_bios_popup = false; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Close")) { show_bios_popup = false; ImGui::CloseCurrentPopup(); } ImGui::Separator(); ImGui::TextWrapped("Select a BIOS file to override the default search/model."); ImGui::EndPopup(); } if (file_picker_popup("Browse BIOS File", bios_picker, bios_path)) { sync_buffer(bios_buf, bios_path); show_bios_popup = false; settings_dirty = true; } #ifdef __EMSCRIPTEN__ if (!g_wasm_pending_bios.empty()) { bios_path = g_wasm_pending_bios; sync_buffer(bios_buf, bios_path); show_bios_popup = false; settings_dirty = true; g_wasm_pending_bios.clear(); } #endif } if (show_cd_popup || cd_picker.open #ifdef __EMSCRIPTEN__ || !g_wasm_pending_cd.empty() #endif ) { if (show_cd_popup) ImGui::OpenPopup("Load CDROM"); ImGui::SetNextWindowSize(ImVec2(420.0f, 210.0f), ImGuiCond_Appearing); if (ImGui::BeginPopupModal("Load CDROM", &show_cd_popup, ImGuiWindowFlags_NoResize)) { if (ImGui::InputText("Path", cdrom_buf.data(), cdrom_buf.size())) { cdrom_path = cdrom_buf.data(); } #ifndef __EMSCRIPTEN__ if (ImGui::Button("Browse...")) { cd_picker.open = true; } #else if (ImGui::Button("Browse...")) { psxe_wasm_pick_file(0); } #endif ImGui::SameLine(); if (ImGui::Button("Use CDROM")) { cdrom_path = cdrom_buf.data(); show_cd_popup = false; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Close")) { show_cd_popup = false; ImGui::CloseCurrentPopup(); } ImGui::Separator(); ImGui::TextWrapped("Choose a CDROM image to boot."); ImGui::EndPopup(); } if (file_picker_popup("Browse CDROM File", cd_picker, cdrom_path)) { sync_buffer(cdrom_buf, cdrom_path); show_cd_popup = false; } #ifdef __EMSCRIPTEN__ if (!g_wasm_pending_cd.empty()) { cdrom_path = g_wasm_pending_cd; sync_buffer(cdrom_buf, cdrom_path); show_cd_popup = false; g_wasm_pending_cd.clear(); } #endif } if (show_settings) { center_next_window(ImVec2(392.0f, 448.0f)); if (ImGui::Begin("Settings", &show_settings, ImGuiWindowFlags_NoCollapse)) { ImGui::TextUnformatted("Boot options"); if (ImGui::InputText("BIOS", bios_buf.data(), bios_buf.size())) { bios_path = bios_buf.data(); settings_dirty = true; } if (ImGui::InputText("CDROM", cdrom_buf.data(), cdrom_buf.size())) cdrom_path = cdrom_buf.data(); if (ImGui::InputText("Expansion ROM", exp_buf.data(), exp_buf.size())) exp_path = exp_buf.data(); if (ImGui::InputText("PS-X EXE", exe_buf.data(), exe_buf.size())) exe_path = exe_buf.data(); ImGui::Separator(); ImGui::TextUnformatted("Console"); const char* regions[] = { "auto", "ntsc", "pal" }; int region_idx = 1; for (int i = 0; i < 3; i++) { if (region == regions[i]) region_idx = i; } if (ImGui::Combo("Region", ®ion_idx, regions, IM_ARRAYSIZE(regions))) { region = regions[region_idx]; settings_dirty = true; } static const char* models[] = { "scph1000","scph1001","scph1002","scph3000","scph3500","scph5000", "scph5500","scph5501","scph5502","scph5552", "scph7000","scph7001","scph7002","scph7003", "scph7501","scph7502","scph9002","scph100","scph101","scph102a","scph102b","scph102c" }; int model_idx = 1; for (int i = 0; i < (int)(sizeof(models)/sizeof(models[0])); i++) { if (model == models[i]) { model_idx = i; break; } } if (ImGui::Combo("Model", &model_idx, models, IM_ARRAYSIZE(models))) { model = models[model_idx]; settings_dirty = true; } ImGui::Checkbox("Use CLI args only (--use-args)", &use_args); if (ImGui::InputText("Settings file", settings_buf.data(), settings_buf.size())) { settings_path = settings_buf.data(); settings_dirty = true; } if (ImGui::InputText("BIOS folder", bios_search_buf.data(), bios_search_buf.size())) { bios_search = bios_search_buf.data(); settings_dirty = true; } ImGui::Separator(); ImGui::TextUnformatted("Video"); if (ImGui::SliderInt("Scale", &scale, 1, 6)) settings_dirty = true; if (ImGui::Checkbox("Texture scale mode", &texture_scale_mode)) settings_dirty = true; if (ImGui::Checkbox("Debug panel", &debug_panel)) settings_dirty = true; if (ImGui::Checkbox("Stretch to window", &stretch_mode)) settings_dirty = true; const char* aspect_labels[] = { "classic", "square", "wide16x9" }; if (display_aspect < 0 || display_aspect > 2) display_aspect = 0; if (ImGui::Combo("Display aspect", &display_aspect, aspect_labels, IM_ARRAYSIZE(aspect_labels))) settings_dirty = true; const char* upscales[] = { "480p", "720p", "1080p", "1440p", "2160p" }; int upscale_idx = map_upscale_index(upscale_height); if (ImGui::Combo("Wide upscale", &upscale_idx, upscales, IM_ARRAYSIZE(upscales))) { upscale_height = upscale_value_from_index(upscale_idx); settings_dirty = true; } ImGui::Separator(); ImGui::TextUnformatted("Logging"); const char* log_levels[] = { "trace", "debug", "info", "warn", "error", "fatal" }; log_level = std::clamp(log_level, 0, (int)LOG_FATAL); if (ImGui::Combo("Log level", &log_level, log_levels, IM_ARRAYSIZE(log_levels))) settings_dirty = true; if (ImGui::Checkbox("Quiet logs", &quiet)) settings_dirty = true; if (!readme_text.empty()) { ImGui::Separator(); ImGui::TextUnformatted("CLI reference (README.md)"); ImGui::BeginChild("readme_help", ImVec2(0, 160), true); ImGui::TextUnformatted(readme_text.c_str()); ImGui::EndChild(); } } ImGui::End(); } if (show_about) { center_next_window(ImVec2(540.0f, 260.0f)); ImGui::OpenPopup("About ARMSX"); show_about = false; } ImGui::SetNextWindowSize(ImVec2(540.0f, 260.0f), ImGuiCond_Appearing); if (ImGui::BeginPopupModal("About ARMSX", nullptr, ImGuiWindowFlags_NoResize)) { ImGui::TextWrapped("ARMSX - the PS1 emulator for everything."); ImGui::Spacing(); ImGui::TextWrapped("Use the menu bar to load a BIOS, choose a CDROM, adjust settings, and press Start to boot."); ImGui::Spacing(); if (ImGui::Button("Close")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::Render(); if (settings_dirty && !settings_path.empty()) { write_settings_file( settings_path, bios_search, model, bios_path, region, texture_scale_mode, debug_panel, stretch_mode, display_aspect, upscale_height ); settings_dirty = false; } SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(renderer); ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer); SDL_RenderPresent(renderer); } imgui_layer_shutdown(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); if (!start_requested) { psxe_cfg_destroy(cfg); return 0; } cfg->bios = bios_path.empty() ? NULL : bios_path.c_str(); cfg->cd_path = cdrom_path.empty() ? NULL : cdrom_path.c_str(); cfg->exp_path = exp_path.empty() ? NULL : exp_path.c_str(); cfg->exe = exe_path.empty() ? NULL : exe_path.c_str(); cfg->region = region.empty() ? cfg->region : region.c_str(); cfg->model = model.empty() ? cfg->model : model.c_str(); cfg->bios_search = bios_search.empty() ? cfg->bios_search : bios_search.c_str(); cfg->settings_path = settings_path.empty() ? cfg->settings_path : settings_path.c_str(); cfg->quiet = quiet ? 1 : 0; cfg->use_args = use_args ? 1 : 0; cfg->texture_scale_mode = texture_scale_mode ? 1 : 0; cfg->debug_panel = debug_panel ? 1 : 0; cfg->stretch_mode = stretch_mode ? 1 : 0; cfg->display_aspect = display_aspect; cfg->upscale_height = upscale_height; cfg->log_level = log_level; cfg->scale = scale; return psxe_run_configured(cfg, NULL, NULL); } #endif #if defined(IMGUI_FRONTEND) && !defined(__DLL_BUILD) && !defined(IOS_TARGET) && !defined(__ANDROID__) static FilePickerState g_ingame_cd_picker{}; static std::string g_ingame_cd_target; static bool g_ingame_about = false; extern "C" PSXE_API void imgui_ingame_menu_render(int paused, const char* current_cd, imgui_ingame_actions_t* actions) { if (!actions) return; memset(actions, 0, sizeof(*actions)); actions->new_cd_path[0] = '\0'; ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Always); ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always); ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_MenuBar; if (ImGui::Begin("InGameMenuBar", nullptr, flags)) { if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Start")) { if (ImGui::MenuItem(paused ? "Resume" : "Pause")) { actions->toggle_pause = 1; } if (ImGui::MenuItem("Reset")) { actions->reset = 1; } ImGui::EndMenu(); } if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Load CDROM...")) { g_ingame_cd_picker.open = true; g_ingame_cd_picker.selection.clear(); g_ingame_cd_picker.cwd.clear(); } if (ImGui::MenuItem("Quit")) { actions->quit = 1; } ImGui::EndMenu(); } if (ImGui::BeginMenu("About")) { if (ImGui::MenuItem("About ARMSX")) { g_ingame_about = true; } ImGui::EndMenu(); } ImGui::EndMenuBar(); } //ImGui::TextWrapped("CDROM: %s", current_cd && current_cd[0] ? current_cd : "(none)"); } ImGui::End(); ImGui::SetNextWindowSize(ImVec2(420.0f, 210.0f), ImGuiCond_Appearing); if (file_picker_popup("Ingame CD Picker", g_ingame_cd_picker, g_ingame_cd_target)) { if (g_ingame_cd_target.size() < sizeof(actions->new_cd_path)) { memcpy(actions->new_cd_path, g_ingame_cd_target.c_str(), g_ingame_cd_target.size() + 1); } else { strncpy(actions->new_cd_path, g_ingame_cd_target.c_str(), sizeof(actions->new_cd_path) - 1); actions->new_cd_path[sizeof(actions->new_cd_path) - 1] = '\0'; } actions->swap_cd = 1; } if (g_ingame_about) ImGui::OpenPopup("About In-Game"); ImGui::SetNextWindowSize(ImVec2(420.0f, 200.0f), ImGuiCond_Appearing); if (ImGui::BeginPopupModal("About In-Game", &g_ingame_about, ImGuiWindowFlags_NoResize)) { ImGui::TextWrapped("ARMSX in-game menu. Press F6 to toggle visibility."); ImGui::Spacing(); ImGui::TextWrapped("Use Start -> Pause/Resume, Reset for soft reset, File -> Load CDROM... to reboot with a new image."); ImGui::Spacing(); if (ImGui::Button("Close")) { g_ingame_about = false; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } #endif