diff --git a/src/core/bios.cpp b/src/core/bios.cpp index 15ad4084d..33fee7a29 100644 --- a/src/core/bios.cpp +++ b/src/core/bios.cpp @@ -176,4 +176,16 @@ bool PatchBIOSForEXE(Image& image, u32 r_pc, u32 r_gp, u32 r_sp, u32 r_fp) return true; } +bool IsValidPSExeHeader(const PSEXEHeader& header, u32 file_size) +{ + static constexpr char expected_id[] = {'P', 'S', '-', 'X', ' ', 'E', 'X', 'E'}; + if (std::memcmp(header.id, expected_id, sizeof(expected_id)) != 0) + return false; + + if (header.file_size > file_size) + return false; + + return true; +} + } // namespace BIOS \ No newline at end of file diff --git a/src/core/bios.h b/src/core/bios.h index 31a9c220b..271dd126d 100644 --- a/src/core/bios.h +++ b/src/core/bios.h @@ -24,6 +24,27 @@ struct Hash std::string ToString() const; }; +#pragma pack(push, 1) +struct PSEXEHeader +{ + char id[8]; // 0x000-0x007 PS-X EXE + char pad1[8]; // 0x008-0x00F + u32 initial_pc; // 0x010 + u32 initial_gp; // 0x014 + u32 load_address; // 0x018 + u32 file_size; // 0x01C excluding 0x800-byte header + u32 unk0; // 0x020 + u32 unk1; // 0x024 + u32 memfill_start; // 0x028 + u32 memfill_size; // 0x02C + u32 initial_sp_base; // 0x030 + u32 initial_sp_offset; // 0x034 + u32 reserved[5]; // 0x038-0x04B + char marker[0x7B4]; // 0x04C-0x7FF +}; +static_assert(sizeof(PSEXEHeader) == 0x800); +#pragma pack(pop) + Hash GetHash(const Image& image); std::optional LoadImageFromFile(std::string_view filename); std::optional GetHashForFile(std::string_view filename); @@ -35,4 +56,6 @@ void PatchBIOS(Image& image, u32 address, u32 value, u32 mask = UINT32_C(0xFFFFF bool PatchBIOSEnableTTY(Image& image, const Hash& hash); bool PatchBIOSFastBoot(Image& image, const Hash& hash); bool PatchBIOSForEXE(Image& image, u32 r_pc, u32 r_gp, u32 r_sp, u32 r_fp); + +bool IsValidPSExeHeader(const PSEXEHeader& header, u32 file_size); } // namespace BIOS \ No newline at end of file diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 4a65da2bf..ae087e693 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1,7 +1,7 @@ #include "game_list.h" -#include "YBaseLib/CString.h" #include "YBaseLib/FileSystem.h" #include "YBaseLib/Log.h" +#include "bios.h" #include "common/cd_image.h" #include "common/iso_reader.h" #include @@ -10,6 +10,12 @@ #include Log_SetChannel(GameList); +#ifdef _MSC_VER +#define CASE_COMPARE _stricmp +#else +#define CASE_COMPARE strcasecmp +#endif + GameList::GameList() = default; GameList::~GameList() = default; @@ -71,8 +77,8 @@ std::string GameList::GetGameCodeForImage(CDImage* cdi) lines.push_back(std::move(current_line)); // Find the BOOT line - auto iter = - std::find_if(lines.begin(), lines.end(), [](const auto& it) { return Y_stricmp(it.first.c_str(), "boot") == 0; }); + auto iter = std::find_if(lines.begin(), lines.end(), + [](const auto& it) { return CASE_COMPARE(it.first.c_str(), "boot") == 0; }); if (iter == lines.end()) return {}; @@ -144,8 +150,67 @@ void GameList::AddDirectory(const char* path, bool recursive) ScanDirectory(path, recursive); } +bool GameList::IsExeFileName(const char* path) +{ + const char* extension = std::strrchr(path, '.'); + return (extension && (CASE_COMPARE(extension, ".exe") == 0 || CASE_COMPARE(extension, ".psexe") == 0)); +} + +bool GameList::GetExeListEntry(const char* path, GameListEntry* entry) +{ + std::FILE* fp = std::fopen(path, "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); + return false; + } + + std::fclose(fp); + + if (!BIOS::IsValidPSExeHeader(header, file_size)) + { + Log_DebugPrintf("%s is not a valid PS-EXE", path); + return false; + } + + const char* extension = std::strrchr(path, '.'); + if (!extension) + return false; + + const char* title_start = std::max(std::strrchr(path, '/'), std::strrchr(path, '\\')); + if (!title_start) + { + entry->title = path; + entry->code = std::string(path, extension - path - 1); + } + else + { + entry->title = title_start + 1; + entry->code = std::string(title_start + 1, extension - title_start - 1); + } + + // no way to detect region... + entry->path = path; + entry->region = ConsoleRegion::NTSC_U; + entry->total_size = ZeroExtend64(file_size); + entry->type = EntryType::PSExe; + + return true; +} + bool GameList::GetGameListEntry(const char* path, GameListEntry* entry) { + if (IsExeFileName(path)) + return GetExeListEntry(path, entry); + std::unique_ptr cdi = CDImage::Open(path); if (!cdi) return false; @@ -153,6 +218,7 @@ bool GameList::GetGameListEntry(const char* path, GameListEntry* entry) entry->path = path; entry->code = GetGameCodeForImage(cdi.get()); entry->total_size = static_cast(CDImage::RAW_SECTOR_SIZE) * static_cast(cdi->GetLBACount()); + entry->type = EntryType::Disc; cdi.reset(); auto iter = m_database.find(entry->code); @@ -185,13 +251,13 @@ void GameList::ScanDirectory(const char* path, bool recursive) // if this is a .bin, check if we have a .cue. if there is one, skip it const char* extension = std::strrchr(ffd.FileName, '.'); - if (extension && Y_stricmp(extension, ".bin") == 0) + if (extension && CASE_COMPARE(extension, ".bin") == 0) { #if 0 std::string temp(ffd.FileName, extension - ffd.FileName); temp += ".cue"; if (std::any_of(files.begin(), files.end(), - [&temp](const FILESYSTEM_FIND_DATA& it) { return Y_stricmp(it.FileName, temp.c_str()) == 0; })) + [&temp](const FILESYSTEM_FIND_DATA& it) { return CASE_COMPARE(it.FileName, temp.c_str()) == 0; })) { Log_DebugPrintf("Skipping due to '%s' existing", temp.c_str()); continue; @@ -237,10 +303,10 @@ public: bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute) override { // recurse into gamelist - if (Y_stricmp(element.Name(), "datafile") == 0) + if (CASE_COMPARE(element.Name(), "datafile") == 0) return true; - if (Y_stricmp(element.Name(), "game") != 0) + if (CASE_COMPARE(element.Name(), "game") != 0) return false; const char* name = element.Attribute("name"); diff --git a/src/core/game_list.h b/src/core/game_list.h index ab81e5d09..c39d0d17b 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -20,6 +20,12 @@ public: using DatabaseMap = std::unordered_map; + enum class EntryType + { + Disc, + PSExe + }; + struct GameListEntry { std::string path; @@ -27,6 +33,7 @@ public: std::string title; u64 total_size; ConsoleRegion region; + EntryType type; }; using EntryList = std::vector; @@ -47,6 +54,8 @@ public: bool ParseRedumpDatabase(const char* redump_dat_path); private: + static bool IsExeFileName(const char* path); + static bool GetExeListEntry(const char* path, GameListEntry* entry); bool GetGameListEntry(const char* path, GameListEntry* entry); diff --git a/src/core/system.cpp b/src/core/system.cpp index 992cc73a7..8aa4a3dae 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -329,33 +329,16 @@ void System::RunFrame() bool System::LoadEXE(const char* filename, std::vector& bios_image) { -#pragma pack(push, 1) - struct EXEHeader - { - char id[8]; // 0x000-0x007 PS-X EXE - char pad1[8]; // 0x008-0x00F - u32 initial_pc; // 0x010 - u32 initial_gp; // 0x014 - u32 load_address; // 0x018 - u32 file_size; // 0x01C excluding 0x800-byte header - u32 unk0; // 0x020 - u32 unk1; // 0x024 - u32 memfill_start; // 0x028 - u32 memfill_size; // 0x02C - u32 initial_sp_base; // 0x030 - u32 initial_sp_offset; // 0x034 - u32 reserved[5]; // 0x038-0x04B - char marker[0x7B4]; // 0x04C-0x7FF - }; - static_assert(sizeof(EXEHeader) == 0x800); -#pragma pack(pop) - std::FILE* fp = std::fopen(filename, "rb"); if (!fp) return false; - EXEHeader header; - if (std::fread(&header, sizeof(header), 1, fp) != 1) + 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; @@ -374,7 +357,7 @@ bool System::LoadEXE(const char* filename, std::vector& bios_image) if (header.file_size >= 4) { - std::vector data_words(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);