Skip to content

Commit 9dce989

Browse files
committed
Add EAPd Resource (.pdr) pattern for Spore (2008)
1 parent a534e2a commit 9dce989

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed

patterns/Spore/spore-pdr.hexpat

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
2+
3+
4+
#pragma author 0KepOnline
5+
#pragma description Spore EAPd Resource (Binary Patch)
6+
7+
#pragma magic [45 41 50 44] @ 0x00
8+
#pragma endian little
9+
10+
import std.mem;
11+
import std.string;
12+
import std.sys;
13+
14+
// "od"
15+
#define pd 0x6470
16+
// "smr"
17+
#define snr 0x726e73
18+
19+
#define default_version 3
20+
21+
22+
23+
// Universal parser for strings (both names and hashes)
24+
fn parse_string(auto value) {
25+
u8 byte = 0;
26+
try {
27+
return parse_string(value.text);
28+
}
29+
catch {
30+
try {
31+
return parse_string(value.data);
32+
}
33+
catch {
34+
try {
35+
byte = u8(value[0]);
36+
return std::string::to_string(value);
37+
}
38+
catch {
39+
str result = "";
40+
for (u32 i = 0, i < sizeof(value), i = i + 1) {
41+
byte = u8((value >> (i * 8)) & 0xff);
42+
if (byte < 0x20) break;
43+
result = result + char(byte);
44+
}
45+
return result;
46+
}
47+
}
48+
}
49+
return "";
50+
};
51+
52+
// 4-byte strings that are treated as numbers in the EAPd code
53+
union str32_t {
54+
u32 number;
55+
char text[4];
56+
} [[sealed]];
57+
58+
// Universal parser for versions (see which value v1 uses to find out why)
59+
fn parse_version(auto value) {
60+
str32_t version;
61+
try {
62+
version.number = u32(value);
63+
}
64+
catch {
65+
try {
66+
version.number = u32(value.value);
67+
}
68+
catch {
69+
return default_version;
70+
}
71+
}
72+
if (version.text == " 1.0") return 1;
73+
return version.number;
74+
};
75+
76+
// 4-byte EAPd Binary Patch version number, similar to str32_t (see which value v1 uses to find out why)
77+
union version_t {
78+
u32 value [[hidden]];
79+
if (parse_version(value) == 1) char text[4];
80+
else u32 number;
81+
} [[sealed]];
82+
83+
// .pdr header
84+
struct header_t {
85+
char signature[4] [[name("Signature"), comment("EAPd signature/magic")]];
86+
version_t version [[format("parse_version"), name("Version"), comment("EAPd Binary Patch version")]];
87+
u32 platform [[name("Platform"), comment("Platform, 2 = PC; it's believed that Xbox 360 and PS3 are also supported")]];
88+
u32 length [[name("Length"), comment("Full length, including signature and header")]];
89+
u32 patch_count [[name("Patch Count"), comment("Number of EAPd patches stored inside a Binary Patch")]];
90+
u32 sample_count [[name("Sample Count"), comment("Number of SNR/SNS audio samples stored inside a Binary Patch")]];
91+
};
92+
93+
// Join the asset name and hash, treating it as a file extension
94+
fn get_asset_name(ref auto asset) {
95+
try {
96+
return parse_string(asset.name) + "." + parse_string(asset.hash);
97+
}
98+
catch {
99+
return parse_string(asset.name);
100+
}
101+
return "";
102+
};
103+
104+
// Length + Payload; same for all versions
105+
struct asset_chunk_t {
106+
u32 length [[name("Asset Data Length"), comment("Length of data of an asset")]];
107+
u8 data[length] [[name("Asset Data"), sealed, comment("Raw data of an asset")]];
108+
} [[inline]];
109+
110+
// Asset record: v1 (1.0)
111+
struct asset_v1_t<auto predefined_hash> {
112+
char name[0x18] [[format("parse_string"), name("Asset Name"), comment("Unique name that is used both internally (by EAPd engine) and externally (in patches)")]];
113+
asset_chunk_t chunk;
114+
auto hash = predefined_hash;
115+
} [[name(get_asset_name(this))]];
116+
117+
// Asset record: v2
118+
struct asset_v2_t<auto predefined_hash> {
119+
std::string::NullString name [[format("parse_string"), name("Asset Name"), comment("Unique name that is used both internally (by EAPd engine) and externally (in patches)")]];
120+
asset_chunk_t chunk;
121+
auto hash = predefined_hash;
122+
} [[name(get_asset_name(this))]];
123+
124+
// Asset record: v3
125+
struct asset_v3_t {
126+
std::string::SizedString<u32> name [[format("parse_string"), name("Asset Name"), comment("Unique name that is used both internally (by EAPd engine) and externally (in patches)")]];
127+
str32_t hash [[format("parse_string"), name("Asset Hash"), comment("Hardcoded value that determines the type of an asset")]];
128+
asset_chunk_t chunk;
129+
} [[name(get_asset_name(this))]];
130+
131+
132+
133+
struct EAPdResource {
134+
header_t header [[name("Header"), comment("EAPd Resource Header")]];
135+
u32 version = parse_version(header.version);
136+
u32 platform = header.platform;
137+
std::assert(platform == 2, std::format("Unsupported EAPd Binary Patch platform ({}): only PC (2) is supported for now", version));
138+
match (version) {
139+
(1): {
140+
asset_v1_t<pd> patches[header.patch_count] [[name("Patches"), comment("Pd Patches")]];
141+
asset_v1_t<snr> samples[header.sample_count] [[name("Patches"), comment("SNR/SNS Audio Samples")]];
142+
}
143+
(2): {
144+
asset_v2_t<pd> patches[header.patch_count] [[name("Patches"), comment("Pd Patches")]];
145+
asset_v2_t<snr> samples[header.sample_count] [[name("Patches"), comment("SNR/SNS Audio Samples")]];
146+
}
147+
(3): {
148+
asset_v3_t patches[header.patch_count] [[name("Patches"), comment("Pd Patches")]];
149+
asset_v3_t samples[header.sample_count] [[name("Patches"), comment("SNR/SNS Audio Samples")]];
150+
}
151+
(_): std::assert(version >= 1 && version <= 3, std::format("Unsupported EAPd Binary Patch version ({})", version));
152+
}
153+
};
154+
155+
EAPdResource eapdResource @0x00 [[name("EAPd Resource")]];

0 commit comments

Comments
 (0)