System: Support loading .CPE files

pull/3322/head
Stenzek 4 months ago
parent a8d846ac8f
commit 2d04f2eff9
No known key found for this signature in database

@ -79,6 +79,9 @@ struct PSEXEHeader
static_assert(sizeof(PSEXEHeader) == 0x800);
#pragma pack(pop)
// .cpe files
static constexpr u32 CPE_MAGIC = 0x01455043;
std::optional<Image> LoadImageFromFile(const char* filename, Error* error);
const ImageInfo* GetInfoForHash(const std::span<const u8> image, const ImageInfo::Hash& hash);

@ -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 <cstdio>
#include <tuple>
@ -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<const u8> buffer, bool set_pc, Error* error);
static void SetHandlers();
static void UpdateMappedRAMSize();
@ -1009,6 +1012,139 @@ bool Bus::InjectExecutable(std::span<const u8> buffer, bool set_pc, Error* error
return true;
}
bool Bus::InjectCPE(std::span<const u8> 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
const std::optional<DynamicHeapArray<u8>> exe_data =
FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error);
if (!exe_data.has_value())
{
Error::AddPrefixFmt(error, "Failed to read {}: ", Path::GetFileName(path));
return false;
}
bool okay = true;
if (const std::string libps_path = Path::BuildRelativePath(path, "libps.exe");
FileSystem::FileExists(libps_path.c_str()))
if (StringUtil::EndsWithNoCase(path, ".cpe"))
{
const std::optional<DynamicHeapArray<u8>> 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: ");
okay = InjectCPE(exe_data->cspan(), true, error);
}
if (okay)
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<DynamicHeapArray<u8>> 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)
{
const std::optional<DynamicHeapArray<u8>> 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));
Error::AddPrefixFmt(error, "Failed to load {}: ", Path::GetFileName(path));
return false;
}
return okay;

@ -3207,6 +3207,11 @@ bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const void* data, u32
return true;
}
bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span<const u8> data)
{
return SafeWriteMemoryBytes(addr, data.data(), static_cast<u32>(data.size()));
}
void* CPU::GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks)
{
using namespace Bus;

@ -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<const u8> data);
// External IRQs
void SetIRQRequest(bool state);

@ -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<u32>(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;
}
std::fclose(fp);
entry->title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path));
entry->type = EntryType::PSExe;
if (!BIOS::IsValidPSExeHeader(header, file_size))
if (StringUtil::EndsWithNoCase(path, ".cpe"))
{
WARNING_LOG("{} is not a valid PS-EXE", path);
return false;
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;
}
else
{
BIOS::PSEXEHeader header;
if (std::fread(&header, sizeof(header), 1, fp.get()) != 1 ||
!BIOS::IsValidPSExeHeader(header, static_cast<size_t>(entry->file_size)))
{
WARNING_LOG("{} is not a valid PS-EXE", path);
return false;
}
const GameHash hash = System::GetGameHashFromFile(path.c_str());
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;
}

@ -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

Loading…
Cancel
Save