GameList: Support homebrew/psexe files
This commit is contained in:
parent
2c645d9e93
commit
dec475db62
|
@ -176,4 +176,16 @@ bool PatchBIOSForEXE(Image& image, u32 r_pc, u32 r_gp, u32 r_sp, u32 r_fp)
|
||||||
return true;
|
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
|
} // namespace BIOS
|
|
@ -24,6 +24,27 @@ struct Hash
|
||||||
std::string ToString() const;
|
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);
|
Hash GetHash(const Image& image);
|
||||||
std::optional<Image> LoadImageFromFile(std::string_view filename);
|
std::optional<Image> LoadImageFromFile(std::string_view filename);
|
||||||
std::optional<Hash> GetHashForFile(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 PatchBIOSEnableTTY(Image& image, const Hash& hash);
|
||||||
bool PatchBIOSFastBoot(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 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
|
} // namespace BIOS
|
|
@ -1,7 +1,7 @@
|
||||||
#include "game_list.h"
|
#include "game_list.h"
|
||||||
#include "YBaseLib/CString.h"
|
|
||||||
#include "YBaseLib/FileSystem.h"
|
#include "YBaseLib/FileSystem.h"
|
||||||
#include "YBaseLib/Log.h"
|
#include "YBaseLib/Log.h"
|
||||||
|
#include "bios.h"
|
||||||
#include "common/cd_image.h"
|
#include "common/cd_image.h"
|
||||||
#include "common/iso_reader.h"
|
#include "common/iso_reader.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -10,6 +10,12 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
Log_SetChannel(GameList);
|
Log_SetChannel(GameList);
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define CASE_COMPARE _stricmp
|
||||||
|
#else
|
||||||
|
#define CASE_COMPARE strcasecmp
|
||||||
|
#endif
|
||||||
|
|
||||||
GameList::GameList() = default;
|
GameList::GameList() = default;
|
||||||
|
|
||||||
GameList::~GameList() = default;
|
GameList::~GameList() = default;
|
||||||
|
@ -71,8 +77,8 @@ std::string GameList::GetGameCodeForImage(CDImage* cdi)
|
||||||
lines.push_back(std::move(current_line));
|
lines.push_back(std::move(current_line));
|
||||||
|
|
||||||
// Find the BOOT line
|
// Find the BOOT line
|
||||||
auto iter =
|
auto iter = std::find_if(lines.begin(), lines.end(),
|
||||||
std::find_if(lines.begin(), lines.end(), [](const auto& it) { return Y_stricmp(it.first.c_str(), "boot") == 0; });
|
[](const auto& it) { return CASE_COMPARE(it.first.c_str(), "boot") == 0; });
|
||||||
if (iter == lines.end())
|
if (iter == lines.end())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
@ -144,8 +150,67 @@ void GameList::AddDirectory(const char* path, bool recursive)
|
||||||
ScanDirectory(path, 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)
|
bool GameList::GetGameListEntry(const char* path, GameListEntry* entry)
|
||||||
{
|
{
|
||||||
|
if (IsExeFileName(path))
|
||||||
|
return GetExeListEntry(path, entry);
|
||||||
|
|
||||||
std::unique_ptr<CDImage> cdi = CDImage::Open(path);
|
std::unique_ptr<CDImage> cdi = CDImage::Open(path);
|
||||||
if (!cdi)
|
if (!cdi)
|
||||||
return false;
|
return false;
|
||||||
|
@ -153,6 +218,7 @@ bool GameList::GetGameListEntry(const char* path, GameListEntry* entry)
|
||||||
entry->path = path;
|
entry->path = path;
|
||||||
entry->code = GetGameCodeForImage(cdi.get());
|
entry->code = GetGameCodeForImage(cdi.get());
|
||||||
entry->total_size = static_cast<u64>(CDImage::RAW_SECTOR_SIZE) * static_cast<u64>(cdi->GetLBACount());
|
entry->total_size = static_cast<u64>(CDImage::RAW_SECTOR_SIZE) * static_cast<u64>(cdi->GetLBACount());
|
||||||
|
entry->type = EntryType::Disc;
|
||||||
cdi.reset();
|
cdi.reset();
|
||||||
|
|
||||||
auto iter = m_database.find(entry->code);
|
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
|
// if this is a .bin, check if we have a .cue. if there is one, skip it
|
||||||
const char* extension = std::strrchr(ffd.FileName, '.');
|
const char* extension = std::strrchr(ffd.FileName, '.');
|
||||||
if (extension && Y_stricmp(extension, ".bin") == 0)
|
if (extension && CASE_COMPARE(extension, ".bin") == 0)
|
||||||
{
|
{
|
||||||
#if 0
|
#if 0
|
||||||
std::string temp(ffd.FileName, extension - ffd.FileName);
|
std::string temp(ffd.FileName, extension - ffd.FileName);
|
||||||
temp += ".cue";
|
temp += ".cue";
|
||||||
if (std::any_of(files.begin(), files.end(),
|
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());
|
Log_DebugPrintf("Skipping due to '%s' existing", temp.c_str());
|
||||||
continue;
|
continue;
|
||||||
|
@ -237,10 +303,10 @@ public:
|
||||||
bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute) override
|
bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute) override
|
||||||
{
|
{
|
||||||
// recurse into gamelist
|
// recurse into gamelist
|
||||||
if (Y_stricmp(element.Name(), "datafile") == 0)
|
if (CASE_COMPARE(element.Name(), "datafile") == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (Y_stricmp(element.Name(), "game") != 0)
|
if (CASE_COMPARE(element.Name(), "game") != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const char* name = element.Attribute("name");
|
const char* name = element.Attribute("name");
|
||||||
|
|
|
@ -20,6 +20,12 @@ public:
|
||||||
|
|
||||||
using DatabaseMap = std::unordered_map<std::string, GameDatabaseEntry>;
|
using DatabaseMap = std::unordered_map<std::string, GameDatabaseEntry>;
|
||||||
|
|
||||||
|
enum class EntryType
|
||||||
|
{
|
||||||
|
Disc,
|
||||||
|
PSExe
|
||||||
|
};
|
||||||
|
|
||||||
struct GameListEntry
|
struct GameListEntry
|
||||||
{
|
{
|
||||||
std::string path;
|
std::string path;
|
||||||
|
@ -27,6 +33,7 @@ public:
|
||||||
std::string title;
|
std::string title;
|
||||||
u64 total_size;
|
u64 total_size;
|
||||||
ConsoleRegion region;
|
ConsoleRegion region;
|
||||||
|
EntryType type;
|
||||||
};
|
};
|
||||||
|
|
||||||
using EntryList = std::vector<GameListEntry>;
|
using EntryList = std::vector<GameListEntry>;
|
||||||
|
@ -47,6 +54,8 @@ public:
|
||||||
bool ParseRedumpDatabase(const char* redump_dat_path);
|
bool ParseRedumpDatabase(const char* redump_dat_path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static bool IsExeFileName(const char* path);
|
||||||
|
static bool GetExeListEntry(const char* path, GameListEntry* entry);
|
||||||
|
|
||||||
bool GetGameListEntry(const char* path, GameListEntry* entry);
|
bool GetGameListEntry(const char* path, GameListEntry* entry);
|
||||||
|
|
||||||
|
|
|
@ -329,33 +329,16 @@ void System::RunFrame()
|
||||||
|
|
||||||
bool System::LoadEXE(const char* filename, std::vector<u8>& bios_image)
|
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");
|
std::FILE* fp = std::fopen(filename, "rb");
|
||||||
if (!fp)
|
if (!fp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
EXEHeader header;
|
std::fseek(fp, 0, SEEK_END);
|
||||||
if (std::fread(&header, sizeof(header), 1, fp) != 1)
|
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);
|
std::fclose(fp);
|
||||||
return false;
|
return false;
|
||||||
|
@ -374,7 +357,7 @@ bool System::LoadEXE(const char* filename, std::vector<u8>& bios_image)
|
||||||
|
|
||||||
if (header.file_size >= 4)
|
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)
|
if (std::fread(data_words.data(), header.file_size, 1, fp) != 1)
|
||||||
{
|
{
|
||||||
std::fclose(fp);
|
std::fclose(fp);
|
||||||
|
|
Loading…
Reference in New Issue