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;
|
||||
}
|
||||
|
||||
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
|
|
@ -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
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue