GameList: Support homebrew/psexe files

This commit is contained in:
Connor McLaughlin 2019-12-04 21:12:50 +10:00
parent 2c645d9e93
commit dec475db62
5 changed files with 124 additions and 31 deletions

View File

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

View File

@ -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<Image> LoadImageFromFile(std::string_view filename);
std::optional<Hash> 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

View File

@ -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 <algorithm>
@ -10,6 +10,12 @@
#include <utility>
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<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);
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<CDImage> 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<u64>(CDImage::RAW_SECTOR_SIZE) * static_cast<u64>(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");

View File

@ -20,6 +20,12 @@ public:
using DatabaseMap = std::unordered_map<std::string, GameDatabaseEntry>;
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<GameListEntry>;
@ -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);

View File

@ -329,33 +329,16 @@ void System::RunFrame()
bool System::LoadEXE(const char* filename, std::vector<u8>& 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<u32>(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<u8>& bios_image)
if (header.file_size >= 4)
{
std::vector<u32> data_words(header.file_size / 4);
std::vector<u32> data_words((header.file_size + 3) / 4);
if (std::fread(data_words.data(), header.file_size, 1, fp) != 1)
{
std::fclose(fp);