#include "system.h" #include "bios.h" #include "bus.h" #include "cdrom.h" #include "common/audio_stream.h" #include "common/file_system.h" #include "common/log.h" #include "common/state_wrapper.h" #include "common/string_util.h" #include "controller.h" #include "cpu_code_cache.h" #include "cpu_core.h" #include "dma.h" #include "game_list.h" #include "gpu.h" #include "gte.h" #include "host_display.h" #include "host_interface.h" #include "host_interface_progress_callback.h" #include "interrupt_controller.h" #include "mdec.h" #include "memory_card.h" #include "pad.h" #include "psf_loader.h" #include "save_state_version.h" #include "sio.h" #include "spu.h" #include "timers.h" #include #include Log_SetChannel(System); #ifdef WIN32 #include "common/windows_headers.h" #else #include #endif SystemBootParameters::SystemBootParameters() = default; SystemBootParameters::SystemBootParameters(std::string filename_) : filename(filename_) {} SystemBootParameters::SystemBootParameters(const SystemBootParameters& copy) : filename(copy.filename), override_fast_boot(copy.override_fast_boot), override_fullscreen(copy.override_fullscreen) { // only exists for qt, we can't copy the state stream Assert(!copy.state_stream); } SystemBootParameters::~SystemBootParameters() = default; namespace System { static bool LoadEXE(const char* filename, std::vector& bios_image); static bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector& bios_image); static bool LoadPSF(const char* filename, std::vector& bios_image); static bool SetExpansionROM(const char* filename); /// Opens CD image, preloading if needed. static std::unique_ptr OpenCDImage(const char* path, bool force_preload); static bool DoLoadState(ByteStream* stream, bool force_software_renderer); static bool DoState(StateWrapper& sw); static bool CreateGPU(GPURenderer renderer); static bool Initialize(bool force_software_renderer); static void UpdateRunningGame(const char* path, CDImage* image); static State s_state = State::Shutdown; static ConsoleRegion s_region = ConsoleRegion::NTSC_U; static u32 s_frame_number = 1; static u32 s_internal_frame_number = 1; static std::string s_running_game_path; static std::string s_running_game_code; static std::string s_running_game_title; static float s_throttle_frequency = 60.0f; static s32 s_throttle_period = 0; static u64 s_last_throttle_time = 0; static Common::Timer s_throttle_timer; static Common::Timer s_speed_lost_time_timestamp; static float s_average_frame_time_accumulator = 0.0f; static float s_worst_frame_time_accumulator = 0.0f; static float s_vps = 0.0f; static float s_fps = 0.0f; static float s_speed = 0.0f; static float s_worst_frame_time = 0.0f; static float s_average_frame_time = 0.0f; static u32 s_last_frame_number = 0; static u32 s_last_internal_frame_number = 0; static u32 s_last_global_tick_counter = 0; static Common::Timer s_fps_timer; static Common::Timer s_frame_timer; // Playlist of disc images. static std::vector s_media_playlist; static std::string s_media_playlist_filename; State GetState() { return s_state; } void SetState(State new_state) { Assert(s_state == State::Paused || s_state == State::Running); Assert(new_state == State::Paused || new_state == State::Running); s_state = new_state; } bool IsRunning() { return s_state == State::Running; } bool IsPaused() { return s_state == State::Paused; } bool IsShutdown() { return s_state == State::Shutdown; } bool IsValid() { return s_state != State::Shutdown && s_state != State::Starting; } ConsoleRegion GetRegion() { return s_region; } bool IsPALRegion() { return s_region == ConsoleRegion::PAL; } u32 GetFrameNumber() { return s_frame_number; } u32 GetInternalFrameNumber() { return s_internal_frame_number; } void FrameDone() { s_frame_number++; CPU::g_state.frame_done = true; } void IncrementInternalFrameNumber() { s_internal_frame_number++; } const std::string& GetRunningPath() { return s_running_game_path; } const std::string& GetRunningCode() { return s_running_game_code; } const std::string& GetRunningTitle() { return s_running_game_title; } float GetFPS() { return s_fps; } float GetVPS() { return s_vps; } float GetEmulationSpeed() { return s_speed; } float GetAverageFrameTime() { return s_average_frame_time; } float GetWorstFrameTime() { return s_worst_frame_time; } float GetThrottleFrequency() { return s_throttle_frequency; } ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region) { switch (region) { case DiscRegion::NTSC_J: return ConsoleRegion::NTSC_J; case DiscRegion::NTSC_U: case DiscRegion::Other: default: return ConsoleRegion::NTSC_U; case DiscRegion::PAL: return ConsoleRegion::PAL; } } bool RecreateGPU(GPURenderer renderer) { g_gpu->RestoreGraphicsAPIState(); // save current state std::unique_ptr state_stream = ByteStream_CreateGrowableMemoryStream(); StateWrapper sw(state_stream.get(), StateWrapper::Mode::Write); const bool state_valid = g_gpu->DoState(sw) && TimingEvents::DoState(sw); if (!state_valid) Log_ErrorPrintf("Failed to save old GPU state when switching renderers"); g_gpu->ResetGraphicsAPIState(); // create new renderer g_gpu.reset(); if (!CreateGPU(renderer)) { Panic("Failed to recreate GPU"); return false; } if (state_valid) { state_stream->SeekAbsolute(0); sw.SetMode(StateWrapper::Mode::Read); g_gpu->RestoreGraphicsAPIState(); g_gpu->DoState(sw); TimingEvents::DoState(sw); g_gpu->ResetGraphicsAPIState(); } return true; } std::unique_ptr OpenCDImage(const char* path, bool force_preload) { std::unique_ptr media = CDImage::Open(path); if (!media) return {}; if (force_preload || g_settings.cdrom_load_image_to_ram) { HostInterfaceProgressCallback callback; std::unique_ptr memory_image = CDImage::CreateMemoryImage(media.get(), &callback); if (memory_image) media = std::move(memory_image); else Log_WarningPrintf("Failed to preload image '%s' to RAM", path); } return media; } bool Boot(const SystemBootParameters& params) { Assert(s_state == State::Shutdown); Assert(s_media_playlist.empty()); s_state = State::Starting; s_region = g_settings.region; if (params.state_stream) { if (!DoLoadState(params.state_stream.get(), params.force_software_renderer)) { Shutdown(); return false; } return true; } // Load CD image up and detect region. std::unique_ptr media; bool exe_boot = false; bool psf_boot = false; if (!params.filename.empty()) { exe_boot = GameList::IsExeFileName(params.filename.c_str()); psf_boot = (!exe_boot && GameList::IsPsfFileName(params.filename.c_str())); if (exe_boot || psf_boot) { // TODO: Pull region from PSF if (s_region == ConsoleRegion::Auto) { Log_InfoPrintf("Defaulting to NTSC-U region for executable."); s_region = ConsoleRegion::NTSC_U; } } else { u32 playlist_index; if (GameList::IsM3UFileName(params.filename.c_str())) { s_media_playlist = GameList::ParseM3UFile(params.filename.c_str()); s_media_playlist_filename = params.filename; if (s_media_playlist.empty()) { g_host_interface->ReportFormattedError("Failed to parse playlist '%s'", params.filename.c_str()); Shutdown(); return false; } if (params.media_playlist_index >= s_media_playlist.size()) { Log_WarningPrintf("Media playlist index %u out of range, using first", params.media_playlist_index); playlist_index = 0; } else { playlist_index = params.media_playlist_index; } } else { AddMediaPathToPlaylist(params.filename); playlist_index = 0; } const std::string& media_path = s_media_playlist[playlist_index]; Log_InfoPrintf("Loading CD image '%s' from playlist index %u...", media_path.c_str(), playlist_index); media = OpenCDImage(media_path.c_str(), params.load_image_to_ram); if (!media) { g_host_interface->ReportFormattedError("Failed to load CD image '%s'", params.filename.c_str()); Shutdown(); return false; } if (s_region == ConsoleRegion::Auto) { const DiscRegion disc_region = GameList::GetRegionForImage(media.get()); if (disc_region != DiscRegion::Other) { s_region = GetConsoleRegionForDiscRegion(disc_region); Log_InfoPrintf("Auto-detected console %s region for '%s' (region %s)", Settings::GetConsoleRegionName(s_region), params.filename.c_str(), Settings::GetDiscRegionName(disc_region)); } else { s_region = ConsoleRegion::NTSC_U; Log_WarningPrintf("Could not determine console region for disc region %s. Defaulting to %s.", Settings::GetDiscRegionName(disc_region), Settings::GetConsoleRegionName(s_region)); } } } } else { // Default to NTSC for BIOS boot. if (s_region == ConsoleRegion::Auto) s_region = ConsoleRegion::NTSC_U; } // Load BIOS image. std::optional bios_image = g_host_interface->GetBIOSImage(s_region); if (!bios_image) { g_host_interface->ReportFormattedError("Failed to load %s BIOS", Settings::GetConsoleRegionName(s_region)); Shutdown(); return false; } // Notify change of disc. UpdateRunningGame(media ? media->GetFileName().c_str() : params.filename.c_str(), media.get()); // Component setup. if (!Initialize(params.force_software_renderer)) { Shutdown(); return false; } UpdateControllers(); UpdateMemoryCards(); Reset(); // Enable tty by patching bios. const BIOS::Hash bios_hash = BIOS::GetHash(*bios_image); if (g_settings.bios_patch_tty_enable) BIOS::PatchBIOSEnableTTY(*bios_image, bios_hash); // Load EXE late after BIOS. if (exe_boot && !LoadEXE(params.filename.c_str(), *bios_image)) { g_host_interface->ReportFormattedError("Failed to load EXE file '%s'", params.filename.c_str()); Shutdown(); return false; } else if (psf_boot && !LoadPSF(params.filename.c_str(), *bios_image)) { g_host_interface->ReportFormattedError("Failed to load PSF file '%s'", params.filename.c_str()); Shutdown(); return false; } // Insert CD, and apply fastboot patch if enabled. if (media) g_cdrom.InsertMedia(std::move(media)); if (g_cdrom.HasMedia() && (params.override_fast_boot.has_value() ? params.override_fast_boot.value() : g_settings.bios_patch_fast_boot)) { BIOS::PatchBIOSFastBoot(*bios_image, bios_hash); } // Load the patched BIOS up. Bus::SetBIOS(*bios_image); // Good to go. s_state = State::Running; return true; } bool Initialize(bool force_software_renderer) { s_frame_number = 1; s_internal_frame_number = 1; s_throttle_frequency = 60.0f; s_throttle_period = 0; s_last_throttle_time = 0; s_throttle_timer.Reset(); s_speed_lost_time_timestamp.Reset(); s_average_frame_time_accumulator = 0.0f; s_worst_frame_time_accumulator = 0.0f; s_vps = 0.0f; s_fps = 0.0f; s_speed = 0.0f; s_worst_frame_time = 0.0f; s_average_frame_time = 0.0f; s_last_frame_number = 0; s_last_internal_frame_number = 0; s_last_global_tick_counter = 0; s_fps_timer.Reset(); s_frame_timer.Reset(); TimingEvents::Initialize(); CPU::Initialize(); CPU::CodeCache::Initialize(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler); Bus::Initialize(); if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer)) return false; g_dma.Initialize(); g_interrupt_controller.Initialize(); g_cdrom.Initialize(); g_pad.Initialize(); g_timers.Initialize(); g_spu.Initialize(); g_mdec.Initialize(); g_sio.Initialize(); UpdateThrottlePeriod(); return true; } void Shutdown() { if (s_state == State::Shutdown) return; g_sio.Shutdown(); g_mdec.Shutdown(); g_spu.Shutdown(); g_timers.Shutdown(); g_pad.Shutdown(); g_cdrom.Shutdown(); g_gpu.reset(); g_interrupt_controller.Shutdown(); g_dma.Shutdown(); CPU::CodeCache::Shutdown(); Bus::Shutdown(); CPU::Shutdown(); TimingEvents::Shutdown(); s_running_game_code.clear(); s_running_game_path.clear(); s_running_game_title.clear(); s_media_playlist.clear(); s_media_playlist_filename.clear(); s_state = State::Shutdown; } bool CreateGPU(GPURenderer renderer) { switch (renderer) { case GPURenderer::HardwareOpenGL: g_gpu = GPU::CreateHardwareOpenGLRenderer(); break; case GPURenderer::HardwareVulkan: g_gpu = GPU::CreateHardwareVulkanRenderer(); break; #ifdef WIN32 case GPURenderer::HardwareD3D11: g_gpu = GPU::CreateHardwareD3D11Renderer(); break; #endif case GPURenderer::Software: default: g_gpu = GPU::CreateSoftwareRenderer(); break; } if (!g_gpu || !g_gpu->Initialize(g_host_interface->GetDisplay())) { Log_ErrorPrintf("Failed to initialize GPU, falling back to software"); g_gpu.reset(); g_gpu = GPU::CreateSoftwareRenderer(); if (!g_gpu->Initialize(g_host_interface->GetDisplay())) return false; } // we put this here rather than in Initialize() because of the virtual calls g_gpu->Reset(); return true; } bool DoState(StateWrapper& sw) { if (!sw.DoMarker("System")) return false; sw.Do(&s_region); sw.Do(&s_frame_number); sw.Do(&s_internal_frame_number); if (!sw.DoMarker("CPU") || !CPU::DoState(sw)) return false; if (sw.IsReading()) CPU::CodeCache::Flush(); if (!sw.DoMarker("Bus") || !Bus::DoState(sw)) return false; if (!sw.DoMarker("DMA") || !g_dma.DoState(sw)) return false; if (!sw.DoMarker("InterruptController") || !g_interrupt_controller.DoState(sw)) return false; g_gpu->RestoreGraphicsAPIState(); const bool gpu_result = sw.DoMarker("GPU") && g_gpu->DoState(sw); g_gpu->ResetGraphicsAPIState(); if (!gpu_result) return false; if (!sw.DoMarker("CDROM") || !g_cdrom.DoState(sw)) return false; if (!sw.DoMarker("Pad") || !g_pad.DoState(sw)) return false; if (!sw.DoMarker("Timers") || !g_timers.DoState(sw)) return false; if (!sw.DoMarker("SPU") || !g_spu.DoState(sw)) return false; if (!sw.DoMarker("MDEC") || !g_mdec.DoState(sw)) return false; if (!sw.DoMarker("SIO") || !g_sio.DoState(sw)) return false; if (!sw.DoMarker("Events") || !TimingEvents::DoState(sw)) return false; return !sw.HasError(); } void Reset() { if (IsShutdown()) return; g_gpu->RestoreGraphicsAPIState(); CPU::Reset(); CPU::CodeCache::Flush(); Bus::Reset(); g_dma.Reset(); g_interrupt_controller.Reset(); g_gpu->Reset(); g_cdrom.Reset(); g_pad.Reset(); g_timers.Reset(); g_spu.Reset(); g_mdec.Reset(); g_sio.Reset(); s_frame_number = 1; s_internal_frame_number = 0; TimingEvents::Reset(); ResetPerformanceCounters(); g_gpu->ResetGraphicsAPIState(); } bool LoadState(ByteStream* state) { if (IsShutdown()) return false; return DoLoadState(state, false); } bool DoLoadState(ByteStream* state, bool force_software_renderer) { SAVE_STATE_HEADER header; if (!state->Read2(&header, sizeof(header))) return false; if (header.magic != SAVE_STATE_MAGIC) return false; if (header.version != SAVE_STATE_VERSION) { g_host_interface->ReportFormattedError( g_host_interface->TranslateString("System", "Save state is incompatible: expecting version %u but state is version %u."), SAVE_STATE_VERSION, header.version); return false; } std::string media_filename; std::unique_ptr media; if (header.media_filename_length > 0) { media_filename.resize(header.media_filename_length); if (!state->SeekAbsolute(header.offset_to_media_filename) || !state->Read2(media_filename.data(), header.media_filename_length)) { return false; } media = g_cdrom.RemoveMedia(); if (!media || media->GetFileName() != media_filename) { media = OpenCDImage(media_filename.c_str(), false); if (!media) { g_host_interface->ReportFormattedError( g_host_interface->TranslateString("System", "Failed to open CD image from save state: '%s'."), media_filename.c_str()); return false; } } } std::string playlist_filename; std::vector playlist_entries; if (header.playlist_filename_length > 0) { playlist_filename.resize(header.offset_to_playlist_filename); if (!state->SeekAbsolute(header.offset_to_playlist_filename) || !state->Read2(playlist_filename.data(), header.playlist_filename_length)) { return false; } playlist_entries = GameList::ParseM3UFile(playlist_filename.c_str()); if (playlist_entries.empty()) { g_host_interface->ReportFormattedError("Failed to load save state playlist entries from '%s'", playlist_filename.c_str()); return false; } } UpdateRunningGame(media_filename.c_str(), media.get()); if (s_state == State::Starting) { if (!Initialize(force_software_renderer)) return false; if (media) g_cdrom.InsertMedia(std::move(media)); s_media_playlist_filename = std::move(playlist_filename); s_media_playlist = std::move(playlist_entries); UpdateControllers(); UpdateMemoryCards(); } else { g_cdrom.Reset(); if (media) g_cdrom.InsertMedia(std::move(media)); else g_cdrom.RemoveMedia(); s_media_playlist_filename = std::move(playlist_filename); s_media_playlist = std::move(playlist_entries); // ensure the correct card is loaded if (g_settings.HasAnyPerGameMemoryCards()) UpdateMemoryCards(); } if (header.data_compression_type != 0) { g_host_interface->ReportFormattedError("Unknown save state compression type %u", header.data_compression_type); return false; } if (!state->SeekAbsolute(header.offset_to_data)) return false; StateWrapper sw(state, StateWrapper::Mode::Read); if (!DoState(sw)) return false; if (s_state == State::Starting) s_state = State::Running; return true; } bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) { if (IsShutdown()) return false; SAVE_STATE_HEADER header = {}; const u64 header_position = state->GetPosition(); if (!state->Write2(&header, sizeof(header))) return false; // fill in header header.magic = SAVE_STATE_MAGIC; header.version = SAVE_STATE_VERSION; StringUtil::Strlcpy(header.title, s_running_game_title.c_str(), sizeof(header.title)); StringUtil::Strlcpy(header.game_code, s_running_game_code.c_str(), sizeof(header.game_code)); if (g_cdrom.HasMedia()) { const std::string& media_filename = g_cdrom.GetMediaFileName(); header.offset_to_media_filename = static_cast(state->GetPosition()); header.media_filename_length = static_cast(media_filename.length()); if (!media_filename.empty() && !state->Write2(media_filename.data(), header.media_filename_length)) return false; } if (!s_media_playlist_filename.empty()) { header.offset_to_playlist_filename = static_cast(state->GetPosition()); header.playlist_filename_length = static_cast(s_media_playlist_filename.length()); if (!state->Write2(s_media_playlist_filename.data(), header.playlist_filename_length)) return false; } // save screenshot if (screenshot_size > 0) { std::vector screenshot_buffer; if (g_host_interface->GetDisplay()->WriteDisplayTextureToBuffer(&screenshot_buffer, screenshot_size, screenshot_size) && !screenshot_buffer.empty()) { header.offset_to_screenshot = static_cast(state->GetPosition()); header.screenshot_width = screenshot_size; header.screenshot_height = screenshot_size; header.screenshot_size = static_cast(screenshot_buffer.size() * sizeof(u32)); if (!state->Write2(screenshot_buffer.data(), header.screenshot_size)) return false; } } // write data { header.offset_to_data = static_cast(state->GetPosition()); g_gpu->RestoreGraphicsAPIState(); StateWrapper sw(state, StateWrapper::Mode::Write); const bool result = DoState(sw); g_gpu->ResetGraphicsAPIState(); if (!result) return false; header.data_compression_type = 0; header.data_uncompressed_size = static_cast(state->GetPosition() - header.offset_to_data); } // re-write header const u64 end_position = state->GetPosition(); if (!state->SeekAbsolute(header_position) || !state->Write2(&header, sizeof(header)) || !state->SeekAbsolute(end_position)) { return false; } return true; } void RunFrame() { s_frame_timer.Reset(); g_gpu->RestoreGraphicsAPIState(); switch (g_settings.cpu_execution_mode) { case CPUExecutionMode::Recompiler: #ifdef WITH_RECOMPILER CPU::CodeCache::ExecuteRecompiler(); #else CPU::CodeCache::Execute(); #endif break; case CPUExecutionMode::CachedInterpreter: CPU::CodeCache::Execute(); break; case CPUExecutionMode::Interpreter: default: CPU::Execute(); break; } // Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns. g_spu.GeneratePendingSamples(); g_gpu->ResetGraphicsAPIState(); } void SetThrottleFrequency(float frequency) { s_throttle_frequency = frequency; UpdateThrottlePeriod(); } void UpdateThrottlePeriod() { s_throttle_period = static_cast(1000000000.0 / static_cast(s_throttle_frequency) / static_cast(g_settings.emulation_speed)); } void Throttle() { // Allow variance of up to 40ms either way. constexpr s64 MAX_VARIANCE_TIME = INT64_C(40000000); // Don't sleep for <1ms or >=period. constexpr s64 MINIMUM_SLEEP_TIME = INT64_C(1000000); // Use unsigned for defined overflow/wrap-around. const u64 time = static_cast(s_throttle_timer.GetTimeNanoseconds()); const s64 sleep_time = static_cast(s_last_throttle_time - time); if (sleep_time < -MAX_VARIANCE_TIME) { #ifndef _DEBUG // Don't display the slow messages in debug, it'll always be slow... // Limit how often the messages are displayed. if (s_speed_lost_time_timestamp.GetTimeSeconds() >= 1.0f) { Log_WarningPrintf("System too slow, lost %.2f ms", static_cast(-sleep_time - MAX_VARIANCE_TIME) / 1000000.0); s_speed_lost_time_timestamp.Reset(); } #endif s_last_throttle_time = 0; s_throttle_timer.Reset(); } else if (sleep_time >= MINIMUM_SLEEP_TIME && sleep_time <= s_throttle_period) { #ifdef WIN32 Sleep(static_cast(sleep_time / 1000000)); #else const struct timespec ts = {0, static_cast(sleep_time)}; nanosleep(&ts, nullptr); #endif } s_last_throttle_time += s_throttle_period; } void UpdatePerformanceCounters() { const float frame_time = static_cast(s_frame_timer.GetTimeMilliseconds()); s_average_frame_time_accumulator += frame_time; s_worst_frame_time_accumulator = std::max(s_worst_frame_time_accumulator, frame_time); // update fps counter const float time = static_cast(s_fps_timer.GetTimeSeconds()); if (time < 1.0f) return; const float frames_presented = static_cast(s_frame_number - s_last_frame_number); const u32 global_tick_counter = TimingEvents::GetGlobalTickCounter(); s_worst_frame_time = s_worst_frame_time_accumulator; s_worst_frame_time_accumulator = 0.0f; s_average_frame_time = s_average_frame_time_accumulator / frames_presented; s_average_frame_time_accumulator = 0.0f; s_vps = static_cast(frames_presented / time); s_last_frame_number = s_frame_number; s_fps = static_cast(s_internal_frame_number - s_last_internal_frame_number) / time; s_last_internal_frame_number = s_internal_frame_number; s_speed = static_cast(static_cast(global_tick_counter - s_last_global_tick_counter) / (static_cast(MASTER_CLOCK) * time)) * 100.0f; s_last_global_tick_counter = global_tick_counter; s_fps_timer.Reset(); g_host_interface->OnSystemPerformanceCountersUpdated(); } void ResetPerformanceCounters() { s_last_frame_number = s_frame_number; s_last_internal_frame_number = s_internal_frame_number; s_last_global_tick_counter = TimingEvents::GetGlobalTickCounter(); s_average_frame_time_accumulator = 0.0f; s_worst_frame_time_accumulator = 0.0f; s_fps_timer.Reset(); s_throttle_timer.Reset(); s_last_throttle_time = 0; } bool LoadEXE(const char* filename, std::vector& bios_image) { std::FILE* fp = FileSystem::OpenCFile(filename, "rb"); if (!fp) return false; std::fseek(fp, 0, SEEK_END); const u32 file_size = static_cast(std::ftell(fp)); std::fseek(fp, 0, SEEK_SET); BIOS::PSEXEHeader header; if (std::fread(&header, sizeof(header), 1, fp) != 1 || !BIOS::IsValidPSExeHeader(header, file_size)) { std::fclose(fp); return false; } if (header.memfill_size > 0) { const u32 words_to_write = header.memfill_size / 4; u32 address = header.memfill_start & ~UINT32_C(3); for (u32 i = 0; i < words_to_write; i++) { CPU::SafeWriteMemoryWord(address, 0); address += sizeof(u32); } } if (header.file_size >= 4) { std::vector data_words((header.file_size + 3) / 4); if (std::fread(data_words.data(), header.file_size, 1, fp) != 1) { std::fclose(fp); return false; } const u32 num_words = header.file_size / 4; u32 address = header.load_address; for (u32 i = 0; i < num_words; i++) { CPU::SafeWriteMemoryWord(address, data_words[i]); address += sizeof(u32); } } std::fclose(fp); // patch the BIOS to jump to the executable directly const u32 r_pc = header.initial_pc; const u32 r_gp = header.initial_gp; const u32 r_sp = header.initial_sp_base + header.initial_sp_offset; const u32 r_fp = header.initial_sp_base + header.initial_sp_offset; return BIOS::PatchBIOSForEXE(bios_image, r_pc, r_gp, r_sp, r_fp); } bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector& bios_image) { const u8* buffer_ptr = static_cast(buffer); const u8* buffer_end = static_cast(buffer) + buffer_size; BIOS::PSEXEHeader header; if (buffer_size < sizeof(header)) return false; std::memcpy(&header, buffer_ptr, sizeof(header)); buffer_ptr += sizeof(header); if (!BIOS::IsValidPSExeHeader(header, static_cast(buffer_end - buffer_ptr))) return false; if (header.memfill_size > 0) { const u32 words_to_write = header.memfill_size / 4; u32 address = header.memfill_start & ~UINT32_C(3); for (u32 i = 0; i < words_to_write; i++) { CPU::SafeWriteMemoryWord(address, 0); address += sizeof(u32); } } if (header.file_size >= 4) { std::vector data_words((header.file_size + 3) / 4); if ((buffer_end - buffer_ptr) < header.file_size) return false; std::memcpy(data_words.data(), buffer_ptr, header.file_size); const u32 num_words = header.file_size / 4; u32 address = header.load_address; for (u32 i = 0; i < num_words; i++) { CPU::SafeWriteMemoryWord(address, data_words[i]); address += sizeof(u32); } } // patch the BIOS to jump to the executable directly const u32 r_pc = header.initial_pc; const u32 r_gp = header.initial_gp; const u32 r_sp = header.initial_sp_base + header.initial_sp_offset; const u32 r_fp = header.initial_sp_base + header.initial_sp_offset; return BIOS::PatchBIOSForEXE(bios_image, r_pc, r_gp, r_sp, r_fp); } bool LoadPSF(const char* filename, std::vector& bios_image) { Log_InfoPrintf("Loading PSF file from '%s'", filename); PSFLoader::File psf; if (!psf.Load(filename)) return false; const std::vector& exe_data = psf.GetProgramData(); return LoadEXEFromBuffer(exe_data.data(), static_cast(exe_data.size()), bios_image); } bool SetExpansionROM(const char* filename) { std::FILE* fp = FileSystem::OpenCFile(filename, "rb"); if (!fp) { Log_ErrorPrintf("Failed to open '%s'", filename); return false; } std::fseek(fp, 0, SEEK_END); const u32 size = static_cast(std::ftell(fp)); std::fseek(fp, 0, SEEK_SET); std::vector data(size); if (std::fread(data.data(), size, 1, fp) != 1) { Log_ErrorPrintf("Failed to read ROM data from '%s'", filename); std::fclose(fp); return false; } std::fclose(fp); Log_InfoPrintf("Loaded expansion ROM from '%s': %u bytes", filename, size); Bus::SetExpansionROM(std::move(data)); return true; } void StallCPU(TickCount ticks) { CPU::AddPendingTicks(ticks); #if 0 if (CPU::GetPendingTicks() >= CPU::GetDowncount() && !m_running_events) RunEvents(); #endif } Controller* GetController(u32 slot) { return g_pad.GetController(slot); } void UpdateControllers() { for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { g_pad.SetController(i, nullptr); const ControllerType type = g_settings.controller_types[i]; if (type != ControllerType::None) { std::unique_ptr controller = Controller::Create(type, i); if (controller) { controller->LoadSettings(TinyString::FromFormat("Controller%u", i + 1u)); g_pad.SetController(i, std::move(controller)); } } } } void UpdateControllerSettings() { for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { Controller* controller = g_pad.GetController(i); if (controller) controller->LoadSettings(TinyString::FromFormat("Controller%u", i + 1u)); } } void ResetControllers() { for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { Controller* controller = g_pad.GetController(i); if (controller) controller->Reset(); } } void UpdateMemoryCards() { for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { g_pad.SetMemoryCard(i, nullptr); std::unique_ptr card; const MemoryCardType type = g_settings.memory_card_types[i]; switch (type) { case MemoryCardType::None: continue; case MemoryCardType::PerGame: { if (s_running_game_code.empty()) { g_host_interface->AddFormattedOSDMessage( 5.0f, g_host_interface->TranslateString("System", "Per-game memory card cannot be used for slot %u as the running " "game has no code. Using shared card instead."), i + 1u); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else { card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(s_running_game_code.c_str(), i)); } } break; case MemoryCardType::PerGameTitle: { if (!s_media_playlist_filename.empty() && g_settings.memory_card_use_playlist_title) { const std::string playlist_title(GameList::GetTitleForPath(s_media_playlist_filename.c_str())); card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(playlist_title.c_str(), i)); } else if (s_running_game_title.empty()) { g_host_interface->AddFormattedOSDMessage( 5.0f, g_host_interface->TranslateString("System", "Per-game memory card cannot be used for slot %u as the running " "game has no title. Using shared card instead."), i + 1u); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else { card = MemoryCard::Open(g_host_interface->GetGameMemoryCardPath(s_running_game_title.c_str(), i)); } } break; case MemoryCardType::Shared: { if (g_settings.memory_card_paths[i].empty()) { g_host_interface->AddFormattedOSDMessage( 10.0f, g_host_interface->TranslateString("System", "Memory card path for slot %u is missing, using default."), i + 1u); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); } else { card = MemoryCard::Open(g_settings.memory_card_paths[i]); } } break; } if (card) g_pad.SetMemoryCard(i, std::move(card)); } } bool DumpRAM(const char* filename) { auto fp = FileSystem::OpenManagedCFile(filename, "wb"); if (!fp) return false; return std::fwrite(Bus::g_ram, Bus::RAM_SIZE, 1, fp.get()) == 1; } bool HasMedia() { return g_cdrom.HasMedia(); } bool InsertMedia(const char* path) { std::unique_ptr image = OpenCDImage(path, false); if (!image) return false; UpdateRunningGame(path, image.get()); g_cdrom.InsertMedia(std::move(image)); Log_InfoPrintf("Inserted media from %s (%s, %s)", s_running_game_path.c_str(), s_running_game_code.c_str(), s_running_game_title.c_str()); if (g_settings.HasAnyPerGameMemoryCards()) { g_host_interface->AddOSDMessage( g_host_interface->TranslateStdString("System", "Game changed, reloading memory cards."), 10.0f); UpdateMemoryCards(); } return true; } void RemoveMedia() { g_cdrom.RemoveMedia(); } void UpdateRunningGame(const char* path, CDImage* image) { if (s_running_game_path == path) return; s_running_game_path.clear(); s_running_game_code.clear(); s_running_game_title.clear(); if (path && std::strlen(path) > 0) { s_running_game_path = path; g_host_interface->GetGameInfo(path, image, &s_running_game_code, &s_running_game_title); } g_host_interface->OnRunningGameChanged(); } bool HasMediaPlaylist() { return !s_media_playlist_filename.empty(); } u32 GetMediaPlaylistCount() { return static_cast(s_media_playlist.size()); } const std::string& GetMediaPlaylistPath(u32 index) { return s_media_playlist[index]; } u32 GetMediaPlaylistIndex() { if (!g_cdrom.HasMedia()) return std::numeric_limits::max(); const std::string& media_path = g_cdrom.GetMediaFileName(); for (u32 i = 0; i < static_cast(s_media_playlist.size()); i++) { if (s_media_playlist[i] == media_path) return i; } return std::numeric_limits::max(); } bool AddMediaPathToPlaylist(const std::string_view& path) { if (std::any_of(s_media_playlist.begin(), s_media_playlist.end(), [&path](const std::string& p) { return (path == p); })) { return false; } s_media_playlist.emplace_back(path); return true; } bool RemoveMediaPathFromPlaylist(const std::string_view& path) { for (u32 i = 0; i < static_cast(s_media_playlist.size()); i++) { if (path == s_media_playlist[i]) return RemoveMediaPathFromPlaylist(i); } return false; } bool RemoveMediaPathFromPlaylist(u32 index) { if (index >= static_cast(s_media_playlist.size())) return false; if (GetMediaPlaylistIndex() == index) { g_host_interface->AddFormattedOSDMessage(10.0f, "Removing current media from playlist, removing media from CD-ROM."); g_cdrom.RemoveMedia(); } s_media_playlist.erase(s_media_playlist.begin() + index); return true; } bool ReplaceMediaPathFromPlaylist(u32 index, const std::string_view& path) { if (index >= static_cast(s_media_playlist.size())) return false; if (GetMediaPlaylistIndex() == index) { g_host_interface->AddFormattedOSDMessage(10.0f, "Changing current media from playlist, replacing current media."); g_cdrom.RemoveMedia(); s_media_playlist[index] = path; InsertMedia(s_media_playlist[index].c_str()); } else { s_media_playlist[index] = path; } return true; } bool SwitchMediaFromPlaylist(u32 index) { if (index >= s_media_playlist.size()) return false; const std::string& path = s_media_playlist[index]; if (g_cdrom.HasMedia() && g_cdrom.GetMediaFileName() == path) return true; return InsertMedia(path.c_str()); } } // namespace System