Skip to content

Commit

Permalink
System: Support loading ELF files
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek committed Nov 5, 2024
1 parent 65f3dcb commit ead9e56
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/common/log_channels.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
X(DInputSource) \
X(DMA) \
X(DynamicLibrary) \
X(FileLoader) \
X(FileSystem) \
X(FullscreenUI) \
X(GDBProtocol) \
Expand Down Expand Up @@ -55,7 +56,6 @@
X(Multitap) \
X(NeGconRumble) \
X(PCDrv) \
X(PSFLoader) \
X(Pad) \
X(PerfMon) \
X(PlatformMisc) \
Expand Down
54 changes: 44 additions & 10 deletions src/core/bus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "timing_event.h"

#include "util/cd_image.h"
#include "util/elf_file.h"
#include "util/state_wrapper.h"

#include "common/align.h"
Expand Down Expand Up @@ -170,6 +171,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 bool InjectELF(const ELFFile& elf, bool set_pc, Error* error);

static void SetHandlers();
static void UpdateMappedRAMSize();
Expand Down Expand Up @@ -972,15 +974,12 @@ bool Bus::InjectExecutable(std::span<const u8> buffer, bool set_pc, Error* error
return false;
}

if (header.memfill_size > 0)
if (header.memfill_size > 0 &&
!CPU::SafeZeroMemoryBytes(header.memfill_start & ~UINT32_C(3), Common::AlignDownPow2(header.memfill_size, 4)))
{
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);
}
Error::SetStringFmt(error, "Failed to zero {} bytes of memory at address 0x{:08X}.", header.memfill_start,
header.memfill_size);
return false;
}

const u32 data_load_size =
Expand Down Expand Up @@ -1145,6 +1144,34 @@ bool Bus::InjectCPE(std::span<const u8> buffer, bool set_pc, Error* error)
return true;
}

bool Bus::InjectELF(const ELFFile& elf, bool set_pc, Error* error)
{
const bool okay = elf.LoadExecutableSections(
[](std::span<const u8> data, u32 dest_addr, u32 dest_size, Error* error) {
if (!data.empty() && !CPU::SafeWriteMemoryBytes(dest_addr, data))
{
Error::SetStringFmt(error, "Failed to load {} bytes to 0x{:08X}", data.size(), dest_addr);
return false;
}

const u32 zero_addr = dest_addr + static_cast<u32>(data.size());
const u32 zero_bytes = dest_size - static_cast<u32>(data.size());
if (zero_bytes > 0 && !CPU::SafeZeroMemoryBytes(zero_addr, zero_bytes))
{
Error::SetStringFmt(error, "Failed to zero {} bytes at 0x{:08X}", zero_bytes, zero_addr);
return false;
}

return true;
},
error);

if (okay && set_pc)
CPU::SetPC(elf.GetEntryPoint());

return okay;
}

void Bus::KernelInitializedHook()
{
if (s_kernel_initialize_hook_run)
Expand Down Expand Up @@ -1179,8 +1206,7 @@ void Bus::KernelInitializedHook()

bool Bus::SideloadEXE(const std::string& path, Error* error)
{
const std::optional<DynamicHeapArray<u8>> exe_data =
FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error);
std::optional<DynamicHeapArray<u8>> exe_data = FileSystem::ReadBinaryFile(path.c_str(), error);
if (!exe_data.has_value())
{
Error::AddPrefixFmt(error, "Failed to read {}: ", Path::GetFileName(path));
Expand All @@ -1195,6 +1221,14 @@ bool Bus::SideloadEXE(const std::string& path, Error* error)
{
okay = InjectCPE(exe_data->cspan(), true, error);
}
else if (StringUtil::EndsWithNoCase(filename, ".elf"))
{
ELFFile elf;
if (!elf.Open(std::move(exe_data.value()), error))
return false;

okay = InjectELF(elf, true, error);
}
else
{
// look for a libps.exe next to the exe, if it exists, load it
Expand Down
41 changes: 41 additions & 0 deletions src/core/cpu_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3212,6 +3212,47 @@ bool CPU::SafeWriteMemoryBytes(VirtualMemoryAddress addr, const std::span<const
return SafeWriteMemoryBytes(addr, data.data(), static_cast<u32>(data.size()));
}

bool CPU::SafeZeroMemoryBytes(VirtualMemoryAddress addr, u32 length)
{
using namespace Bus;

const u32 seg = (addr >> 29);
if ((seg != 0 && seg != 4 && seg != 5) || (((addr + length) & PHYSICAL_MEMORY_ADDRESS_MASK) >= RAM_MIRROR_END) ||
(((addr & g_ram_mask) + length) > g_ram_size))
{
while ((addr & 3u) != 0 && length > 0)
{
if (!CPU::SafeWriteMemoryByte(addr, 0)) [[unlikely]]
return false;

addr++;
length--;
}
while (length >= 4)
{
if (!CPU::SafeWriteMemoryWord(addr, 0)) [[unlikely]]
return false;

addr += 4;
length -= 4;
}
while (length > 0)
{
if (!CPU::SafeWriteMemoryByte(addr, 0)) [[unlikely]]
return false;

addr++;
length--;
}

return true;
}

// Fast path: all in RAM, no wraparound.
std::memset(&g_ram[addr & g_ram_mask], 0, length);
return true;
}

void* CPU::GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks)
{
using namespace Bus;
Expand Down
1 change: 1 addition & 0 deletions src/core/cpu_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ 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);
bool SafeZeroMemoryBytes(VirtualMemoryAddress addr, u32 length);

// External IRQs
void SetIRQRequest(bool state);
Expand Down
4 changes: 2 additions & 2 deletions src/core/fullscreen_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -949,8 +949,8 @@ void FullscreenUI::DestroyResources()

ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters()
{
return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.mds", "*.psexe",
"*.ps-exe", "*.exe", "*.psx", "*.psf", "*.minipsf", "*.m3u", "*.pbp"};
return {"*.bin", "*.cue", "*.iso", "*.img", "*.chd", "*.ecm", "*.mds", "*.cpe", "*.elf",
"*.psexe", "*.ps-exe", "*.exe", "*.psx", "*.psf", "*.minipsf", "*.m3u", "*.pbp"};
}

void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional<bool> fast_boot)
Expand Down
2 changes: 1 addition & 1 deletion src/core/psf_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

#include <cstring>

LOG_CHANNEL(PSFLoader);
LOG_CHANNEL(FileLoader);

namespace PSFLoader {
static bool LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth = 0);
Expand Down
4 changes: 2 additions & 2 deletions src/core/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ 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, ".cpe"));
StringUtil::EndsWithNoCase(path, ".cpe") || StringUtil::EndsWithNoCase(path, ".elf"));
}

bool System::IsPsfPath(std::string_view path)
Expand All @@ -811,7 +811,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", ".cpe", // exes
".exe", ".psexe", ".ps-exe", ".psx", ".cpe", ".elf", // exes
".psf", ".minipsf", // psf
".psxgpu", ".psxgpu.zst", ".psxgpu.xz", // gpu dump
".m3u", // playlists
Expand Down
11 changes: 6 additions & 5 deletions src/duckstation-qt/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@
LOG_CHANNEL(Host);

static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
"MainWindow", "All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psx *.psf "
"*.minipsf *.m3u *.psxgpu);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD "
"Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images "
"(*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.exe *.psexe *.ps-exe, "
"*.psx);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u);;PSX GPU Dumps (*.psxgpu)");
"MainWindow",
"All File Types (*.bin *.img *.iso *.cue *.chd *.cpe *.ecm *.mds *.pbp *.elf *.exe *.psexe *.ps-exe *.psx *.psf "
"*.minipsf *.m3u *.psxgpu);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images "
"(*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp "
"*.PBP);;PlayStation Executables (*.cpe *.elf *.exe *.psexe *.ps-exe, *.psx);;Portable Sound Format Files (*.psf "
"*.minipsf);;Playlists (*.m3u);;PSX GPU Dumps (*.psxgpu)");

MainWindow* g_main_window = nullptr;

Expand Down
2 changes: 2 additions & 0 deletions src/util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ add_library(util
compress_helpers.h
cue_parser.cpp
cue_parser.h
elf_file.cpp
elf_file.h
gpu_device.cpp
gpu_device.h
gpu_framebuffer_manager.h
Expand Down
Loading

4 comments on commit ead9e56

@crashGG
Copy link
Contributor

@crashGG crashGG commented on ead9e56 Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image
The newest preview will report an error when loading any chd file. The last rolling build is normal.

@andercard0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of my games are in chd format and just tested here, they're working good here with the latest version.

Please verify your dump;
Right click > properties > verify
Red results > bad dump green results are good dump.

also, do check the log to see what is going on on your end while booting the game.

@crashGG
Copy link
Contributor

@crashGG crashGG commented on ead9e56 Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I didn't make it clear. I just tested it again and finally found the problem.
Currently, the latest preview version can only run properly if the chd is compressed with default parameters. If the compression algorithm is specified during compression, the loaded chd will report an error.
An example:
with default parameters compress a chd file:
chdman -i “Crash Bash (USA).cue” -o "Crash Bash (USA).chd"
check the obtained info of Crash Bash (USA).chd
Hunks Percent Name


 7,879    83.2%  CD LZMA
 1,593    16.8%  CD Deflate

this chd file works fine.
But when we specify the compression algorithm as cdzs or cdlz

chdman createcd -c cdzs -i “Crash Bash (USA).cue” -o "Crash Bash (USA)_zstd.chd"
or
chdman createcd -c cdlz -i “Crash Bash (USA).cue” -o "Crash Bash (USA)_lzma.chd"

The obtained two 100% encoded lzma or zstd chd files will cause errors when read by duckstation.

Even if we specify multiple mixed encodings,like this:
chdman createcd -c cdlz,cdzs -i “Crash Bash (USA).cue” -o "Crash Bash (USA)_mix.chd"
the resulting chd file will report an error.

The reason why the encoding format is specified in practice is that although lzma encoding has a high compression rate, it has a slow decoding speed. zstd encoding has the fastest decoding speed. However, chdman does not enable zstd encoding under default parameters for some historical compatibility reasons.

In addition, everything works fine with this commit.

@stenzek
Copy link
Owner Author

@stenzek stenzek commented on ead9e56 Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now.

Please sign in to comment.