Skip to content

Instantly share code, notes, and snippets.

@playday3008
Last active May 7, 2025 11:43
Show Gist options
  • Save playday3008/0c8ba916ba3b1c4f52654db6e3f85109 to your computer and use it in GitHub Desktop.
Save playday3008/0c8ba916ba3b1c4f52654db6e3f85109 to your computer and use it in GitHub Desktop.
PBP-files pattern for ImHex, supports: EBOOT, PBOOT, PARAM, and maybe more

PlayStation Boot Package (PBP) in ImHex

image

Any suggestions on improvement are welcome

#pragma author PlayDay
#pragma description PlayStation Boot Package (PBP)
#pragma magic [ 00 50 42 50 ] @ 0x00
#pragma endian little
//#pragma debug // Most likely will cause OOM of whole system
/**
* # PlayStation Boot Package (PBP) file format
*
* ## TODO
*
* - `~SCE` structure
* - `~PSP` structure/decryption/unpacking
* - `PSAR` structure/decryption/unpacking
*
* ## External libraries
*
* - [ELF](https://github.com/WerWolv/ImHex-Patterns/blob/master/patterns/elf.hexpat)
* - Slightly modified with fixes and support for PS files
* - [PNG](https://github.com/WerWolv/ImHex-Patterns/blob/master/patterns/png.hexpat)
* - [WAV](https://github.com/WerWolv/ImHex-Patterns/blob/master/patterns/wav.hexpat)
*
* ## References
*
* - [PSPDEV](https://github.com/pspdev)
* - [PSPSDK](https://github.com/pspdev/pspsdk)
* - [PSPLINK](https://github.com/pspdev/psplinkusb)
* - [GNU GDB](https://github.com/pspdev/gdb)
* - [GNU Binutils](https://github.com/pspdev/binutils-gdb)
* - [GNU Insight](https://github.com/pspdev/insight)
* - [PRXTool](https://github.com/pspdev/prxtool)
* - [EBOOT Signer](https://github.com/pspdev/ebootsigner)
* - [uOFW](https://github.com/uofw/uofw)
* - [PSDevWiki](https://www.psdevwiki.com)
* - [PSP](https://www.psdevwiki.com/psp)
* - [PS3](https://www.psdevwiki.com/ps3)
* - [PS Vita](https://www.psdevwiki.com/vita)
* - [PPSSPP](https://github.com/hrydgard/ppsspp)
* - [pspdecrypt](https://github.com/hrydgard/pspdecrypt/tree/add-sce-support)
* - Official PSP SDK
*
*/
import hex.core;
import std.core;
import std.ptr;
import std.mem;
import std.math;
import std.sys;
import std.io;
import std.string;
import type.base;
import type.bcd;
import type.magic;
/// Copied from `png.hexpat`
namespace PNG {
struct header_t {
u8 highBitByte;
char signature[3];
char dosLineEnding[2];
char dosEOF;
char unixLineEnding;
};
struct actl_t {
u32 frames [[comment("Total № of frames in animation")]];
u32 plays [[comment("№ of times animation will loop")]];
} [[comment("Animation control chunk"), name("acTL")]];
enum ColorType: u8 {
Grayscale = 0x0,
RGBTriple = 0x2,
Palette,
GrayscaleAlpha,
RGBA = 0x6
};
enum Interlacing: u8 {
None,
Adam7
};
struct ihdr_t {
u32 width [[comment("Image width")]];
u32 height [[comment("Image height")]];
u8 bit_depth;
ColorType color_type [[comment("PNG Image Type")]];
u8 compression_method [[comment("Only 0x0 = zlib supported by most")]];
u8 filter_method [[comment("Only 0x0 = adaptive supported by most")]];
Interlacing interlacing;
};
enum sRGB: u8 {
Perceptual = 0x0,
RelativeColorimetric,
Saturation,
AbsoluteColorimetric
};
enum Unit: u8 {
Unknown,
Meter
};
struct phys_t {
u32 ppu_x [[comment("Pixels per unit, X axis")]];
u32 ppu_y [[comment("Pixels per unit, Y axis")]];
Unit unit;
};
enum BlendOp: u8 {
Source = 0x0,
Over
};
enum DisposeOp: u8 {
None = 0x0,
Background,
Previous
};
struct fctl_t {
u32 sequence_no [[comment("Sequence №")]];
u32 width [[comment("Frame width")]];
u32 height;
u32 xoff;
u32 yoff;
u16 delay_num;
u16 delay_den;
DisposeOp dispose_op;
BlendOp blend_op;
};
struct fdat_t {
u32 sequence_no;
};
fn text_len() {
u64 len = parent.parent.length - ($ - addressof(parent.keyword));
return len;
};
struct itxt_t {
char keyword[];
u8 compression_flag;
u8 compression_method;
char language_tag[];
char translated_keyword[];
char text[PNG::text_len()];
};
struct ztxt_t {
char keyword[];
u8 compression_method;
char text[PNG::text_len()];
};
struct text_t {
char keyword[];
char text[PNG::text_len()];
};
struct iccp_t {
char keyword[];
u8 compression_method;
u8 compressed_profile[PNG::text_len()];
};
struct palette_entry_t {
u24 color;
} [[inline]];
struct chrm_t {
u32 white_point_x;
u32 white_point_y;
u32 red_x;
u32 red_y;
u32 green_x;
u32 green_y;
u32 blue_x;
u32 blue_y;
};
struct time_t {
u16 year;
u8 month;
u8 day;
u8 hour;
u8 minute;
u8 second;
};
struct chunk_t {
u32 length [[color("17BECF")]];
char name[4];
#define IHDR_k "IHDR"
#define PLTE_k "PLTE"
#define sRGB_k "sRGB"
#define pHYs_k "pHYs"
#define iTXt_k "iTXt"
#define tEXt_k "tEXt"
#define zTXt_k "zTXt"
#define IDAT_k "IDAT"
#define IEND_k "IEND"
#define gAMA_k "gAMA"
#define iCCP_k "iCCP"
#define acTL_k "acTL"
#define fdAT_k "fdAT"
#define fcTL_k "fcTL"
#define cHRM_k "cHRM"
#define tIME_k "tIME"
if (name == IHDR_k) {
ihdr_t ihdr [[comment("Image Header chunk"), name("IHDR")]];
} else if (name == PLTE_k) {
palette_entry_t entries[length / 3];
} else if (name == sRGB_k) {
sRGB srgb;
} else if (name == pHYs_k) {
phys_t phys;
} else if (name == acTL_k) {
actl_t actl [[comment("Animation control chunk")]];
} else if (name == fcTL_k) {
fctl_t fctl [[comment("Frame control chunk")]];
} else if (name == iTXt_k) {
itxt_t text;
} else if (name == gAMA_k) {
u32 gamma [[name("image gamma"), comment("4 byte unsigned integer representing gamma times 100000")]];
} else if (name == iCCP_k) {
iccp_t iccp;
} else if (name == tEXt_k) {
text_t text;
} else if (name == zTXt_k) {
ztxt_t text;
} else if (name == iCCP_k) {
iccp_t iccp;
} else if (name == fdAT_k) {
fdat_t fdat [[comment("Frame data chunk")]];
u8 data[length-sizeof(u32)];
} else if (name == cHRM_k) {
chrm_t chrm;
} else if (name == tIME_k) {
time_t time;
} else {
u8 data[length];
}
u32 crc;
} [[name(this.name)]];
struct chunk_set {
chunk_t chunks[while(std::mem::read_string($ + 4, 4) != "IEND")] [[inline]];
} [[inline]];
struct Chunks {
chunk_t ihdr_chunk [[comment("PNG Header chunk")]];
chunk_set set [[comment("PNG Chunks"), name("Chunks"), inline]];
chunk_t iend_chunk [[comment("Image End Chunk")]];
};
}
/// Wrapper for PNG
struct PNG<auto Size> {
u8 visualizer[Size] @ $ [[sealed, hex::visualize("image", this)]];
be PNG::header_t header [[comment("PNG file signature"), name("Signature")]];
be PNG::Chunks chunks [[name("Chunks")]];
};
/// Copied from `wav.hexpat`
namespace WAV {
struct RiffHeader {
char ckID[4] [[comment("Container Signature"), name("RIFF Header Signature")]];
u32 ckSize [[comment("Size of RIFF Header"), name("RIFF Chunk Size")]];
char format[4] [[comment("RIFF format"), name("WAVE Header Signature")]];
};
struct WaveChunk {
char chunkId[4];
u32 chunkSize;
};
enum WaveFormatType : u16 {
Unknown,
PCM,
MS_ADPCM,
IEEEFloatingPoint,
ALAW = 6,
MULAW,
IMA_ADPCM = 0x11,
GSM610 = 0x31,
MPEG = 0x50,
MPEGLAYER3 = 0x55,
};
struct WaveFormat {
WaveFormatType formatTag;
u16 channels;
u32 samplesPerSec [[comment("Sample Frequency")]];
u32 avgBytesPerSec [[comment("BPS - Used to estimate buffer size")]];
u16 blockAlign;
};
struct WaveFormatEx {
u16 bitsPerSample;
u16 extendedDataSize;
};
struct WaveFormatExDummy {
u16 bitsPerSample;
u16 extendedDataSize;
u8 extendedData[extendedDataSize];
};
struct WaveFormatPCM {
u16 bitsPerSample;
};
struct WaveMSADPCMCoefSet {
s16 coef1;
s16 coef2;
};
struct WaveFormatMSADPCM : WaveFormatEx {
u16 samplesPerBlock;
u16 numCoef;
WaveMSADPCMCoefSet coef[numCoef];
};
bitfield WaveMPEGLayer {
Layer1 : 1;
Layer2 : 1;
Layer3 : 1;
padding : 13;
};
bitfield WaveMPEGMode {
Stereo : 1;
JointStereo : 1;
DualChannel : 1;
SingleChannel : 1;
padding : 12;
};
bitfield WaveMPEGFlags {
PrivateBit : 1;
Copyright : 1;
OriginalHome : 1;
ProtectionBit : 1;
IdMPEG1 : 1;
padding : 11;
};
struct WaveFormatIEEEFloatingPoint : WaveFormatPCM {
};
struct WaveFormatMPEG : WaveFormatEx {
WaveMPEGLayer headLayersUsed;
u32 headBitrate;
WaveMPEGMode headMode;
u16 headModeExt;
u16 headEmphasis;
WaveMPEGFlags headFlags;
u32 PTSLow;
u32 PTSHigh;
};
enum WaveFormatMPEGLayer3Flags : u32 {
MPEGLAYER3_FLAG_PADDING_ISO,
MPEGLAYER3_FLAG_PADDING_ON,
MPEGLAYER3_FLAG_PADDING_OFF
};
struct WaveFormatMPEGLayer3 : WaveFormatEx {
u16 id;
WaveFormatMPEGLayer3Flags flags;
u16 blockSize;
u16 framesPerBlock;
u16 codecDelay;
};
struct WaveFact {
u32 uncompressedSize;
};
enum SampleLookupType : u32 {
TYPE_LOOP_FORWARD,
TYPE_LOOP_ALTERNATE,
TYPE_LOOP_BACKWARD,
};
struct SampleLookup {
u32 id;
SampleLookupType type;
u32 start;
u32 end;
u32 fraction;
u32 playCount;
};
struct WaveSample {
u32 manufacturer;
u32 product;
u32 samplePeriod;
u32 MIDIUnityNote;
u32 MIDIPitchFraction;
u32 SMPTEFormat;
u32 SMPTEOffset;
u32 numSampleLoops;
u32 sampleLoopsSize;
SampleLookup lookups[numSampleLoops];
};
struct WaveCuePoint {
u32 indentifier;
u32 position;
char chunkID[4];
u32 chunkStart;
u32 blockStart;
u32 sampleOffset;
};
struct WaveCue {
u32 numCuePoints;
WaveCuePoint cuePoints[numCuePoints];
};
struct WaveLabel {
u32 id;
char text[];
padding[sizeof(text) % 2];
};
using WaveNote = WaveLabel;
struct WaveListItem : WaveChunk {
if (chunkId == "labl") {
WaveLabel label;
} else if (chunkId == "note") {
WaveNote note;
} else {
padding[(chunkSize + 1) >> 1 << 1];
}
};
u64 listEnd;
struct WaveList {
char type[4];
WaveListItem item[while ($ < listEnd)];
};
u32 paddedChunkSize;
struct WavData {
WaveChunk chunk;
paddedChunkSize = (chunk.chunkSize + 1) >> 1 << 1;
if (chunk.chunkId == "fmt ") {
WaveFormat fmt;
if (fmt.formatTag == WaveFormatType::PCM) {
WaveFormatPCM pcmExtraData;
padding[paddedChunkSize - sizeof(fmt) - sizeof(pcmExtraData)];
} else if (fmt.formatTag == WaveFormatType::MS_ADPCM) {
WaveFormatMSADPCM msAdpcmExtraData;
padding[paddedChunkSize - sizeof(fmt) - sizeof(msAdpcmExtraData)];
} else if (fmt.formatTag == WaveFormatType::MPEG) {
WaveFormatMPEG mpegExtraData;
padding[paddedChunkSize - sizeof(fmt) - sizeof(mpegExtraData)];
} else if (fmt.formatTag == WaveFormatType::MPEGLAYER3) {
WaveFormatMPEGLayer3 mpegLayer3ExtraData;
padding[paddedChunkSize - sizeof(fmt) - sizeof(mpegLayer3ExtraData)];
} else if (fmt.formatTag == WaveFormatType::IEEEFloatingPoint) {
WaveFormatIEEEFloatingPoint ieeFloatingPointExtraData;
padding[paddedChunkSize - sizeof(fmt) - sizeof(ieeFloatingPointExtraData)];
} else {
WaveFormatExDummy unknown;
padding[paddedChunkSize - sizeof(fmt) - sizeof(unknown)];
}
} else if (chunk.chunkId == "data") {
padding[paddedChunkSize];
} else if (chunk.chunkId == "fact") {
WaveFact fact;
padding[paddedChunkSize - sizeof(fact)];
} else if (chunk.chunkId == "smpl") {
WaveSample smpl;
padding[paddedChunkSize - sizeof(smpl)];
} else if (chunk.chunkId == "cue ") {
WaveCue cue;
padding[paddedChunkSize - sizeof(cue)];
} else if (chunk.chunkId == "LIST") {
listEnd = $ + chunk.chunkSize;
WaveList list;
padding[paddedChunkSize % 1];
} else {
padding[paddedChunkSize];
}
};
}
/// Wrapper for WAV
struct WAV<auto Size> {
le WAV::RiffHeader header;
le WAV::WavData data[while ($ < std::ptr::relative_to_parent(0) + Size)];
};
/// Copied from `elf.hexpat`
/// With slight modifications
namespace ELF {
using BitfieldOrder = std::core::BitfieldOrder;
using EI_ABIVERSION = u8;
using Elf32_Addr = u32;
using Elf32_BaseAddr = u32;
using Elf32_BaseOff = u32;
using Elf32_Half = u16;
using Elf32_Off = u32;
using Elf32_Sword = s32;
using Elf32_VAddr = u32;
using Elf32_Word = u32;
using Elf64_Addr = u64;
using Elf64_BaseAddr = u64;
using Elf64_BaseOff = u64;
using Elf64_Half = u16;
using Elf64_Off = u64;
using Elf64_Sword = s32;
using Elf64_Sxword = s64;
using Elf64_VAddr = u64;
using Elf64_Word = u32;
using Elf64_Xword = u64;
using E_VERSION = u32;
enum EI_CLASS : u8 {
ELFCLASSNONE = 0x00,
ELFCLASS32 = 0x01,
ELFCLASS64 = 0x02,
ELFCLASSNUM = 0x03,
};
enum EI_DATA : u8 {
ELFDATANONE = 0x00,
ELFDATA2LSB = 0x01,
ELFDATA2MSB = 0x02,
ELFDATANUM = 0x03,
};
enum EI_OSABI : u8 {
NONE = 0x00,
SYSV = 0x00,
HPUX = 0x01,
NetBSD = 0x02,
Linux = 0x03,
GNUHurd = 0x04,
Solaris = 0x06,
AIX = 0x07,
IRIX = 0x08,
FreeBSD = 0x09,
Tru64 = 0x0A,
NovellModesto = 0x0B,
OpenBSD = 0x0C,
OpenVMS = 0x0D,
NonStopKernel = 0x0E,
AROS = 0x0F,
FenixOS = 0x10,
CloudABI = 0x11,
OpenVOS = 0x12,
ARM_EABI = 0x40,
ARM = 0x61,
CELL_LV2 = 0x66, // CELL LV2
STANDALONE = 0xFF,
};
enum EI_VERSION : u8 {
NONE = 0x00,
CURRENT = 0x01,
};
enum EM : u16 {
// Some of them have only numbers in the name, so,
// we're starting all of them with `_`
_NONE = 0x0000,
_M32 = 0x0001,
_SPARC = 0x0002,
_386 = 0x0003,
_68K = 0x0004,
_88K = 0x0005,
_IAMCU = 0x0006,
_860 = 0x0007,
_MIPS = 0x0008,
_S370 = 0x0009,
_MIPS_RS4_BE = 0x000a,
_PARISC = 0x000f,
_VPP500 = 0x0011,
_SPARC32PLUS = 0x0012,
_960 = 0x0013,
_PPC = 0x0014,
_PPC64 = 0x0015,
_S390 = 0x0016,
_SPU = 0x0017,
_V800 = 0x0024,
_FR20 = 0x0025,
_RH32 = 0x0026,
_RCE = 0x0027,
_ARM = 0x0028,
_ALPHA = 0x0029,
_SH = 0x002A,
_SPARCV9 = 0x002B,
_TRICORE = 0x002C,
_ARC = 0x002D,
_H8_300 = 0x002E,
_H8_300H = 0x002F,
_H8S = 0x0030,
_H8_500 = 0x0031,
_IA_64 = 0x0032,
_MIPS_X = 0x0033,
_COLDFIRE = 0x0034,
_68HC12 = 0x0035,
_MMA = 0x0036,
_PCP = 0x0037,
_NCPU = 0x0038,
_NDR1 = 0x0039,
_STARCORE = 0x003A,
_ME16 = 0x003B,
_ST100 = 0x003C,
_TINYJ = 0x003D,
_X86_64 = 0x003E,
_PDSP = 0x003F,
_PDP10 = 0x0040,
_PDP11 = 0x0041,
_FX66 = 0x0042,
_ST9PLUS = 0x0043,
_ST7 = 0x0044,
_68HC16 = 0x0045,
_68HC11 = 0x0046,
_68HC08 = 0x0047,
_68HC05 = 0x0048,
_SVX = 0x0049,
_ST19 = 0x004A,
_VAX = 0x004B,
_CRIS = 0x004C,
_JAVELIN = 0x004D,
_FIREPATH = 0x004E,
_ZSP = 0x004F,
_MMIX = 0x0050,
_HUANY = 0x0051,
_PRISM = 0x0052,
_AVR = 0x0053,
_FR30 = 0x0054,
_D10V = 0x0055,
_D30V = 0x0056,
_V850 = 0x0057,
_M32R = 0x0058,
_MN10300 = 0x0059,
_MN10200 = 0x005A,
_PJ = 0x005B,
_OPENRISC = 0x005C,
_ARC_COMPACT = 0x005D,
_XTENSA = 0x005E,
_VIDEOCORE = 0x005F,
_TMM_GPP = 0x0060,
_NS32K = 0x0061,
_TPC = 0x0062,
_SNP1K = 0x0063,
_ST200 = 0x0064,
_IP2K = 0x0065,
_MAX = 0x0066,
_CR = 0x0067,
_F2MC16 = 0x0068,
_MSP430 = 0x0069,
_BLACKFIN = 0x006A,
_SE_C33 = 0x006B,
_SEP = 0x006C,
_ARCA = 0x006D,
_UNICORE = 0x006E,
_EXCESS = 0x006F,
_DXP = 0x0070,
_ALTERA_NIOS2 = 0x0071,
_CRX = 0x0072,
_XGATE = 0x0073,
_C166 = 0x0074,
_M16C = 0x0075,
_DSPIC30F = 0x0076,
_CE = 0x0077,
_M32C = 0x0078,
_TSK3000 = 0x0083,
_RS08 = 0x0084,
_SHARC = 0x0085,
_ECOG2 = 0x0086,
_SCORE7 = 0x0087,
_DSP24 = 0x0088,
_VIDEOCORE3 = 0x0089,
_LATTICEMICO32 = 0x008A,
_SE_C17 = 0x008B,
_TI_C6000 = 0x008C,
_TI_C2000 = 0x008D,
_TI_C5500 = 0x008E,
_TI_ARP32 = 0x008F,
_TI_PRU = 0x0090,
_MMDSP_PLUS = 0x00A0,
_CYPRESS_M8C = 0x00A1,
_R32C = 0x00A2,
_TRIMEDIA = 0x00A3,
_QDSP6 = 0x00A4,
_8051 = 0x00A5,
_STXP7X = 0x00A6,
_NDS32 = 0x00A7,
_ECOG1 = 0x00A8,
_ECOG1X = 0x00A8,
_MAXQ30 = 0x00A9,
_XIMO16 = 0x00AA,
_MANIK = 0x00AB,
_CRAYNV2 = 0x00AC,
_RX = 0x00AD,
_METAG = 0x00AE,
_MCST_ELBRUS = 0x00AF,
_ECOG16 = 0x00B0,
_CR16 = 0x00B1,
_ETPU = 0x00B2,
_SLE9X = 0x00B3,
_L10M = 0x00B4,
_K10M = 0x00B5,
_AARCH64 = 0x00B7,
_AVR32 = 0x00B9,
_STM8 = 0x00BA,
_TILE64 = 0x00BB,
_TILEPRO = 0x00BC,
_MICROBLAZE = 0x00BD,
_CUDA = 0x00BE,
_TILEGX = 0x00BF,
_CLOUDSHIELD = 0x00C0,
_COREA_1ST = 0x00C1,
_COREA_2ND = 0x00C2,
_ARC_COMPACT2 = 0x00C3,
_OPEN8 = 0x00C4,
_RL78 = 0x00C5,
_VIDEOCORE5 = 0x00C6,
_78KOR = 0x00C7,
_56800EX = 0x00C8,
_BA1 = 0x00C9,
_BA2 = 0x00CA,
_XCORE = 0x00CB,
_MCHP_PIC = 0x00CC,
_INTEL205 = 0x00CD,
_INTEL206 = 0x00CE,
_INTEL207 = 0x00CF,
_INTEL208 = 0x00D0,
_INTEL209 = 0x00D1,
_KM32 = 0x00D2,
_KMX32 = 0x00D3,
_KMX16 = 0x00D4,
_KMX8 = 0x00D5,
_KVARC = 0x00D6,
_CDP = 0x00D7,
_COGE = 0x00D8,
_COOL = 0x00D9,
_NORC = 0x00DA,
_CSR_KALIMBA = 0x00DB,
_Z80 = 0x00DC,
_VISIUM = 0x00DD,
_FT32 = 0x00DE,
_MOXIE = 0x00DF,
_AMDGPU = 0x00E0,
_RISCV = 0x00F3,
_BPF = 0x00F7,
_CSKY = 0x00FC,
_LOONGARCH = 0x0102,
_NUM = 0x0103,
};
enum ET : u16 {
NONE = 0x0000,
REL = 0x0001,
EXEC = 0x0002,
DYN = 0x0003,
CORE = 0x0004,
NUM = 0x0005,
/// OS-specific range
SCE_EXEC = 0xFE00, // SCE Executable - PRX2
RANGE_OS = 0xFE01 ... 0xFE03,
SCE_RELEXEC = 0xFE04, // SCE Relocatable Executable - PRX2
RANGE_OS = 0xFE05 ... 0xFE0B,
SCE_STUBLIB = 0xFE0C, // SCE SDK Stubs
RANGE_OS = 0xFE0D ... 0xFE0F,
SCE_DYNEXEC = 0xFE10, // SCE EXEC_ASLR (PS4 Executable with ASLR)
RANGE_OS = 0xFE11 ... 0xFE17,
SCE_DYNAMIC = 0xFE18, // ?
RANGE_OS = 0xFE19 ... 0xFEFF,
/// Processor-specific range
RANGE_PROC = 0xFF00 ... 0xFF7F,
SCE_IOPRELEXEC = 0xFF80, // SCE IOP Relocatable Executable
SCE_IOPRELEXEC2 = 0xFF81, // SCE IOP Relocatable Executable Version 2
RANGE_PROC = 0xFF82 ... 0xFF8F,
SCE_EERELEXEC = 0xFF90, // SCE EE Relocatable Executable
SCE_EERELEXEC2 = 0xFF91, // SCE EE Relocatable Executable Version 2
RANGE_PROC = 0xFF92 ... 0xFF9F,
SCE_PSPRELEXEC = 0xFFA0, // SCE PSP Relocatable Executable
RANGE_PROC = 0xFFA1 ... 0xFFA3,
SCE_PPURELEXEC = 0xFFA4, // SCE PPU Relocatable Executable
SCE_ARMRELEXEC = 0xFFA5, // SCE ARM Relocatable Executable (PS Vita System Software earlier or equal 0.931.010)?
RANGE_PROC = 0xFFA6 ... 0xFFF7,
SCE_PSPOVERLAY = 0xFFA8, // PSP Overlay file
RANGE_PROC = 0xFFA9 ... 0xFFFF,
};
enum DT : u32 {
NULL = 0x0,
NEEDED = 0x1,
PLTRELSZ = 0x2,
PLTGOT = 0x3,
HASH = 0x4,
STRTAB = 0x5,
SYMTAB = 0x6,
RELA = 0x7,
RELASZ = 0x8,
RELAENT = 0x9,
STRSZ = 0xA,
SYMENT = 0xB,
INIT = 0xC,
FINI = 0xD,
SONAME = 0xE,
RPATH = 0xF,
SYMBOLIC = 0x10,
REL = 0x11,
RELSZ = 0x12,
RELENT = 0x13,
PLTREL = 0x14,
DEBUG = 0x15,
TEXTREL = 0x16,
JMPREL = 0x17,
BIND_NOW = 0x18,
INIT_ARRAY = 0x19,
FINI_ARRAY = 0x1A,
INIT_ARRAYSZ = 0x1B,
FINI_ARRAYSZ = 0x1C,
RUNPATH = 0x1D,
FLAGS = 0x1E,
PREINIT_ARRAY = 0x20,
PREINIT_ARRAYSZ = 0x21,
MAXPOSTAGS = 0x22,
NUM = 0x23,
/// OS-specific range
SUNW_AUXILIARY = 0x6000000D,
SUNW_RTLDINF = 0x6000000E,
SUNW_FILTER = 0x6000000F,
SUNW_CAP = 0x60000010,
SUNW_SYMTAB = 0x60000011,
SUNW_SYMSZ = 0x60000012,
SUNW_SORTENT = 0x60000013,
SUNW_SYMSORT = 0x60000014,
SUNW_SYMSORTSZ = 0x60000015,
SUNW_TLSSORT = 0x60000016,
SUNW_TLSSORTSZ = 0x60000017,
SUNW_STRPAD = 0x60000019,
SUNW_LDMACH = 0x6000001B,
RANGE_OS = 0x6000001C ... 0x6FFFF000,
/// Processor-specific range
RANGE_PROC = 0x70000000 ... 0x7FFFFFFC,
AUXILIARY = 0x7FFFFFFD,
USED = 0x7FFFFFFE,
FILTER = 0x7FFFFFFF,
/// Use the Dyn.d_un.d_val field of the Elf*_Dyn structure
RANGE_VALRNG = 0x6FFFFD00 ... 0x6FFFFDF4,
GNU_PRELINKED = 0x6FFFFDF5,
GNU_CONFLICTSZ = 0x6FFFFDF6,
GNU_LIBLISTSZ = 0x6FFFFDF7,
CHECKSUM = 0x6FFFFDF8,
PLTPADSZ = 0x6FFFFDF9,
MOVEENT = 0x6FFFFDFA,
MOVESZ = 0x6FFFFDFB,
FEATURE_1 = 0x6FFFFDFC,
POSFLAG_1 = 0x6FFFFDFD,
SYMINSZ = 0x6FFFFDFE,
SYMINENT = 0x6FFFFDFF,
/// Use the Dyn.d_un.d_ptr field of the Elf*_Dyn structure.
RANGE_ADDRRNG = 0x6FFFFE00 ... 0x6FFFFEF4,
GNU_HASH = 0x6FFFFEF5,
TLSDESC_PLT = 0x6FFFFEF6,
TLSDESC_GOT = 0x6FFFFEF7,
GNU_CONFLICT = 0x6FFFFEF8,
GNU_LIBLIST = 0x6FFFFEF9,
CONFIG = 0x6FFFFEFA,
DEPAUDIT = 0x6FFFFEFB,
AUDIT = 0x6FFFFEFC,
PLTPAD = 0x6FFFFEFD,
MOVETAB = 0x6FFFFEFE,
SYMINFO = 0x6FFFFEFF,
/// GNU extensions
VERSYM = 0x6FFFFFF0,
RELACOUNT = 0x6FFFFFF9,
RELCOUNT = 0x6FFFFFFA,
FLAGS_1 = 0x6FFFFFFB,
VERDEF = 0x6FFFFFFC,
VERDEFNUM = 0x6FFFFFFD,
VERNEED = 0x6FFFFFFE,
VERNEEDNUM = 0x6FFFFFFF,
};
enum PT : Elf32_Word {
NULL = 0x00,
LOAD = 0x01,
DYNAMIC = 0x02,
INTERP = 0x03,
NOTE = 0x04,
SHLIB = 0x05,
PHDR = 0x06,
TLS = 0x07,
NUM = 0x08,
/// OS-specific range
SCE_RELA = 0x60000000,
SCE_LICINFO_1 = 0x60000001,
SCE_LICINFO_2 = 0x60000002,
RANGE_OS = 0x60000003 ... 0x60FFFFFF,
SCE_DYNLIBDATA = 0x61000000,
SCE_PROCESS_PARAM = 0x61000001,
SCE_MODULE_PARAM = 0x61000002,
RANGE_OS = 0x61000003 ... 0x6100000F,
SCE_RELRO = 0x61000010, // for PS4
RANGE_OS = 0x61000011 ... 0x6474E54F,
GNU_EH_FRAME = 0x6474E550,
GNU_STACK = 0x6474E551,
GNU_RELRO = 0x6474E552,
GNU_PROPERTY = 0x6474E553,
RANGE_OS = 0x6474E554 ... 0x6FFFFEFF,
SCE_COMMENT = 0x6FFFFF00,
SCE_LIBVERSION = 0x6FFFFF01,
RANGE_OS = 0x6FFFFF02 ... 0x6FFFFFF9,
SUNWBSS = 0x6FFFFFFA,
SUNWSTACK = 0x6FFFFFFB,
RANGE_OS = 0x6FFFFFFC ... 0x6FFFFFFF,
/// Processor-specific range
ARM_ARCHEXT = 0x70000000,
ARM_UNWIND = 0x70000001,
RANGE_PROC = 0x70000002 ... 0x7000007F,
SCE_IOPMOD = 0x70000080,
RANGE_PROC = 0x70000081 ... 0x7000008F,
SCE_EEMOD = 0x70000090,
RANGE_PROC = 0x70000091 ... 0x7000009F,
SCE_PSPRELA = 0x700000A0, // PSP Relocation data segment
SCE_PSPRELA2 = 0x700000A1, // PSP Relocation data segment Version 2
RANGE_PROC = 0x700000A2 ... 0x700000A3,
SCE_PPURELA = 0x700000A4,
RANGE_PROC = 0x700000A5 ... 0x700000A7,
SCE_SEGSYM = 0x700000A8,
RANGE_PROC = 0x700000A9 ... 0x7FFFFFFF,
};
enum ELFCOMPRESS : u8 {
ZLIB = 0x01,
ZSTD = 0x02,
/// OS-specific range
RANGE_OS = 0x60000000 ... 0x6fffffff,
/// Processor-specific range
RANGE_PROC = 0x70000000 ... 0x7fffffff,
};
enum SHN : u16 {
UNDEF = 0x00,
BEFORE = 0xFF00,
AFTER = 0xFF01,
/// Processor-specific range
RANGE_PROC = 0xFF02 ... 0xFF1F,
/// OS-specific range
RANGE_OS = 0xFF20 ... 0xFF3F,
ABS = 0xFFF1,
COMMON = 0xFFF2,
XINDEX = 0xFFFF,
};
enum SHT : Elf32_Word {
NULL = 0x00,
PROGBITS = 0x01,
SYMTAB = 0x02,
STRTAB = 0x03,
RELA = 0x04,
HASH = 0x05,
DYNAMIC = 0x06,
NOTE = 0x07,
NOBITS = 0x08,
REL = 0x09,
SHLIB = 0x0A,
DYNSYM = 0x0B,
UNKNOWN12 = 0x0C,
UNKNOWN13 = 0x0D,
INIT_ARRAY = 0x0E,
FINI_ARRAY = 0x0F,
PREINIT_ARRAY = 0x10,
GROUP = 0x11,
SYMTAB_SHNDX = 0x12,
RELR = 0x13,
NUM = 0x14,
/// OS-specific range
SCE_RELA = 0x60000000,
SCE_NID = 0x61000001,
RANGE_OS = 0x60000002 ... 0x6FFF46FF,
GNU_INCREMENTAL_INPUTS = 0x6FFF4700,
RANGE_OS = 0x6FFF4701 ... 0x6FFFFFF4,
GNU_ATTRIBUTES = 0x6FFFFFF5,
GNU_HASH = 0x6FFFFFF6,
GNU_LIBLIST = 0x6FFFFFF7,
CHECKSUM = 0x6FFFFFF8,
RANGE_OS = 0x6FFFFFF9,
SUNW_MOVE = 0x6FFFFFFA,
SUNW_COMDAT = 0x6FFFFFFB,
SUNW_syminfo = 0x6FFFFFFC,
GNU_verdef = 0x6FFFFFFD,
GNU_verneed = 0x6FFFFFFE,
GNU_versym = 0x6FFFFFFF,
/// Processor-specific range
RANGE_PROC = 0x70000000,
ARM_EXIDX = 0x70000001,
ARM_PREEMPTMAP = 0x70000002,
ARM_ATTRIBUTES = 0x70000003,
ARM_DEBUGOVERLAY = 0x70000004,
ARM_OVERLAYSECTION = 0x70000005,
RANGE_PROC = 0x70000006 ... 0x7000007F,
SCE_IOPMOD = 0x70000080,
RANGE_PROC = 0x70000081 ... 0x7000008F,
SCE_EEMOD = 0x70000090,
RANGE_PROC = 0x70000091 ... 0x7000009F,
SCE_PSPRELA = 0x700000A0,
RANGE_PROC = 0x700000A1 ... 0x700000A3,
SCE_PPURELA = 0x700000A4,
RANGE_PROC = 0x700000A5 ... 0x7FFFFFFF,
/// Application-specific range
RANGE_USER = 0x80000000 ... 0x8FFFFFFF,
};
enum STV : u8 {
DEFAULT = 0x00,
INTERNAL = 0x01,
HIDDEN = 0x02,
PROTECTED = 0x03,
};
enum SYMINFO_BT : Elf32_Half {
SELF = 0xFFFF,
PARENT = 0xFFFE,
NONE = 0xFFFD,
};
enum VER_DEF : Elf32_Half {
NON = 0x00,
CURRENT = 0x01,
NUM = 0x02,
};
enum VER_NDX : Elf32_Half {
LOCAL = 0x00,
GLOBAL = 0x01,
ELIMINATE = 0xFF01,
};
enum VER_NEED : Elf32_Half {
NONE = 0x00,
CURRENT = 0x01,
NUM = 0x02,
};
bitfield SYMINFO_FLG {
DIRECT : 1;
PASSTHRU : 1;
COPY : 1;
LAZYLOAD : 1;
DIRECTBIND : 1;
NOEXTDIRECT : 1;
padding : 10;
};
bitfield ST {
ST_BIND : 4;
ST_TYPE : 4;
} [[bitfield_order(BitfieldOrder::MostToLeastSignificant, 8)]];
bitfield SHF {
WRITE : 1;
ALLOC : 1;
EXECINSTR : 1;
padding : 1;
MERGE : 1;
STRINGS : 1;
INFO_LINK : 1;
LINK_ORDER : 1;
OS_NONCONFORMING : 1;
GROUP : 1;
TLS : 1;
COMPRESSED : 1;
UNKNOWN : 8;
MASKOS : 8;
MASKPROC : 4;
};
bitfield ELF32_R_INFO {
SYM : 8;
TYPE : 8;
} [[bitfield_order(BitfieldOrder::MostToLeastSignificant, 16)]];
bitfield ELF64_R_INFO {
SYM : 32;
TYPE : 32;
} [[bitfield_order(BitfieldOrder::MostToLeastSignificant, 64)]];
bitfield PF {
X : 1;
W : 1;
R : 1;
padding : 17;
SPU_X : 1;
SPU_W : 1;
SPU_R : 1;
padding : 1;
RSX_X : 1;
RSX_W : 1;
RSX_R : 1;
padding : 1;
MASKPROC : 4;
};
struct E_IDENT {
type::Magic<"\x7fELF"> EI_MAG;
EI_CLASS EI_CLASS;
EI_DATA EI_DATA;
EI_VERSION EI_VERSION;
EI_OSABI EI_OSABI;
EI_ABIVERSION EI_ABIVERSION;
padding[7];
};
struct Elf32_Ehdr {
ET e_type;
EM e_machine;
E_VERSION e_version;
Elf32_VAddr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
};
struct Elf64_Ehdr {
ET e_type;
EM e_machine;
E_VERSION e_version;
Elf64_VAddr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
};
struct Elf32_Phdr<auto Origin> {
PT p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
PF p_flags;
Elf32_Word p_align;
if (p_offset >= 0 && p_filesz > 0 && (Origin + p_offset + p_filesz) <= std::mem::size() && (Origin + p_filesz) <= std::mem::size())
u8 p_data[p_filesz] @ Origin + p_offset [[sealed]];
};
struct Elf64_Phdr<auto Origin> {
PT p_type;
PF p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
if (p_offset >= 0 && p_filesz > 0 && (Origin + p_offset + p_filesz) <= std::mem::size() && (Origin + p_filesz) <= std::mem::size())
u8 p_data[p_filesz] @ Origin + p_offset [[sealed]];
};
struct Elf32_Chdr {
u32 ch_type;
Elf32_Word ch_size;
Elf32_Word ch_addralign;
};
struct Elf32_Rel {
Elf32_Off r_offset;
ELF32_R_INFO r_info;
};
struct Elf32_Rela {
Elf32_Off r_offset;
ELF32_R_INFO r_info;
Elf32_Sword r_addend;
};
struct Elf32_Sym {
u32 st_name;
Elf32_VAddr st_value;
Elf32_Word st_size;
ST st_info;
STV st_other;
u16 st_shndx;
};
struct Elf32_Syminfo {
u16 si_boundto;
SYMINFO_FLG si_flags;
};
s64 stringTableIndex;
struct String { // Basically `std::string::NullString`, but that one works with `std::core::set_display_name`
char value[while(std::mem::read_unsigned($, sizeof(char)) != 0x00)];
char tail; // NULL terminator
} [[sealed, format("ELF::format_string")]];
fn format_string(String string) {
return string.value;
};
struct Elf32_Shdr<auto Origin> {
u32 sh_name;
SHT sh_type;
SHF sh_flags;
u32 sh_addr;
u32 sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
if (sh_size > 0 && (Origin + sh_offset + sh_size) <= std::mem::size()) {
if (sh_type == SHT::NOBITS || sh_type == SHT::NULL) {
// Section has no data
} else if (sh_type == SHT::STRTAB) {
String stringTable[while($ < (Origin + sh_offset + sh_size))] @ Origin + sh_offset;
} else if (sh_type == SHT::SYMTAB || sh_type == SHT::DYNSYM) {
Elf32_Sym symbolTable[sh_size / sh_entsize] @ Origin + sh_offset;
} else if (sh_type == SHT::INIT_ARRAY || sh_type == SHT::FINI_ARRAY) {
u32 pointer[while($ < (Origin + sh_offset + sh_size))] @ Origin + sh_offset;
} else {
u8 data[sh_size] @ Origin + sh_offset [[sealed]];
}
}
};
struct Elf64_Chdr {
u32 ch_type;
Elf64_Word ch_size;
Elf64_Word ch_addralign;
};
struct Elf64_Rel {
Elf64_Off r_offset;
ELF64_R_INFO r_info;
};
struct Elf64_Rela {
Elf64_Off r_offset;
ELF64_R_INFO r_info;
Elf64_Sxword r_addend;
};
struct Elf64_Sym {
u32 st_name;
ST st_info;
STV st_other;
u16 st_shndx;
Elf64_VAddr st_value;
Elf64_Xword st_size;
};
struct Elf64_Syminfo {
u16 si_boundto;
SYMINFO_FLG si_flags;
};
struct Elf64_Shdr<auto Origin> {
u32 sh_name;
SHT sh_type;
SHF sh_flags;
padding[4];
u64 sh_addr;
u64 sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
if (sh_size > 0 && (Origin + sh_offset + sh_size) <= std::mem::size()) {
if (sh_type == SHT::NOBITS || sh_type == SHT::NULL) {
// Section has no data
} else if (sh_type == SHT::STRTAB) {
String stringTable[while($ < (Origin + sh_offset + sh_size))] @ Origin + sh_offset;
} else if (sh_type == SHT::SYMTAB || sh_type == SHT::DYNSYM) {
Elf64_Sym symbolTable[sh_size / sh_entsize] @ Origin + sh_offset;
} else if (sh_type == SHT::INIT_ARRAY || sh_type == SHT::FINI_ARRAY) {
u32 pointer[while($ < (Origin + sh_offset + sh_size))] @ Origin + sh_offset;
} else {
u8 data[sh_size] @ Origin + sh_offset [[sealed]];
}
}
};
fn format_section_header(ref auto elf, ref auto shdr) {
u32 i = 0;
u32 nameAddress = addressof(elf.shdr[stringTableIndex].stringTable) + shdr.sh_name;
String string @ nameAddress;
if (sizeof(string.value) == 0) {
// Section name is empty
return ".<null>";
}
return string;
};
struct ELF<auto Origin> {
E_IDENT e_ident;
if (e_ident.EI_DATA == EI_DATA::ELFDATA2LSB) {
if (e_ident.EI_CLASS == EI_CLASS::ELFCLASS32) {
le Elf32_Ehdr ehdr;
stringTableIndex = ehdr.e_shstrndx;
le Elf32_Phdr<Origin> phdr[ehdr.e_phnum] @ Origin + ehdr.e_phoff;
le Elf32_Shdr<Origin> shdr[ehdr.e_shnum] @ Origin + ehdr.e_shoff;
} else if (e_ident.EI_CLASS == EI_CLASS::ELFCLASS64) {
le Elf64_Ehdr ehdr;
stringTableIndex = ehdr.e_shstrndx;
le Elf64_Phdr<Origin> phdr[ehdr.e_phnum] @ Origin + ehdr.e_phoff;
le Elf64_Shdr<Origin> shdr[ehdr.e_shnum] @ Origin + ehdr.e_shoff;
}
}
else {
if (e_ident.EI_CLASS == EI_CLASS::ELFCLASS32) {
be Elf32_Ehdr ehdr;
stringTableIndex = ehdr.e_shstrndx;
be Elf32_Phdr<Origin> phdr[ehdr.e_phnum] @ Origin + ehdr.e_phoff;
be Elf32_Shdr<Origin> shdr[ehdr.e_shnum] @ Origin + ehdr.e_shoff;
} else if (e_ident.EI_CLASS == EI_CLASS::ELFCLASS64) {
be Elf64_Ehdr ehdr;
stringTableIndex = ehdr.e_shstrndx;
be Elf64_Phdr<Origin> phdr[ehdr.e_phnum] @ Origin + ehdr.e_phoff;
be Elf64_Shdr<Origin> shdr[ehdr.e_shnum] @ Origin + ehdr.e_shoff;
}
}
};
fn gen_shdr_disp_name(ref auto elf, ref auto pattern, str member_name, u8 member_index, u8 member_total) {
return std::format(
"[{:0{}}]{}",
member_index,
u8(std::math::log10(member_total)+1),
ELF::format_section_header(elf, pattern)
);
};
fn displaySectionNames(ref auto elf) {
auto shdr_count = std::core::member_count(elf.shdr);
for (u32 i = 0, i < shdr_count, i += 1) {
std::core::set_display_name(
elf.shdr[i],
ELF::gen_shdr_disp_name(elf, elf.shdr[i], "shdr", i, shdr_count)
);
}
};
}
/// Wrapper for ELF
struct ELF<auto Size> {
ELF::ELF<$> elf [[inline]];
ELF::displaySectionNames(elf);
padding[Size - sizeof(this)]; // Artificially bloat size of struct to real size
};
/// PlayStation System File
namespace PSF {
enum ValueType : u16 {
RSV_M = 0x0000,
FILE_M = 0x0100,
VALUE_STR8 = 0x0204,
VALUE_STR16 = 0x0304,
VALUE_INT = 0x0404,
VALUE_FLOAT = 0x0504,
VALUE_FILE4 = 0x0104,
VALUE_FILE8 = 0x0108,
VALUE_FILE16 = 0x0110,
VALUE_FILE32 = 0x0120,
VALUE_RSV4 = 0x0004,
VALUE_RSV8 = 0x0008,
VALUE_RSV16 = 0x0010,
VALUE_RSV32 = 0x0020,
};
bitfield Region {
// Assumption based on PS3 and uOFW
bool JP : 1 [[comment("Japan")]];
bool US : 1 [[comment("US, Canada (North America)")]];
bool EU : 1 [[comment("Europe / Middle East / Africa")]];
bool KR : 1 [[comment("Korea (South Korea)")]];
bool UK : 1 [[comment("U.K. / Ireland")]];
bool MX : 1 [[comment("Mexico, Central America, South America")]];
bool AU : 1 [[comment("Australia / New Zealand (Oceania)")]];
bool SA : 1 [[comment("Singapore / Malaysia (Southeast Asia)")]];
bool TW : 1 [[comment("Taiwan")]];
bool RU : 1 [[comment("Russia, Ukraine, India, Central Asia")]];
bool CN : 1 [[comment("China")]];
bool HK : 1 [[comment("Hong Kong")]];
padding : 3;
bool ALL : 1 [[comment("All regions")]];
padding : 16;
};
fn indexKeyDesc(ref str key) {
match (key) {
("APP_VER" ): return "Application version";
("ATTRIBUTE" ): return "Attribute";
("BOOTABLE" ): return "Is bootable";
("CATEGORY" ): return "Category";
("DISC_ID" ): return "Disc ID";
("DISC_NUMBER" ): return "Disc number";
("DISC_TOTAL" ): return "Number of discs";
("DISC_VERSION" ): return "Disc version";
("DRIVER_PATH" ): return "Path to the device drivers";
("GAMEDATA_ID" ): return "Game data ID";
("HRKGMP_VER" ): return "HRKGMP version"; // Nobody really knows what this is
("LICENSE" ): return "License information";
("MEMSIZE" ): return "Add extra RAM for application"; // Does this exists outside of Homebrew?
("PARENTAL_LEVEL" ): return "Parental-lock level";
("PBOOT_TITLE" ): return "Patch title name";
("PSP_SYSTEM_VER" ): return "PSP system version";
("REGION" ): return "Region";
("SHARING_ID" ): return "Sharing ID";
("TARGET_APP_VER" ): return "Target application version";
("TITLE" ): return "Title name (Default)";
("TITLE_0" | "TITLE_00"): return "Title name (JA)";
("TITLE_1" | "TITLE_01"): return "Title name (EN)";
("TITLE_2" | "TITLE_02"): return "Title name (FR)";
("TITLE_3" | "TITLE_03"): return "Title name (ES)";
("TITLE_4" | "TITLE_04"): return "Title name (DE)";
("TITLE_5" | "TITLE_05"): return "Title name (IT)";
("TITLE_6" | "TITLE_06"): return "Title name (NL)";
("TITLE_7" | "TITLE_07"): return "Title name (PT)";
("TITLE_8" | "TITLE_08"): return "Title name (RU)";
("TITLE_9" | "TITLE_09"): return "Title name (KO)";
("TITLE_10" ): return "Title name (CH)";
("TITLE_11" ): return "Title name (ZH)";
("UPDATER_VER" ): return "Updater version";
("USE_USB" ): return "Use USB peripheral";
// TODO: Add more keys
(_): return null;
}
};
struct Index {
type::Hex<u16> key_offset [[hidden]];
ValueType value_type [[name("type")]];
u32 value_used_size [[hidden]];
u32 value_max_size [[hidden]];
type::Hex<u32> value_offset [[hidden]];
/// Pointers to the start of the key and value
auto key_ptr = parent.origin + parent.key_table_offset + key_offset;
auto val_ptr = parent.origin + parent.val_table_offset + value_offset;
std::string::NullString key @ key_ptr;
str name = PSF::indexKeyDesc(key) [[export]];
match (value_type) {
(ValueType::VALUE_STR8): {
std::string::NullString value_raw @ val_ptr [[hidden]];
match (key) {
// TODO: Handle all these values
("CATEGORY"):
match (value_raw) {
("MA"): str value = "MemoryStick App" [[export]];
("ME"): str value = "PS1 Classic" [[export]]; // IDK why it's ME
("MG"): str value = "MemoryStick Game" [[export]];
("MS"): str value = "MemoryStick Save" [[export]];
("PG"): str value = "Game Patch" [[export]];
("UG"): str value = "UMD Game" [[export]];
("WG"): str value = "WLAN Game" [[export]];
("EG"): std::string::NullString value @ addressof(value_raw);
("MM"): std::string::NullString value @ addressof(value_raw);
(_): std::string::NullString value @ addressof(value_raw); // Return as is
}
("DRIVER_PATH"): std::string::NullString value @ addressof(value_raw);
("GAMEDATA_ID"): std::string::NullString value @ addressof(value_raw);
(_): std::string::NullString value @ addressof(value_raw); // Return as is
}
}
(ValueType::VALUE_STR16):
std::string::NullString16 value @ val_ptr;
(ValueType::VALUE_INT): {
u32 value_raw @ val_ptr [[hidden]];
match (key) {
// TODO: Handle all these values
("ATTRIBUTE" ): u32 value @ addressof(value_raw);
("BOOTABLE" ): u32 value @ addressof(value_raw);
("HRKGMP_VER"): u32 value @ addressof(value_raw);
("REGION" ): Region value @ addressof(value_raw);
("SHARING_ID"): u32 value @ addressof(value_raw);
(_): u32 value @ addressof(value_raw); // Return as is
}
}
(ValueType::VALUE_FLOAT):
float value @ val_ptr;
(ValueType::VALUE_FILE4):
str value = "FileRef(align=4)" [[export]];
(ValueType::VALUE_FILE8):
str value = "FileRef(align=8)" [[export]];
(ValueType::VALUE_FILE16):
str value = "FileRef(align=16)" [[export]];
(ValueType::VALUE_FILE32):
str value = "FileRef(align=32)" [[export]];
(ValueType::VALUE_RSV4):
str value = "Reserve(align=4)" [[export]];
(ValueType::VALUE_RSV8):
str value = "Reserve(align=8)" [[export]];
(ValueType::VALUE_RSV16):
str value = "Reserve(align=16)" [[export]];
(ValueType::VALUE_RSV32):
str value = "Reserve(align=32)" [[export]];
(_):
std::assert(false, "Unknown type");
}
};
struct Header<auto Size> {
/// Size of the header must be at least 0x14
/// 0x4 - magic
/// 0x4 - version
/// 0x4 - key_table_offset
/// 0x4 - val_table_offset
/// 0x4 - tables_entries
if (Size < 0x14)
std::assert(false, "PSF Header size cannot be 0");
/// Needed for relative to beginning of the header offsets
auto origin = $;
type::Magic<"\x00PSF"> magic;
type::BCD<4> version;
type::Hex<u32> key_table_offset [[hidden]];
type::Hex<u32> val_table_offset [[hidden]];
type::Hex<u32> tables_entries [[hidden]];
/// As we are using calculated pointers inside, the size is undetermined
Index index[tables_entries];
/// Adjust $ to compensate for the dynamic size of the index table
$ = origin + Size;
};
fn displaySectionNames(ref auto psf) {
auto index_count = std::core::member_count(psf.index);
for (u32 i = 0, i < index_count, i += 1) {
std::core::set_display_name(
psf.index[i],
std::format(
"[{:0{}}]{}",
i,
u8(std::math::log10(index_count)+1),
psf.index[i].key
)
);
}
};
}
/// Wrapper for PSF
struct PSF<auto Size> {
le PSF::Header<Size> header [[inline]];
PSF::displaySectionNames(header);
};
/// PlayStation Movie Format
namespace PSMF {
enum Version : u32 {
NUMBER_0012 = 0x30303132, // 0012
NUMBER_0013 = 0x30303133, // 0013
NUMBER_0014 = 0x30303134, // 0014
NUMBER_0015 = 0x30303135, // 0015
};
struct EpEntry {
u48 pts [[comment("48-bit presentation time stamp")]];
u32 rpn [[comment("Relative Position Number")]];
};
struct StreamEpMap<auto Count> {
EpEntry ep_entries[Count];
};
struct Video {
u8 width_div_16 [[comment("Width divided by 16")]];
u8 height_div_16 [[comment("Height divided by 16")]];
u16 reserved [[comment("Always 0")]];
};
struct Audio {
u16 reserved [[comment("Always 0")]];
u8 channel_config [[comment("Channel configuration")]];
u8 sampling_frequency [[comment("Sampling frequency")]];
};
struct StreamInfo<auto Origin> {
u8 stream_id [[comment("Stream ID")]];
u8 private_stream_id [[comment("Private stream ID")]];
u16 pstd_buffer [[comment("P-STD buffer size (Program Stream - System Target Decoder)")]];
u32 ep_map_offset [[comment("EP map offset")]];
u32 ep_entry_count [[comment("EP entry count")]];
if ((stream_id & 0xF0) == 0xE0)
Video video [[comment("Video stream")]];
else if (stream_id == 0xBD && ((private_stream_id & 0xF0) == 0 || (private_stream_id & 0xF0) == 0x10))
Audio audio [[comment("Audio stream")]];
else if (stream_id == 0xBD && (private_stream_id & 0xF0) == 0x20)
u32 other;
else if (stream_id == 0xBD && private_stream_id == 0x7D)
u32 other;
else if (stream_id == 0xBD && private_stream_id == 0x7E)
u32 other;
else if (stream_id != 0xBD || private_stream_id != 0x7F)
std::assert(false, "Stream has incorrect attributes");
else
u32 other;
if (ep_map_offset != 0)
StreamEpMap<ep_entry_count> stream_ep_map @ Origin + ep_map_offset [[comment("EP Map section (entry points)")]];
};
struct GroupesInfo<auto Origin> {
u8 count [[comment("Number of groups (always 1)")]];
u32 size [[comment("Size of groups info (excluding this and previous fields)")]];
u8 unk0 [[comment("Always 0")]];
u8 stream_count [[comment("Number of streams")]];
StreamInfo<Origin> stream_info[stream_count];
auto size_exclude = sizeof(count) + sizeof(size);
std::assert(size == sizeof(this) - size_exclude, "Groupes info size mismatch");
};
struct GroupingPeriodInfo<auto Origin> {
u8 count [[comment("Number of grouping periods (always 1)")]];
u32 size [[comment("Size of grouping period info (excluding this and previous fields)")]];
u48 presentation_start [[comment("48-bit presentation start time")]];
u48 presentation_end [[comment("48-bit presentation end time")]];
u8 unk0 [[comment("Always 0")]];
GroupesInfo<Origin> groups_info;
auto size_exclude = sizeof(count) + sizeof(size);
std::assert(size == sizeof(this) - size_exclude, "Grouping period info size mismatch");
};
struct SequenceInfo<auto Origin> {
u32 size [[comment("Size of sequence info block (excluding this and previous fields)")]];
u48 presentation_start [[comment("48-bit presentation start time")]];
u48 presentation_end [[comment("48-bit presentation end time")]];
u32 mux_rate_bound [[comment("MUX rate bound (0x3FFFFF bits used)")]];
u32 std_delay_bound [[comment("STD delay bound (0xFFFFF bits used)")]];
u8 stream_count [[comment("Number of streams")]];
GroupingPeriodInfo<Origin> grouping_period_info;
auto size_exclude = sizeof(size);
std::assert(size == sizeof(this) - size_exclude, "Sequence info size mismatch");
};
enum MarkType : u8 {
CHAPTER = 0x05,
EVENT = 0x10,
};
struct MarkEntry {
MarkType type [[comment("Type")]];
u8 name_length [[comment("Length of mark name")]];
u16 unk0 [[comment("Always 0")]];
u48 timestamp [[comment("48-bit timestamp")]];
u8 stream_id [[comment("Stream ID (0 for chapter marks)")]];
u8 unk1 [[comment("Always 0")]];
u32 mark_data [[comment("Mark data")]];
char name[24] [[comment("Mark name (padded with zeros)")]];
};
struct MarkSection {
u32 size [[comment("Size of mark section")]];
u16 count [[comment("Number of marks")]];
MarkEntry mark_entries[count];
std::assert(sizeof(this) == size, "Mark section size mismatch");
};
struct Header<auto Size> {
type::Magic<"PSMF"> magic;
Version version;
type::Hex<u32> data_offset [[hidden]];
type::Hex<u32> data_size [[hidden]];
type::Hex<u32> mark_offset [[hidden]];
type::Hex<u32> mark_size [[hidden]];
padding[56];
SequenceInfo<std::ptr::relative_to_parent(0)> sequence_info;
if (mark_size > 0) {
// Padding instead of pointer to the start of structure
padding[mark_offset - sizeof(this)];
MarkSection mark_section;
}
// Padding instead of pointer to the start of structure
padding[data_offset - sizeof(this)];
type::Hex<u8> mps_data[data_size];
};
}
/// Wrapper for PSMF
struct PSMF<auto Size> {
be PSMF::Header<Size> header [[inline]];
};
namespace PG {
enum DataMagic : u32 {
PRX = 0x7E505350, // ~PSP
SCE = 0x7E534345, // ~SCE
ELF = 0x7F454C46, // \x7fELF
PSAR = 0x50534152, // PSAR
};
namespace PRX {
bitfield ModuleAttr {
padding : 9;
bool MS : 1;
bool USB_WLAN : 1;
bool VSH : 1;
bool KERNEL : 1;
bool KIRK_MEMLMD_LIB : 1;
bool KIRK_SEMAPHORE_LIB : 1;
padding : 1;
};
bitfield CompressionAttr {
bool COMPRESSED : 1;
bool ELF : 1;
padding : 1;
bool GZIP_OVERLAP : 1;
padding : 5;
bool KL4E_COMPRESSED : 1;
padding : 6;
};
enum DecryptMode : u8 {
NO_EXEC = 0, /* Not an executable. */
BOGUS_MODULE = 1, /* 1.50 Kernel module. */
KERNEL_MODULE = 2,
VSH_MODULE = 3,
USER_MODULE = 4,
UMD_GAME_EXEC = 9,
GAMESHARING_EXEC = 10,
GAMESHARING_EXEC_DEVTOOL = 11,
MS_UPDATER = 12,
DEMO_EXEC = 13,
APP_MODULE = 14,
MS_GAME_PATCH = 18, // 0x12
MS_GAME_PATCH_DEVTOOL = 19, // 0x13
POPS_EXEC = 20, // 0x14
UNKNOWN_21 = 21, // 0x15
UNKNOWN_22 = 22, // 0x16
USER_NPDRM = 23, // 0x17
MS_GAME_PBOOT = 25, // 0x19
};
struct Header {
type::Magic<"~PSP"> magic;
PRX::ModuleAttr mod_attr [[comment("Module attributes")]];
PRX::CompressionAttr comp_attr [[comment("Compression attributes")]];
u8 module_ver_lo [[comment("Minor module version")]];
u8 module_ver_hi [[comment("Major module version")]];
std::string::NullString mod_name [[comment("Module name")]];
padding[28 - sizeof(mod_name)]; // Padding to 28 bytes
u8 mod_version [[comment("Module version")]];
u8 n_segments [[comment("Number of segments")]];
type::Hex<u32> elf_size [[comment("Size of ELF (uncompressed and decrypted)")]];
type::Hex<u32> psp_size [[comment("Size of PSP (compressed/encrypted)")]];
type::Hex<u32> boot_entry [[comment("Boot entry address (offset from the start of .text)")]];
type::Hex<u32> mod_info_offset [[comment("Offset to the module info")]];
type::Hex<u32> bss_size [[comment("Size of BSS")]];
type::Hex<u16> seg_align[4] [[comment("Alignment info of each segment")]];
type::Hex<u32> seg_addr[4] [[comment("Start address of each segment")]];
type::Hex<u32> seg_size[4] [[comment("Size of each segment")]];
padding[4*5]; // TODO: Unknown
u32 devkit_version [[comment("DevKit version")]];
PRX::DecryptMode decrypt_mode [[comment("Decrypt mode")]];
padding[1]; // TODO: Unknown
type::Hex<u16> overlap_size [[comment("Size of the overlap")]];
type::Hex<u8> aes_key[0x10] [[comment("AES key")]];
type::Hex<u8> cmac_key[0x10] [[comment("CMAC key")]];
type::Hex<u8> cmac_hdr_hash[0x10] [[comment("CMAC header hash")]];
type::Hex<u32> comp_size [[comment("Size of the compressed ELF")]];
padding[4*3]; // TODO: Unknown
type::Hex<u8> cmac_data_hash[0x10] [[comment("CMAC data hash")]];
type::Hex<u32> tag [[comment("Tag")]];
type::Hex<u8> s_check[0x58] [[comment("Check")]];
type::Hex<u8> sha1_hash[0x14] [[comment("SHA1 hash")]];
type::Hex<u8> key_data4[0x10] [[comment("Key data")]];
std::assert(sizeof(this) == 0x150, "PRX Header size mismatch");
};
}
struct PRX<auto Size> {
PRX::Header header;
type::Hex<u8> data[header.psp_size - sizeof(this)];
std::assert(sizeof(this) == Size, "PRX size mismatch");
};
namespace SCE {
struct Header<auto Size> {
type::Magic<"~SCE"> magic;
type::Hex<u32> hdr_size [[hidden]];
type::Hex<u8> hdr_version [[hidden]];
padding[4*13]; // TODO: Unknown
std::assert(hdr_size == sizeof(this), "SCE Header size mismatch");
PRX<Size - hdr_size> prx;
};
}
struct SCE<auto Size> {
SCE::Header<Size> header [[inline]];
std::assert(sizeof(this) == Size, "SCE size mismatch");
};
namespace PSAR {
struct Version {
type::Hex<u16> lo;
type::Hex<u16> hi;
};
}
struct PSAR<auto Size> {
type::Magic<"PSAR"> magic;
PSAR::Version version;
type::Hex<u32> data_size;
type::Hex<u32> unk;
type::Hex<u8> data[data_size];
// TODO: Expected size have 0x10 bytes more, figure out why
//std::assert(sizeof(this) == Size, "PSAR size mismatch");
};
}
struct PG<auto Size> {
// Get magic and reset $ to the beginning of the structure
be PG::DataMagic magic [[hidden]];
$ = std::ptr::relative_to_parent(0);
match (magic) {
(PG::DataMagic::PRX ): le PG::PRX<Size> prx;
(PG::DataMagic::SCE ): le PG::SCE<Size> sce;
(PG::DataMagic::ELF ): le ELF<Size> elf;
(PG::DataMagic::PSAR): le PG::PSAR<Size> psar;
(_): type::Hex<u8> other[Size - sizeof(this)];
}
};
struct PBP {
type::Magic<"\x00PBP"> magic;
type::BCD<4> version;
// Get offsets
type::Hex<u32> param_sfo_offset [[hidden]];
type::Hex<u32> icon0_offset [[hidden]];
type::Hex<u32> icon1_offset [[hidden]];
type::Hex<u32> pic0_offset [[hidden]];
type::Hex<u32> pic1_offset [[hidden]];
type::Hex<u32> snd0_offset [[hidden]];
type::Hex<u32> boot_pg_offset [[hidden]];
type::Hex<u32> pg_data_offset [[hidden]];
// Get sizes
u32 param_sfo_size = icon0_offset - param_sfo_offset;
u32 icon0_size = icon1_offset - icon0_offset;
u32 icon1_size = pic0_offset - icon1_offset;
u32 pic0_size = pic1_offset - pic0_offset;
u32 pic1_size = snd0_offset - pic1_offset;
u32 snd0_size = boot_pg_offset - snd0_offset;
u32 boot_pg_size = pg_data_offset - boot_pg_offset;
u32 pg_data_size = std::ptr::relative_to_end(0) - pg_data_offset;
// Evaluate
PSF<param_sfo_size> param_sfo;
if (icon0_size > 0) PNG<icon0_size> icon0 @ icon0_offset;
if (icon1_size > 0) PSMF<icon1_size> icon1 @ icon1_offset;
if (pic0_size > 0) PNG<pic0_size> pic0 @ pic0_offset;
if (pic1_size > 0) PNG<pic1_size> pic1 @ pic1_offset;
if (snd0_size > 0) WAV<snd0_size> snd0 @ snd0_offset;
if (boot_pg_size > 0) PG<boot_pg_size> boot_pg @ boot_pg_offset;
if (pg_data_size > 0) PG<pg_data_size> pg_data @ pg_data_offset;
// Add virtual files
hex::core::add_virtual_file("PARAM.SFO", param_sfo);
if (icon0_size > 0) hex::core::add_virtual_file("ICON0.PNG", icon0);
if (icon1_size > 0) hex::core::add_virtual_file("ICON1.PMF", icon1);
if (pic0_size > 0) hex::core::add_virtual_file("PIC0.PNG", pic0);
if (pic1_size > 0) hex::core::add_virtual_file("PIC1.PNG", pic1);
if (snd0_size > 0) hex::core::add_virtual_file("SND0.AT3", snd0);
if (boot_pg_size > 0) hex::core::add_virtual_file("DATA.PSP", boot_pg);
if (pg_data_size > 0) hex::core::add_virtual_file("DATA.PSAR", pg_data);
};
le PBP pbp @ $ [[inline]];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment