mirror of
https://github.com/stenzek/duckstation.git
synced 2025-07-22 18:10:08 +00:00
System: Support loading .CPE files
This commit is contained in:
parent
a8d846ac8f
commit
2d04f2eff9
@ -79,6 +79,9 @@ struct PSEXEHeader
|
|||||||
static_assert(sizeof(PSEXEHeader) == 0x800);
|
static_assert(sizeof(PSEXEHeader) == 0x800);
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// .cpe files
|
||||||
|
static constexpr u32 CPE_MAGIC = 0x01455043;
|
||||||
|
|
||||||
std::optional<Image> LoadImageFromFile(const char* filename, Error* error);
|
std::optional<Image> LoadImageFromFile(const char* filename, Error* error);
|
||||||
|
|
||||||
const ImageInfo* GetInfoForHash(const std::span<const u8> image, const ImageInfo::Hash& hash);
|
const ImageInfo* GetInfoForHash(const std::span<const u8> image, const ImageInfo::Hash& hash);
|
||||||
|
181
src/core/bus.cpp
181
src/core/bus.cpp
@ -27,12 +27,14 @@
|
|||||||
|
|
||||||
#include "common/align.h"
|
#include "common/align.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/binary_reader_writer.h"
|
||||||
#include "common/error.h"
|
#include "common/error.h"
|
||||||
#include "common/file_system.h"
|
#include "common/file_system.h"
|
||||||
#include "common/intrin.h"
|
#include "common/intrin.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/memmap.h"
|
#include "common/memmap.h"
|
||||||
#include "common/path.h"
|
#include "common/path.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@ -167,6 +169,7 @@ static void SetRAMPageWritable(u32 page_index, bool writable);
|
|||||||
|
|
||||||
static void KernelInitializedHook();
|
static void KernelInitializedHook();
|
||||||
static bool SideloadEXE(const std::string& path, Error* error);
|
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 SetHandlers();
|
||||||
static void UpdateMappedRAMSize();
|
static void UpdateMappedRAMSize();
|
||||||
@ -1009,6 +1012,139 @@ bool Bus::InjectExecutable(std::span<const u8> buffer, bool set_pc, Error* error
|
|||||||
return true;
|
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()
|
void Bus::KernelInitializedHook()
|
||||||
{
|
{
|
||||||
if (s_kernel_initialize_hook_run)
|
if (s_kernel_initialize_hook_run)
|
||||||
@ -1043,23 +1179,40 @@ void Bus::KernelInitializedHook()
|
|||||||
|
|
||||||
bool Bus::SideloadEXE(const std::string& path, Error* error)
|
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 =
|
||||||
bool okay = true;
|
FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error);
|
||||||
if (const std::string libps_path = Path::BuildRelativePath(path, "libps.exe");
|
if (!exe_data.has_value())
|
||||||
FileSystem::FileExists(libps_path.c_str()))
|
|
||||||
{
|
{
|
||||||
const std::optional<DynamicHeapArray<u8>> exe_data = FileSystem::ReadBinaryFile(libps_path.c_str(), error);
|
Error::AddPrefixFmt(error, "Failed to read {}: ", Path::GetFileName(path));
|
||||||
okay = (exe_data.has_value() && InjectExecutable(exe_data->cspan(), false, error));
|
return false;
|
||||||
if (!okay)
|
|
||||||
Error::AddPrefix(error, "Failed to load libps.exe: ");
|
|
||||||
}
|
}
|
||||||
if (okay)
|
|
||||||
|
bool okay = true;
|
||||||
|
if (StringUtil::EndsWithNoCase(path, ".cpe"))
|
||||||
{
|
{
|
||||||
const std::optional<DynamicHeapArray<u8>> exe_data =
|
okay = InjectCPE(exe_data->cspan(), true, error);
|
||||||
FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error);
|
}
|
||||||
okay = (exe_data.has_value() && InjectExecutable(exe_data->cspan(), true, error));
|
else
|
||||||
if (!okay)
|
{
|
||||||
Error::AddPrefixFmt(error, "Failed to load {}: ", Path::GetFileName(path));
|
// 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)
|
||||||
|
{
|
||||||
|
Error::AddPrefixFmt(error, "Failed to load {}: ", Path::GetFileName(path));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return okay;
|
return okay;
|
||||||
|
@ -3207,6 +3207,11 @@ bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const void* data, u32
|
|||||||
return true;
|
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)
|
void* CPU::GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks)
|
||||||
{
|
{
|
||||||
using namespace Bus;
|
using namespace Bus;
|
||||||
|
@ -184,6 +184,7 @@ bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value);
|
|||||||
bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
|
bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
|
||||||
bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value);
|
bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value);
|
||||||
bool SafeWriteMemoryBytes(VirtualMemoryAddress addr, const void* data, u32 length);
|
bool SafeWriteMemoryBytes(VirtualMemoryAddress addr, const void* data, u32 length);
|
||||||
|
bool SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span<const u8> data);
|
||||||
|
|
||||||
// External IRQs
|
// External IRQs
|
||||||
void SetIRQRequest(bool state);
|
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)
|
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)
|
if (!fp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::fseek(fp, 0, SEEK_END);
|
entry->file_size = FileSystem::FSize64(fp.get());
|
||||||
const u32 file_size = static_cast<u32>(std::ftell(fp));
|
entry->uncompressed_size = entry->file_size;
|
||||||
std::fseek(fp, 0, SEEK_SET);
|
if (entry->file_size < 0)
|
||||||
|
|
||||||
BIOS::PSEXEHeader header;
|
|
||||||
if (std::fread(&header, sizeof(header), 1, fp) != 1)
|
|
||||||
{
|
|
||||||
std::fclose(fp);
|
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
std::fclose(fp);
|
|
||||||
|
|
||||||
if (!BIOS::IsValidPSExeHeader(header, file_size))
|
|
||||||
{
|
{
|
||||||
WARNING_LOG("{} is not a valid PS-EXE", path);
|
BIOS::PSEXEHeader header;
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->region = BIOS::GetPSExeDiscRegion(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
const GameHash hash = System::GetGameHashFromFile(path.c_str());
|
const GameHash hash = System::GetGameHashFromFile(path.c_str());
|
||||||
|
|
||||||
entry->serial = hash ? System::GetGameHashId(hash) : std::string();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -859,7 +859,8 @@ u32 System::GetFrameTimeHistoryPos()
|
|||||||
bool System::IsExePath(std::string_view path)
|
bool System::IsExePath(std::string_view path)
|
||||||
{
|
{
|
||||||
return (StringUtil::EndsWithNoCase(path, ".exe") || StringUtil::EndsWithNoCase(path, ".psexe") ||
|
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)
|
bool System::IsPsfPath(std::string_view path)
|
||||||
@ -877,7 +878,7 @@ bool System::IsLoadablePath(std::string_view path)
|
|||||||
{
|
{
|
||||||
static constexpr const std::array extensions = {
|
static constexpr const std::array extensions = {
|
||||||
".bin", ".cue", ".img", ".iso", ".chd", ".ecm", ".mds", // discs
|
".bin", ".cue", ".img", ".iso", ".chd", ".ecm", ".mds", // discs
|
||||||
".exe", ".psexe", ".ps-exe", ".psx", // exes
|
".exe", ".psexe", ".ps-exe", ".psx", ".cpe", // exes
|
||||||
".psf", ".minipsf", // psf
|
".psf", ".minipsf", // psf
|
||||||
".psxgpu", ".psxgpu.zst", ".psxgpu.xz", // gpu dump
|
".psxgpu", ".psxgpu.zst", ".psxgpu.xz", // gpu dump
|
||||||
".m3u", // playlists
|
".m3u", // playlists
|
||||||
|
Loading…
x
Reference in New Issue
Block a user