diff --git a/src/core/bios.h b/src/core/bios.h index c18232571..adea58aa5 100644 --- a/src/core/bios.h +++ b/src/core/bios.h @@ -79,6 +79,9 @@ struct PSEXEHeader static_assert(sizeof(PSEXEHeader) == 0x800); #pragma pack(pop) +// .cpe files +static constexpr u32 CPE_MAGIC = 0x01455043; + std::optional LoadImageFromFile(const char* filename, Error* error); const ImageInfo* GetInfoForHash(const std::span image, const ImageInfo::Hash& hash); diff --git a/src/core/bus.cpp b/src/core/bus.cpp index c7eb4f999..fda412dd3 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -27,12 +27,14 @@ #include "common/align.h" #include "common/assert.h" +#include "common/binary_reader_writer.h" #include "common/error.h" #include "common/file_system.h" #include "common/intrin.h" #include "common/log.h" #include "common/memmap.h" #include "common/path.h" +#include "common/string_util.h" #include #include @@ -167,6 +169,7 @@ static void SetRAMPageWritable(u32 page_index, bool writable); static void KernelInitializedHook(); static bool SideloadEXE(const std::string& path, Error* error); +static bool InjectCPE(std::span buffer, bool set_pc, Error* error); static void SetHandlers(); static void UpdateMappedRAMSize(); @@ -1009,6 +1012,139 @@ bool Bus::InjectExecutable(std::span buffer, bool set_pc, Error* error return true; } +bool Bus::InjectCPE(std::span buffer, bool set_pc, Error* error) +{ + // https://psx-spx.consoledev.net/cdromfileformats/#cdrom-file-psyq-cpe-files-debug-executables + BinarySpanReader reader(buffer); + if (reader.ReadU32() != BIOS::CPE_MAGIC) + { + Error::SetStringView(error, "Invalid CPE signature."); + return false; + } + + static constexpr auto set_register = [](u32 reg, u32 value) { + if (reg == 0x90) + { + CPU::SetPC(value); + } + else + { + WARNING_LOG("Ignoring set register 0x{:X} to 0x{:X}", reg, value); + } + }; + + for (;;) + { + if (!reader.CheckRemaining(1)) + { + Error::SetStringView(error, "End of file reached before EOF chunk."); + return false; + } + + // Little error checking on chunk sizes, because if any of them run out of buffer, + // it'll loop around and hit the EOF if above. + const u8 chunk = reader.ReadU8(); + switch (chunk) + { + case 0x00: + { + // End of file + return true; + } + + case 0x01: + { + // Load data + const u32 addr = reader.ReadU32(); + const u32 size = reader.ReadU32(); + if (size > 0) + { + if (!reader.CheckRemaining(size)) + { + Error::SetStringFmt(error, "EOF reached in the middle of load to 0x{:08X}", addr); + return false; + } + + if (const auto data = reader.GetRemainingSpan(size); !CPU::SafeWriteMemoryBytes(addr, data)) + { + Error::SetStringFmt(error, "Failed to write {} bytes to address 0x{:08X}", size, addr); + return false; + } + + reader.IncrementPosition(size); + } + } + break; + + case 0x02: + { + // Run address, ignored + DEV_LOG("Ignoring run address 0x{:X}", reader.ReadU32()); + } + break; + + case 0x03: + { + // Set register 32-bit + const u16 reg = reader.ReadU16(); + const u32 value = reader.ReadU32(); + set_register(reg, value); + } + break; + + case 0x04: + { + // Set register 16-bit + const u16 reg = reader.ReadU16(); + const u16 value = reader.ReadU16(); + set_register(reg, value); + } + break; + + case 0x05: + { + // Set register 8-bit + const u16 reg = reader.ReadU16(); + const u8 value = reader.ReadU8(); + set_register(reg, value); + } + break; + + case 0x06: + { + // Set register 24-bit + const u16 reg = reader.ReadU16(); + const u16 low = reader.ReadU16(); + const u8 high = reader.ReadU8(); + set_register(reg, ZeroExtend32(low) | (ZeroExtend32(high) << 16)); + } + break; + + case 0x07: + { + // Select workspace + DEV_LOG("Ignoring set workspace 0x{:X}", reader.ReadU32()); + } + break; + + case 0x08: + { + // Select unit + DEV_LOG("Ignoring select unit 0x{:X}", reader.ReadU8()); + } + break; + + default: + { + WARNING_LOG("Unknown chunk 0x{:02X} in CPE file, parsing will probably fail now.", chunk); + } + break; + } + } + + return true; +} + void Bus::KernelInitializedHook() { if (s_kernel_initialize_hook_run) @@ -1043,23 +1179,40 @@ void Bus::KernelInitializedHook() bool Bus::SideloadEXE(const std::string& path, Error* error) { - // look for a libps.exe next to the exe, if it exists, load it - bool okay = true; - if (const std::string libps_path = Path::BuildRelativePath(path, "libps.exe"); - FileSystem::FileExists(libps_path.c_str())) + const std::optional> exe_data = + FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error); + if (!exe_data.has_value()) { - const std::optional> exe_data = FileSystem::ReadBinaryFile(libps_path.c_str(), error); - okay = (exe_data.has_value() && InjectExecutable(exe_data->cspan(), false, error)); - if (!okay) - Error::AddPrefix(error, "Failed to load libps.exe: "); + Error::AddPrefixFmt(error, "Failed to read {}: ", Path::GetFileName(path)); + return false; } - if (okay) + + bool okay = true; + if (StringUtil::EndsWithNoCase(path, ".cpe")) { - const std::optional> exe_data = - FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error); - okay = (exe_data.has_value() && InjectExecutable(exe_data->cspan(), true, error)); - if (!okay) - Error::AddPrefixFmt(error, "Failed to load {}: ", Path::GetFileName(path)); + okay = InjectCPE(exe_data->cspan(), true, error); + } + else + { + // look for a libps.exe next to the exe, if it exists, load it + if (const std::string libps_path = Path::BuildRelativePath(path, "libps.exe"); + FileSystem::FileExists(libps_path.c_str())) + { + const std::optional> libps_data = FileSystem::ReadBinaryFile(libps_path.c_str(), error); + if (!libps_data.has_value() || !InjectExecutable(libps_data->cspan(), false, error)) + { + Error::AddPrefix(error, "Failed to load libps.exe: "); + return false; + } + } + + okay = InjectExecutable(exe_data->cspan(), true, error); + } + + if (!okay) + { + Error::AddPrefixFmt(error, "Failed to load {}: ", Path::GetFileName(path)); + return false; } return okay; diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 277d8677c..e257bf688 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -3207,6 +3207,11 @@ bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const void* data, u32 return true; } +bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span data) +{ + return SafeWriteMemoryBytes(addr, data.data(), static_cast(data.size())); +} + void* CPU::GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks) { using namespace Bus; diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index 708410ee3..8406839e9 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -184,6 +184,7 @@ bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value); bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value); bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value); bool SafeWriteMemoryBytes(VirtualMemoryAddress addr, const void* data, u32 length); +bool SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span data); // External IRQs void SetIRQRequest(bool state); diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 4cf1e4c9f..d5944a80f 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -170,38 +170,45 @@ bool GameList::IsScannableFilename(std::string_view path) bool GameList::GetExeListEntry(const std::string& path, GameList::Entry* entry) { - std::FILE* fp = FileSystem::OpenCFile(path.c_str(), "rb"); + const auto fp = FileSystem::OpenManagedCFile(path.c_str(), "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) - { - std::fclose(fp); + entry->file_size = FileSystem::FSize64(fp.get()); + entry->uncompressed_size = entry->file_size; + if (entry->file_size < 0) return false; + + entry->title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); + entry->type = EntryType::PSExe; + + if (StringUtil::EndsWithNoCase(path, ".cpe")) + { + u32 magic; + if (std::fread(&magic, sizeof(magic), 1, fp.get()) != 1 || magic != BIOS::CPE_MAGIC) + { + WARNING_LOG("{} is not a valid CPE", path); + return false; + } + + // Who knows + entry->region = DiscRegion::Other; } - - std::fclose(fp); - - if (!BIOS::IsValidPSExeHeader(header, file_size)) + else { - WARNING_LOG("{} is not a valid PS-EXE", path); - return false; + BIOS::PSEXEHeader header; + if (std::fread(&header, sizeof(header), 1, fp.get()) != 1 || + !BIOS::IsValidPSExeHeader(header, static_cast(entry->file_size))) + { + WARNING_LOG("{} is not a valid PS-EXE", path); + return false; + } + + entry->region = BIOS::GetPSExeDiscRegion(header); } const GameHash hash = System::GetGameHashFromFile(path.c_str()); - entry->serial = hash ? System::GetGameHashId(hash) : std::string(); - entry->title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); - entry->region = BIOS::GetPSExeDiscRegion(header); - entry->file_size = ZeroExtend64(file_size); - entry->uncompressed_size = entry->file_size; - entry->type = EntryType::PSExe; - return true; } diff --git a/src/core/system.cpp b/src/core/system.cpp index c15adc053..1a0716025 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -859,7 +859,8 @@ u32 System::GetFrameTimeHistoryPos() bool System::IsExePath(std::string_view path) { return (StringUtil::EndsWithNoCase(path, ".exe") || StringUtil::EndsWithNoCase(path, ".psexe") || - StringUtil::EndsWithNoCase(path, ".ps-exe") || StringUtil::EndsWithNoCase(path, ".psx")); + StringUtil::EndsWithNoCase(path, ".ps-exe") || StringUtil::EndsWithNoCase(path, ".psx") || + StringUtil::EndsWithNoCase(path, ".cpe")); } bool System::IsPsfPath(std::string_view path) @@ -877,7 +878,7 @@ bool System::IsLoadablePath(std::string_view path) { static constexpr const std::array extensions = { ".bin", ".cue", ".img", ".iso", ".chd", ".ecm", ".mds", // discs - ".exe", ".psexe", ".ps-exe", ".psx", // exes + ".exe", ".psexe", ".ps-exe", ".psx", ".cpe", // exes ".psf", ".minipsf", // psf ".psxgpu", ".psxgpu.zst", ".psxgpu.xz", // gpu dump ".m3u", // playlists