2023-11-18 06:21:51 +00:00
|
|
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
2022-12-04 11:03:45 +00:00
|
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
#include "cd_image.h"
|
|
|
|
#include "cd_subchannel_replacement.h"
|
2023-11-18 06:21:51 +00:00
|
|
|
|
2022-07-08 12:43:38 +00:00
|
|
|
#include "common/assert.h"
|
|
|
|
#include "common/error.h"
|
|
|
|
#include "common/file_system.h"
|
|
|
|
#include "common/log.h"
|
|
|
|
#include "common/path.h"
|
|
|
|
#include "common/string_util.h"
|
2023-11-18 06:21:51 +00:00
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
#include "zlib.h"
|
2023-11-18 06:21:51 +00:00
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
#include <array>
|
|
|
|
#include <cstdio>
|
2023-11-18 06:21:51 +00:00
|
|
|
#include <cstring>
|
|
|
|
#include <map>
|
|
|
|
#include <string>
|
|
|
|
#include <variant>
|
2021-03-12 07:24:12 +00:00
|
|
|
#include <vector>
|
2023-11-18 06:21:51 +00:00
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
Log_SetChannel(CDImagePBP);
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
enum : u32
|
|
|
|
{
|
|
|
|
PBP_HEADER_OFFSET_COUNT = 8u,
|
|
|
|
TOC_NUM_ENTRIES = 102u,
|
|
|
|
BLOCK_TABLE_NUM_ENTRIES = 32256u,
|
|
|
|
DISC_TABLE_NUM_ENTRIES = 5u,
|
|
|
|
DECOMPRESSED_BLOCK_SIZE = 37632u // 2352 bytes per sector * 16 sectors per block
|
|
|
|
};
|
|
|
|
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
|
|
|
|
struct PBPHeader
|
|
|
|
{
|
|
|
|
u8 magic[4]; // "\0PBP"
|
|
|
|
u32 version;
|
|
|
|
|
|
|
|
union
|
|
|
|
{
|
|
|
|
u32 offsets[PBP_HEADER_OFFSET_COUNT];
|
|
|
|
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
u32 param_sfo_offset; // 0x00000028
|
|
|
|
u32 icon0_png_offset;
|
|
|
|
u32 icon1_png_offset;
|
|
|
|
u32 pic0_png_offset;
|
|
|
|
u32 pic1_png_offset;
|
|
|
|
u32 snd0_at3_offset;
|
|
|
|
u32 data_psp_offset;
|
|
|
|
u32 data_psar_offset;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
static_assert(sizeof(PBPHeader) == 0x28);
|
|
|
|
|
|
|
|
struct SFOHeader
|
|
|
|
{
|
|
|
|
u8 magic[4]; // "\0PSF"
|
|
|
|
u32 version;
|
|
|
|
u32 key_table_offset; // Relative to start of SFOHeader, 0x000000A4 expected
|
|
|
|
u32 data_table_offset; // Relative to start of SFOHeader, 0x00000100 expected
|
|
|
|
u32 num_table_entries; // 0x00000009
|
|
|
|
};
|
|
|
|
static_assert(sizeof(SFOHeader) == 0x14);
|
|
|
|
|
|
|
|
struct SFOIndexTableEntry
|
|
|
|
{
|
|
|
|
u16 key_offset; // Relative to key_table_offset
|
|
|
|
u16 data_type;
|
|
|
|
u32 data_size; // Size of actual data in bytes
|
|
|
|
u32 data_total_size; // Size of data field in bytes, data_total_size >= data_size
|
|
|
|
u32 data_offset; // Relative to data_table_offset
|
|
|
|
};
|
|
|
|
static_assert(sizeof(SFOIndexTableEntry) == 0x10);
|
|
|
|
|
|
|
|
using SFOIndexTable = std::vector<SFOIndexTableEntry>;
|
|
|
|
using SFOTableDataValue = std::variant<std::string, u32>;
|
|
|
|
using SFOTable = std::map<std::string, SFOTableDataValue>;
|
|
|
|
|
|
|
|
struct BlockTableEntry
|
|
|
|
{
|
|
|
|
u32 offset;
|
|
|
|
u16 size;
|
|
|
|
u16 marker;
|
|
|
|
u8 checksum[0x10];
|
|
|
|
u64 padding;
|
|
|
|
};
|
|
|
|
static_assert(sizeof(BlockTableEntry) == 0x20);
|
|
|
|
|
|
|
|
struct TOCEntry
|
|
|
|
{
|
|
|
|
struct Timecode
|
|
|
|
{
|
|
|
|
u8 m;
|
|
|
|
u8 s;
|
|
|
|
u8 f;
|
|
|
|
};
|
|
|
|
|
|
|
|
u8 type;
|
|
|
|
u8 unknown;
|
|
|
|
u8 point;
|
|
|
|
Timecode pregap_start;
|
|
|
|
u8 zero;
|
|
|
|
Timecode userdata_start;
|
|
|
|
};
|
|
|
|
static_assert(sizeof(TOCEntry) == 0x0A);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
struct AudioTrackTableEntry
|
|
|
|
{
|
|
|
|
u32 block_offset;
|
|
|
|
u32 block_size;
|
|
|
|
u32 block_padding;
|
|
|
|
u32 block_checksum;
|
|
|
|
};
|
|
|
|
static_assert(sizeof(CDDATrackTableEntry) == 0x10);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#pragma pack(pop)
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
class CDImagePBP final : public CDImage
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CDImagePBP() = default;
|
|
|
|
~CDImagePBP() override;
|
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
bool Open(const char* filename, Error* error);
|
2021-03-12 07:24:12 +00:00
|
|
|
|
2021-03-26 16:19:23 +00:00
|
|
|
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
|
2021-03-12 07:24:12 +00:00
|
|
|
bool HasNonStandardSubchannel() const override;
|
2023-12-20 13:40:24 +00:00
|
|
|
s64 GetSizeOnDisk() const override;
|
2021-03-12 07:24:12 +00:00
|
|
|
|
2021-03-26 17:34:50 +00:00
|
|
|
bool HasSubImages() const override;
|
|
|
|
u32 GetSubImageCount() const override;
|
|
|
|
u32 GetCurrentSubImage() const override;
|
2023-08-19 13:40:36 +00:00
|
|
|
bool SwitchSubImage(u32 index, Error* error) override;
|
2021-03-26 17:34:50 +00:00
|
|
|
std::string GetMetadata(const std::string_view& type) const override;
|
|
|
|
std::string GetSubImageMetadata(u32 index, const std::string_view& type) const override;
|
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
protected:
|
|
|
|
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
struct BlockInfo
|
|
|
|
{
|
|
|
|
u32 offset; // Absolute offset from start of file
|
|
|
|
u16 size;
|
|
|
|
};
|
|
|
|
|
|
|
|
#if _DEBUG
|
|
|
|
static void PrintPBPHeaderInfo(const PBPHeader& pbp_header);
|
|
|
|
static void PrintSFOHeaderInfo(const SFOHeader& sfo_header);
|
|
|
|
static void PrintSFOIndexTableEntry(const SFOIndexTableEntry& sfo_index_table_entry, size_t i);
|
|
|
|
static void PrintSFOTable(const SFOTable& sfo_table);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
bool LoadPBPHeader();
|
|
|
|
bool LoadSFOHeader();
|
|
|
|
bool LoadSFOIndexTable();
|
|
|
|
bool LoadSFOTable();
|
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
bool IsValidEboot(Error* error);
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
bool InitDecompressionStream();
|
2021-04-07 08:30:51 +00:00
|
|
|
bool DecompressBlock(const BlockInfo& block_info);
|
2021-03-12 07:24:12 +00:00
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
bool OpenDisc(u32 index, Error* error);
|
2021-03-18 23:51:09 +00:00
|
|
|
|
2021-03-26 17:34:50 +00:00
|
|
|
static const std::string* LookupStringSFOTableEntry(const char* key, const SFOTable& table);
|
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
FILE* m_file = nullptr;
|
|
|
|
|
|
|
|
PBPHeader m_pbp_header;
|
|
|
|
SFOHeader m_sfo_header;
|
|
|
|
SFOTable m_sfo_table;
|
|
|
|
SFOIndexTable m_sfo_index_table;
|
|
|
|
|
2021-03-18 23:51:09 +00:00
|
|
|
// Absolute offsets to ISO headers, size is the number of discs in the file
|
|
|
|
std::vector<u32> m_disc_offsets;
|
2021-03-26 17:34:50 +00:00
|
|
|
u32 m_current_disc = 0;
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
// Absolute offsets and sizes of blocks in m_file
|
|
|
|
std::array<BlockInfo, BLOCK_TABLE_NUM_ENTRIES> m_blockinfo_table;
|
|
|
|
|
|
|
|
std::array<TOCEntry, TOC_NUM_ENTRIES> m_toc;
|
|
|
|
|
|
|
|
u32 m_current_block = static_cast<u32>(-1);
|
|
|
|
std::array<u8, DECOMPRESSED_BLOCK_SIZE> m_decompressed_block;
|
|
|
|
std::vector<u8> m_compressed_block;
|
|
|
|
|
|
|
|
z_stream m_inflate_stream;
|
|
|
|
|
|
|
|
CDSubChannelReplacement m_sbi;
|
|
|
|
};
|
2023-11-18 06:21:51 +00:00
|
|
|
} // namespace
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
CDImagePBP::~CDImagePBP()
|
|
|
|
{
|
|
|
|
if (m_file)
|
|
|
|
fclose(m_file);
|
|
|
|
|
|
|
|
inflateEnd(&m_inflate_stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CDImagePBP::LoadPBPHeader()
|
|
|
|
{
|
|
|
|
if (!m_file)
|
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, 0, SEEK_END) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FTell64(m_file) < 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, 0, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(&m_pbp_header, sizeof(PBPHeader), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrint("Unable to read PBP header");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::strncmp((char*)m_pbp_header.magic, "\0PBP", 4) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrint("PBP magic number mismatch");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if _DEBUG
|
|
|
|
PrintPBPHeaderInfo(m_pbp_header);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CDImagePBP::LoadSFOHeader()
|
|
|
|
{
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, m_pbp_header.param_sfo_offset, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(&m_sfo_header, sizeof(SFOHeader), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::strncmp((char*)m_sfo_header.magic, "\0PSF", 4) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrint("SFO magic number mismatch");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if _DEBUG
|
|
|
|
PrintSFOHeaderInfo(m_sfo_header);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CDImagePBP::LoadSFOIndexTable()
|
|
|
|
{
|
|
|
|
m_sfo_index_table.clear();
|
|
|
|
m_sfo_index_table.resize(m_sfo_header.num_table_entries);
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, m_pbp_header.param_sfo_offset + sizeof(m_sfo_header), SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(m_sfo_index_table.data(), sizeof(SFOIndexTableEntry), m_sfo_header.num_table_entries, m_file) !=
|
2021-03-12 07:24:12 +00:00
|
|
|
m_sfo_header.num_table_entries)
|
2023-11-18 06:21:51 +00:00
|
|
|
{
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
2023-11-18 06:21:51 +00:00
|
|
|
}
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
#if _DEBUG
|
|
|
|
for (size_t i = 0; i < static_cast<size_t>(m_sfo_header.num_table_entries); ++i)
|
|
|
|
PrintSFOIndexTableEntry(m_sfo_index_table[i], i);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CDImagePBP::LoadSFOTable()
|
|
|
|
{
|
|
|
|
m_sfo_table.clear();
|
|
|
|
|
|
|
|
for (size_t i = 0; i < static_cast<size_t>(m_sfo_header.num_table_entries); ++i)
|
|
|
|
{
|
|
|
|
u32 abs_key_offset =
|
|
|
|
m_pbp_header.param_sfo_offset + m_sfo_header.key_table_offset + m_sfo_index_table[i].key_offset;
|
|
|
|
u32 abs_data_offset =
|
|
|
|
m_pbp_header.param_sfo_offset + m_sfo_header.data_table_offset + m_sfo_index_table[i].data_offset;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, abs_key_offset, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed seek to key for SFO table entry %zu", i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Longest known key string is 20 characters total, including the null character
|
|
|
|
char key_cstr[20] = {};
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fgets(key_cstr, sizeof(key_cstr), m_file) == nullptr)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed to read key string for SFO table entry %zu", i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, abs_data_offset, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed seek to data for SFO table entry %zu", i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_sfo_index_table[i].data_type == 0x0004) // "special mode" UTF-8 (not null terminated)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Unhandled special mode UTF-8 type found in SFO table for entry %zu", i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (m_sfo_index_table[i].data_type == 0x0204) // null-terminated UTF-8 character string
|
|
|
|
{
|
|
|
|
std::vector<char> data_cstr(m_sfo_index_table[i].data_size);
|
|
|
|
if (fgets(data_cstr.data(), static_cast<int>(data_cstr.size() * sizeof(char)), m_file) == nullptr)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed to read data string for SFO table entry %zu", i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_sfo_table.emplace(std::string(key_cstr), std::string(data_cstr.data()));
|
|
|
|
}
|
|
|
|
else if (m_sfo_index_table[i].data_type == 0x0404) // uint32_t
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
if (fread(&val, sizeof(u32), 1, m_file) != 1)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed to read unsigned data value for SFO table entry %zu", i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_sfo_table.emplace(std::string(key_cstr), val);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Unhandled SFO data type 0x%04X found in SFO table for entry %zu", m_sfo_index_table[i].data_type,
|
|
|
|
i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if _DEBUG
|
|
|
|
PrintSFOTable(m_sfo_table);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
bool CDImagePBP::IsValidEboot(Error* error)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
// Check some fields to make sure this is a valid PS1 EBOOT.PBP
|
|
|
|
|
|
|
|
auto a_it = m_sfo_table.find("BOOTABLE");
|
|
|
|
if (a_it != m_sfo_table.end())
|
|
|
|
{
|
|
|
|
SFOTableDataValue data_value = a_it->second;
|
|
|
|
if (!std::holds_alternative<u32>(data_value) || std::get<u32>(data_value) != 1)
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Invalid BOOTABLE value");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Invalid BOOTABLE value");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("No BOOTABLE value found");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "No BOOTABLE value found");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
a_it = m_sfo_table.find("CATEGORY");
|
|
|
|
if (a_it != m_sfo_table.end())
|
|
|
|
{
|
|
|
|
SFOTableDataValue data_value = a_it->second;
|
|
|
|
if (!std::holds_alternative<std::string>(data_value) || std::get<std::string>(data_value) != "ME")
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Invalid CATEGORY value");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Invalid CATEGORY value");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("No CATEGORY value found");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "No CATEGORY value found");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
bool CDImagePBP::Open(const char* filename, Error* error)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
m_file = FileSystem::OpenCFile(filename, "rb");
|
|
|
|
if (!m_file)
|
2021-03-18 15:52:00 +00:00
|
|
|
{
|
|
|
|
if (error)
|
|
|
|
error->SetErrno(errno);
|
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
2021-03-18 15:52:00 +00:00
|
|
|
}
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
m_filename = filename;
|
|
|
|
|
|
|
|
// Read in PBP header
|
|
|
|
if (!LoadPBPHeader())
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Failed to load PBP header");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Failed to load PBP header");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read in SFO header
|
|
|
|
if (!LoadSFOHeader())
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Failed to load SFO header");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Failed to load SFO header");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read in SFO index table
|
|
|
|
if (!LoadSFOIndexTable())
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Failed to load SFO index table");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Failed to load SFO index table");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read in SFO table
|
|
|
|
if (!LoadSFOTable())
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Failed to load SFO table");
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Failed to load SFO table");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since PBP files can store things that aren't PS1 CD images, make sure we're loading the right kind
|
2021-03-18 15:52:00 +00:00
|
|
|
if (!IsValidEboot(error))
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrint("Couldn't validate EBOOT");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start parsing ISO stuff
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, m_pbp_header.data_psar_offset, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Check "PSTITLEIMG000000" for multi-disc
|
|
|
|
char data_psar_magic[16] = {};
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(data_psar_magic, sizeof(data_psar_magic), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::strncmp(data_psar_magic, "PSTITLEIMG000000", 16) == 0) // Multi-disc header found
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
// For multi-disc, the five disc offsets are located at data_psar_offset + 0x200. Non-present discs have an offset
|
|
|
|
// of 0. There are also some disc hashes, a serial (from one of the discs, but used as an identifier for the entire
|
|
|
|
// "title image" header), and some other offsets, but we don't really need to check those
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, m_pbp_header.data_psar_offset + 0x200, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
u32 disc_table[DISC_TABLE_NUM_ENTRIES] = {};
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(disc_table, sizeof(u32), DISC_TABLE_NUM_ENTRIES, m_file) != DISC_TABLE_NUM_ENTRIES)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Ignore encrypted files
|
|
|
|
if (disc_table[0] == 0x44475000) // "\0PGD"
|
2021-03-18 03:24:04 +00:00
|
|
|
{
|
2021-03-18 23:51:09 +00:00
|
|
|
Log_ErrorPrintf("Encrypted PBP images are not supported, skipping %s", m_filename.c_str());
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Encrypted PBP images are not supported");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
2021-03-18 03:24:04 +00:00
|
|
|
}
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
// Convert relative offsets to absolute offsets for available discs
|
|
|
|
for (u32 i = 0; i < DISC_TABLE_NUM_ENTRIES; i++)
|
|
|
|
{
|
|
|
|
if (disc_table[i] != 0)
|
2021-03-18 23:51:09 +00:00
|
|
|
m_disc_offsets.push_back(m_pbp_header.data_psar_offset + disc_table[i]);
|
2021-03-12 07:24:12 +00:00
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-03-28 08:59:57 +00:00
|
|
|
if (m_disc_offsets.size() < 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
2021-03-18 23:51:09 +00:00
|
|
|
Log_ErrorPrintf("Invalid number of discs (%u) in multi-disc PBP file", static_cast<u32>(m_disc_offsets.size()));
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-03-18 23:51:09 +00:00
|
|
|
}
|
|
|
|
else // Single-disc
|
|
|
|
{
|
|
|
|
m_disc_offsets.push_back(m_pbp_header.data_psar_offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default to first disc for now
|
|
|
|
return OpenDisc(0, error);
|
|
|
|
}
|
2021-03-12 07:24:12 +00:00
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
bool CDImagePBP::OpenDisc(u32 index, Error* error)
|
2021-03-18 23:51:09 +00:00
|
|
|
{
|
|
|
|
if (index >= m_disc_offsets.size())
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("File does not contain disc %u", index + 1);
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, fmt::format("File does not contain disc {}", index + 1));
|
2021-03-18 23:51:09 +00:00
|
|
|
return false;
|
2021-03-12 07:24:12 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 23:51:09 +00:00
|
|
|
m_current_block = static_cast<u32>(-1);
|
|
|
|
m_blockinfo_table.fill({});
|
|
|
|
m_toc.fill({});
|
|
|
|
m_decompressed_block.fill(0x00);
|
|
|
|
m_compressed_block.clear();
|
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
// Go to ISO header
|
2021-03-18 23:51:09 +00:00
|
|
|
const u32 iso_header_start = m_disc_offsets[index];
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, iso_header_start, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
char iso_header_magic[12] = {};
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(iso_header_magic, sizeof(iso_header_magic), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::strncmp(iso_header_magic, "PSISOIMG0000", 12) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
Log_ErrorPrint("ISO header magic number mismatch");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore encrypted files
|
|
|
|
u32 pgd_magic;
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, iso_header_start + 0x400, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(&pgd_magic, sizeof(pgd_magic), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (pgd_magic == 0x44475000) // "\0PGD"
|
2021-03-18 03:24:04 +00:00
|
|
|
{
|
2021-03-18 23:51:09 +00:00
|
|
|
Log_ErrorPrintf("Encrypted PBP images are not supported, skipping %s", m_filename.c_str());
|
2023-08-19 13:40:36 +00:00
|
|
|
Error::SetString(error, "Encrypted PBP images are not supported");
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
2021-03-18 03:24:04 +00:00
|
|
|
}
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
// Read in the TOC
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, iso_header_start + 0x800, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
for (u32 i = 0; i < TOC_NUM_ENTRIES; i++)
|
|
|
|
{
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(&m_toc[i], sizeof(m_toc[i]), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For homebrew EBOOTs, audio track table doesn't exist -- the data track block table will point to compressed blocks
|
|
|
|
// for both data and audio
|
|
|
|
|
|
|
|
// Get the offset of the compressed iso
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, iso_header_start + 0xBFC, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
u32 iso_offset;
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(&iso_offset, sizeof(iso_offset), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Generate block info table
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, iso_header_start + 0x4000, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
for (u32 i = 0; i < BLOCK_TABLE_NUM_ENTRIES; i++)
|
|
|
|
{
|
|
|
|
BlockTableEntry bte;
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(&bte, sizeof(bte), 1, m_file) != 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Only store absolute file offset into a BlockInfo if this is a valid block
|
|
|
|
m_blockinfo_table[i] = {(bte.size != 0) ? (iso_header_start + iso_offset + bte.offset) : 0, bte.size};
|
|
|
|
|
|
|
|
// printf("Block %u, file offset %u, size %u\n", i, m_blockinfo_table[i].offset, m_blockinfo_table[i].size);
|
|
|
|
}
|
|
|
|
|
|
|
|
// iso_header_start + 0x12D4, 0x12D6, 0x12D8 supposedly contain data on block size, num clusters, and num blocks
|
|
|
|
// Might be useful for error checking, but probably not that important as of now
|
|
|
|
|
|
|
|
// Ignore track types for first three TOC entries, these don't seem to be consistent, but check that the points are
|
|
|
|
// valid. Not sure what m_toc[0].userdata_start.s encodes on homebrew EBOOTs though, so ignore that
|
|
|
|
if (m_toc[0].point != 0xA0 || m_toc[1].point != 0xA1 || m_toc[2].point != 0xA2)
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Invalid points on information tracks");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const u8 first_track = PackedBCDToBinary(m_toc[0].userdata_start.m);
|
|
|
|
const u8 last_track = PackedBCDToBinary(m_toc[1].userdata_start.m);
|
|
|
|
const LBA sectors_on_file =
|
|
|
|
Position::FromBCD(m_toc[2].userdata_start.m, m_toc[2].userdata_start.s, m_toc[2].userdata_start.f).ToLBA();
|
|
|
|
|
|
|
|
if (first_track != 1 || last_track < first_track)
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Invalid starting track number or track count");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We assume that the pregap for the data track (track 1) is not on file, but pregaps for any additional tracks are on
|
|
|
|
// file. Also, homebrew tools seem to create 2 second pregaps for audio tracks, even when the audio track has a pregap
|
|
|
|
// that isn't 2 seconds long. We don't have a good way to validate this, and have to assume the TOC is giving us
|
|
|
|
// correct pregap lengths...
|
|
|
|
|
2021-03-26 17:34:50 +00:00
|
|
|
ClearTOC();
|
2021-03-12 07:24:12 +00:00
|
|
|
m_lba_count = sectors_on_file;
|
|
|
|
LBA track1_pregap_frames = 0;
|
|
|
|
for (u32 curr_track = 1; curr_track <= last_track; curr_track++)
|
|
|
|
{
|
|
|
|
// Load in all the user stuff to m_tracks and m_indices
|
|
|
|
const TOCEntry& t = m_toc[static_cast<size_t>(curr_track) + 2];
|
|
|
|
const u8 track_num = PackedBCDToBinary(t.point);
|
|
|
|
if (track_num != curr_track)
|
2021-04-01 06:26:16 +00:00
|
|
|
Log_WarningPrintf("Mismatched TOC track number, expected %u but got %u", static_cast<u32>(curr_track), track_num);
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
const bool is_audio_track = t.type == 0x01;
|
|
|
|
const bool is_first_track = curr_track == 1;
|
|
|
|
const bool is_last_track = curr_track == last_track;
|
|
|
|
const TrackMode track_mode = is_audio_track ? TrackMode::Audio : TrackMode::Mode2Raw;
|
|
|
|
const u32 track_sector_size = GetBytesPerSector(track_mode);
|
|
|
|
|
|
|
|
SubChannelQ::Control track_control = {};
|
|
|
|
track_control.data = !is_audio_track;
|
|
|
|
|
2021-05-12 17:20:39 +00:00
|
|
|
LBA pregap_start = Position::FromBCD(t.pregap_start.m, t.pregap_start.s, t.pregap_start.f).ToLBA();
|
|
|
|
LBA userdata_start = Position::FromBCD(t.userdata_start.m, t.userdata_start.s, t.userdata_start.f).ToLBA();
|
2021-04-01 06:26:16 +00:00
|
|
|
LBA pregap_frames;
|
|
|
|
u32 pregap_sector_size;
|
2021-03-12 07:24:12 +00:00
|
|
|
|
|
|
|
if (userdata_start < pregap_start)
|
|
|
|
{
|
2021-04-01 06:26:16 +00:00
|
|
|
if (!is_first_track || is_audio_track)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Invalid TOC entry at index %u, user data (%u) should not start before pregap (%u)",
|
|
|
|
static_cast<u32>(curr_track), userdata_start, pregap_start);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Log_WarningPrintf(
|
|
|
|
"Invalid TOC entry at index %u, user data (%u) should not start before pregap (%u), assuming not in file.",
|
|
|
|
static_cast<u32>(curr_track), userdata_start, pregap_start);
|
2021-05-12 17:20:39 +00:00
|
|
|
pregap_start = 0;
|
2021-04-01 06:26:16 +00:00
|
|
|
pregap_frames = userdata_start;
|
|
|
|
pregap_sector_size = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pregap_frames = userdata_start - pregap_start;
|
|
|
|
pregap_sector_size = track_sector_size;
|
2021-03-12 07:24:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (is_first_track)
|
|
|
|
{
|
|
|
|
m_lba_count += pregap_frames;
|
|
|
|
track1_pregap_frames = pregap_frames;
|
|
|
|
}
|
|
|
|
|
|
|
|
Index pregap_index = {};
|
|
|
|
pregap_index.file_offset =
|
2021-04-01 06:26:16 +00:00
|
|
|
is_first_track ? 0 : (static_cast<u64>(pregap_start - track1_pregap_frames) * pregap_sector_size);
|
2021-03-12 07:24:12 +00:00
|
|
|
pregap_index.file_index = 0;
|
2021-04-01 06:26:16 +00:00
|
|
|
pregap_index.file_sector_size = pregap_sector_size;
|
2021-03-12 07:24:12 +00:00
|
|
|
pregap_index.start_lba_on_disc = pregap_start;
|
|
|
|
pregap_index.track_number = curr_track;
|
|
|
|
pregap_index.index_number = 0;
|
|
|
|
pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(pregap_frames));
|
|
|
|
pregap_index.length = pregap_frames;
|
|
|
|
pregap_index.mode = track_mode;
|
2023-11-05 10:36:28 +00:00
|
|
|
pregap_index.submode = CDImage::SubchannelMode::None;
|
2021-03-12 07:24:12 +00:00
|
|
|
pregap_index.control.bits = track_control.bits;
|
|
|
|
pregap_index.is_pregap = true;
|
|
|
|
|
|
|
|
m_indices.push_back(pregap_index);
|
|
|
|
|
|
|
|
Index userdata_index = {};
|
|
|
|
userdata_index.file_offset = static_cast<u64>(userdata_start - track1_pregap_frames) * track_sector_size;
|
|
|
|
userdata_index.file_index = 0;
|
|
|
|
userdata_index.file_sector_size = track_sector_size;
|
|
|
|
userdata_index.start_lba_on_disc = userdata_start;
|
|
|
|
userdata_index.track_number = curr_track;
|
|
|
|
userdata_index.index_number = 1;
|
|
|
|
userdata_index.start_lba_in_track = 0;
|
|
|
|
userdata_index.mode = track_mode;
|
2023-11-05 10:36:28 +00:00
|
|
|
userdata_index.submode = CDImage::SubchannelMode::None;
|
2021-03-12 07:24:12 +00:00
|
|
|
userdata_index.control.bits = track_control.bits;
|
|
|
|
userdata_index.is_pregap = false;
|
|
|
|
|
|
|
|
if (is_last_track)
|
|
|
|
{
|
|
|
|
if (userdata_start >= m_lba_count)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Last user data index on disc for TOC entry %u should not be 0 or less in length",
|
|
|
|
static_cast<u32>(curr_track));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
userdata_index.length = m_lba_count - userdata_start;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const TOCEntry& next_track = m_toc[static_cast<size_t>(curr_track) + 3];
|
|
|
|
const LBA next_track_start =
|
|
|
|
Position::FromBCD(next_track.pregap_start.m, next_track.pregap_start.s, next_track.pregap_start.f).ToLBA();
|
|
|
|
const u8 next_track_num = PackedBCDToBinary(next_track.point);
|
|
|
|
|
|
|
|
if (next_track_num != curr_track + 1 || next_track_start < userdata_start)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Unable to calculate user data index length for TOC entry %u", static_cast<u32>(curr_track));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
userdata_index.length = next_track_start - userdata_start;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_indices.push_back(userdata_index);
|
|
|
|
|
|
|
|
m_tracks.push_back(Track{curr_track, userdata_start, 2 * curr_track - 1,
|
2023-11-05 10:36:28 +00:00
|
|
|
pregap_index.length + userdata_index.length, track_mode, SubchannelMode::None,
|
|
|
|
track_control});
|
2021-03-12 07:24:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AddLeadOutIndex();
|
|
|
|
|
|
|
|
// Initialize zlib stream
|
|
|
|
if (!InitDecompressionStream())
|
|
|
|
{
|
|
|
|
Log_ErrorPrint("Failed to initialize zlib decompression stream");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-03-18 23:51:09 +00:00
|
|
|
if (m_disc_offsets.size() > 1)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
2024-01-01 04:46:56 +00:00
|
|
|
// Gross. Have to use the SBI suffix here, otherwise Android won't resolve content URIs...
|
|
|
|
// Which means that LSD won't be usable with PBP on Android. Oh well.
|
|
|
|
const std::string display_name = FileSystem::GetDisplayNameFromPath(m_filename);
|
|
|
|
const std::string offset_path =
|
|
|
|
Path::BuildRelativePath(m_filename, fmt::format("{}_{}.sbi", Path::StripExtension(display_name), index + 1));
|
2023-11-15 09:05:21 +00:00
|
|
|
m_sbi.LoadFromImagePath(offset_path);
|
2021-03-12 07:24:12 +00:00
|
|
|
}
|
|
|
|
else
|
2023-11-15 09:05:21 +00:00
|
|
|
{
|
2024-01-01 04:46:56 +00:00
|
|
|
m_sbi.LoadFromImagePath(m_filename);
|
2023-11-15 09:05:21 +00:00
|
|
|
}
|
2021-03-12 07:24:12 +00:00
|
|
|
|
2021-03-26 17:34:50 +00:00
|
|
|
m_current_disc = index;
|
2021-03-12 07:24:12 +00:00
|
|
|
return Seek(1, Position{0, 0, 0});
|
|
|
|
}
|
|
|
|
|
2021-03-26 17:34:50 +00:00
|
|
|
const std::string* CDImagePBP::LookupStringSFOTableEntry(const char* key, const SFOTable& table)
|
|
|
|
{
|
|
|
|
auto iter = table.find(key);
|
|
|
|
if (iter == table.end())
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
const SFOTableDataValue& data_value = iter->second;
|
|
|
|
if (!std::holds_alternative<std::string>(data_value))
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
return &std::get<std::string>(data_value);
|
|
|
|
}
|
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
bool CDImagePBP::InitDecompressionStream()
|
|
|
|
{
|
|
|
|
m_inflate_stream = {};
|
|
|
|
m_inflate_stream.next_in = Z_NULL;
|
|
|
|
m_inflate_stream.avail_in = 0;
|
|
|
|
m_inflate_stream.zalloc = Z_NULL;
|
|
|
|
m_inflate_stream.zfree = Z_NULL;
|
|
|
|
m_inflate_stream.opaque = Z_NULL;
|
|
|
|
|
|
|
|
int ret = inflateInit2(&m_inflate_stream, -MAX_WBITS);
|
|
|
|
return ret == Z_OK;
|
|
|
|
}
|
|
|
|
|
2021-04-07 08:30:51 +00:00
|
|
|
bool CDImagePBP::DecompressBlock(const BlockInfo& block_info)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
2023-11-18 06:21:51 +00:00
|
|
|
if (FileSystem::FSeek64(m_file, block_info.offset, SEEK_SET) != 0)
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
2021-05-12 17:20:54 +00:00
|
|
|
// Compression level 0 has compressed size == decompressed size.
|
|
|
|
if (block_info.size == m_decompressed_block.size())
|
|
|
|
{
|
2023-11-18 06:21:51 +00:00
|
|
|
return (std::fread(m_decompressed_block.data(), sizeof(u8), m_decompressed_block.size(), m_file) ==
|
2021-05-12 17:20:54 +00:00
|
|
|
m_decompressed_block.size());
|
|
|
|
}
|
|
|
|
|
2021-03-12 07:24:12 +00:00
|
|
|
m_compressed_block.resize(block_info.size);
|
|
|
|
|
2023-11-18 06:21:51 +00:00
|
|
|
if (std::fread(m_compressed_block.data(), sizeof(u8), m_compressed_block.size(), m_file) != m_compressed_block.size())
|
2021-03-12 07:24:12 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
m_inflate_stream.next_in = m_compressed_block.data();
|
|
|
|
m_inflate_stream.avail_in = static_cast<uInt>(m_compressed_block.size());
|
|
|
|
m_inflate_stream.next_out = m_decompressed_block.data();
|
|
|
|
m_inflate_stream.avail_out = static_cast<uInt>(m_decompressed_block.size());
|
|
|
|
|
|
|
|
if (inflateReset(&m_inflate_stream) != Z_OK)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
int err = inflate(&m_inflate_stream, Z_FINISH);
|
|
|
|
if (err != Z_STREAM_END)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Inflate error %d", err);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-03-26 16:19:23 +00:00
|
|
|
bool CDImagePBP::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
2021-03-26 16:19:23 +00:00
|
|
|
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
|
2021-03-12 07:24:12 +00:00
|
|
|
return true;
|
|
|
|
|
2021-03-26 16:19:23 +00:00
|
|
|
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
|
2021-03-12 07:24:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CDImagePBP::HasNonStandardSubchannel() const
|
|
|
|
{
|
|
|
|
return (m_sbi.GetReplacementSectorCount() > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CDImagePBP::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
|
|
|
|
{
|
|
|
|
const u32 offset_in_file = static_cast<u32>(index.file_offset) + (lba_in_index * index.file_sector_size);
|
|
|
|
const u32 offset_in_block = offset_in_file % DECOMPRESSED_BLOCK_SIZE;
|
|
|
|
const u32 requested_block = offset_in_file / DECOMPRESSED_BLOCK_SIZE;
|
|
|
|
|
|
|
|
BlockInfo& bi = m_blockinfo_table[requested_block];
|
|
|
|
|
|
|
|
if (bi.size == 0)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Invalid block %u requested", requested_block);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_current_block != requested_block && !DecompressBlock(bi))
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Failed to decompress block %u", requested_block);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::memcpy(buffer, &m_decompressed_block[offset_in_block], RAW_SECTOR_SIZE);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if _DEBUG
|
|
|
|
void CDImagePBP::PrintPBPHeaderInfo(const PBPHeader& pbp_header)
|
|
|
|
{
|
|
|
|
printf("PBP header info\n");
|
|
|
|
printf("PBP format version 0x%08X\n", pbp_header.version);
|
|
|
|
printf("File offsets\n");
|
|
|
|
printf("PARAM.SFO 0x%08X PARSE\n", pbp_header.param_sfo_offset);
|
|
|
|
printf("ICON0.PNG 0x%08X IGNORE\n", pbp_header.icon0_png_offset);
|
|
|
|
printf("ICON1.PNG 0x%08X IGNORE\n", pbp_header.icon1_png_offset);
|
|
|
|
printf("PIC0.PNG 0x%08X IGNORE\n", pbp_header.pic0_png_offset);
|
|
|
|
printf("PIC1.PNG 0x%08X IGNORE\n", pbp_header.pic1_png_offset);
|
|
|
|
printf("SND0.AT3 0x%08X IGNORE\n", pbp_header.snd0_at3_offset);
|
|
|
|
printf("DATA.PSP 0x%08X IGNORE\n", pbp_header.data_psp_offset);
|
|
|
|
printf("DATA.PSAR 0x%08X PARSE\n", pbp_header.data_psar_offset);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDImagePBP::PrintSFOHeaderInfo(const SFOHeader& sfo_header)
|
|
|
|
{
|
|
|
|
printf("SFO header info\n");
|
|
|
|
printf("SFO format version 0x%08X\n", sfo_header.version);
|
|
|
|
printf("SFO key table offset 0x%08X\n", sfo_header.key_table_offset);
|
|
|
|
printf("SFO data table offset 0x%08X\n", sfo_header.data_table_offset);
|
|
|
|
printf("SFO table entry count 0x%08X\n", sfo_header.num_table_entries);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDImagePBP::PrintSFOIndexTableEntry(const SFOIndexTableEntry& sfo_index_table_entry, size_t i)
|
|
|
|
{
|
|
|
|
printf("SFO index table entry %zu\n", i);
|
|
|
|
printf("Key offset 0x%08X\n", sfo_index_table_entry.key_offset);
|
|
|
|
printf("Data type 0x%08X\n", sfo_index_table_entry.data_type);
|
|
|
|
printf("Data size 0x%08X\n", sfo_index_table_entry.data_size);
|
|
|
|
printf("Total data size 0x%08X\n", sfo_index_table_entry.data_total_size);
|
|
|
|
printf("Data offset 0x%08X\n", sfo_index_table_entry.data_offset);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDImagePBP::PrintSFOTable(const SFOTable& sfo_table)
|
|
|
|
{
|
|
|
|
for (auto it = sfo_table.begin(); it != sfo_table.end(); ++it)
|
|
|
|
{
|
|
|
|
std::string key_value = it->first;
|
|
|
|
SFOTableDataValue data_value = it->second;
|
|
|
|
|
|
|
|
if (std::holds_alternative<std::string>(data_value))
|
|
|
|
printf("Key: %s, Data: %s\n", key_value.c_str(), std::get<std::string>(data_value).c_str());
|
|
|
|
else if (std::holds_alternative<u32>(data_value))
|
|
|
|
printf("Key: %s, Data: %u\n", key_value.c_str(), std::get<u32>(data_value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-03-26 17:34:50 +00:00
|
|
|
bool CDImagePBP::HasSubImages() const
|
|
|
|
{
|
|
|
|
return m_disc_offsets.size() > 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CDImagePBP::GetMetadata(const std::string_view& type) const
|
|
|
|
{
|
|
|
|
if (type == "title")
|
|
|
|
{
|
|
|
|
const std::string* title = LookupStringSFOTableEntry("TITLE", m_sfo_table);
|
|
|
|
if (title && !title->empty())
|
|
|
|
return *title;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CDImage::GetMetadata(type);
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CDImagePBP::GetSubImageCount() const
|
|
|
|
{
|
|
|
|
return static_cast<u32>(m_disc_offsets.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 CDImagePBP::GetCurrentSubImage() const
|
|
|
|
{
|
|
|
|
return m_current_disc;
|
|
|
|
}
|
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
bool CDImagePBP::SwitchSubImage(u32 index, Error* error)
|
2021-03-26 17:34:50 +00:00
|
|
|
{
|
|
|
|
if (index >= m_disc_offsets.size())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const u32 old_disc = m_current_disc;
|
|
|
|
if (!OpenDisc(index, error))
|
|
|
|
{
|
|
|
|
// return to old disc, this should never fail... in theory.
|
|
|
|
if (!OpenDisc(old_disc, nullptr))
|
|
|
|
Panic("Failed to reopen old disc after switch.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CDImagePBP::GetSubImageMetadata(u32 index, const std::string_view& type) const
|
|
|
|
{
|
|
|
|
if (type == "title")
|
|
|
|
{
|
|
|
|
const std::string* title = LookupStringSFOTableEntry("TITLE", m_sfo_table);
|
|
|
|
if (title && !title->empty())
|
2023-09-20 14:32:39 +00:00
|
|
|
return fmt::format("{} (Disc {})", *title, index + 1);
|
2021-03-26 17:34:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return CDImage::GetSubImageMetadata(index, type);
|
|
|
|
}
|
|
|
|
|
2023-12-20 13:40:24 +00:00
|
|
|
s64 CDImagePBP::GetSizeOnDisk() const
|
|
|
|
{
|
|
|
|
return FileSystem::FSize64(m_file);
|
|
|
|
}
|
|
|
|
|
2023-08-19 13:40:36 +00:00
|
|
|
std::unique_ptr<CDImage> CDImage::OpenPBPImage(const char* filename, Error* error)
|
2021-03-12 07:24:12 +00:00
|
|
|
{
|
|
|
|
std::unique_ptr<CDImagePBP> image = std::make_unique<CDImagePBP>();
|
2021-03-18 15:52:00 +00:00
|
|
|
if (!image->Open(filename, error))
|
2021-03-12 07:24:12 +00:00
|
|
|
return {};
|
|
|
|
|
|
|
|
return image;
|
|
|
|
}
|