Skip to content

Commit 90871eb

Browse files
0KepOnlinepaxcut
andauthored
Add EAPd Resource (.pdr) pattern for Spore (2008) (#501)
* patterns: Add EAPd Resource (`.pdr`) pattern for Spore (2008) * patterns: Add enum for platform types in EAPd Resource files * patterns: Fix minor bugs * tests: Replace `spore-pdr.hexpat` test with a custom, more robust test file * tests: Fix `spore-pdr.hexpat` test (total length: 0xc) --------- Co-authored-by: paxcut <53811119+paxcut@users.noreply.github.com>
1 parent 1579825 commit 90871eb

3 files changed

Lines changed: 182 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
213213
| sup | | [`patterns/sup.hexpat`](patterns/sup.hexpat) | PGS Subtitle |
214214
| SPC | | [`patterns/spc.hexpat`](patterns/spc.hexpat) | Super Nintendo Entertainment System SPC-700 dump file |
215215
| SPIRV | | [`patterns/spirv.hexpat`](patterns/spirv.hexpat) | SPIR-V header and instructions |
216+
| Spore EAPd Resource | | [`patterns/Spore/spore-pdr.hexpat`](patterns/Spore/spore-pdr.hexpat) | Spore EAPd Resource (Binary Patch) |
216217
| STDF | | [`patterns/stdfv4.hexpat`](patterns/stdfv4.hexpat) | Standard test data format for IC testers |
217218
| STL | `model/stl` | [`patterns/stl.hexpat`](patterns/stl.hexpat) | STL 3D Model format |
218219
| StuffItV5 | `application/x-stuffit` | [`patterns/sit5.hexpat`](patterns/sit5.hexpat) | StuffIt V5 archive |

patterns/Spore/spore-pdr.hexpat

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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+
// "pd"
15+
#define pd 0x6470
16+
// "snr"
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+
u32 length = std::string::length(value);
37+
while (length > 0 && std::string::at(value, length - 1) == ' ') length = length - 1;
38+
return std::string::to_string(std::string::substr(value, 0, length));
39+
}
40+
catch {
41+
str result = "";
42+
for (u32 i = 0, i < sizeof(value), i = i + 1) {
43+
byte = u8((value >> (i * 8)) & 0xff);
44+
if (byte < 0x20) break;
45+
result = result + char(byte);
46+
}
47+
return result;
48+
}
49+
}
50+
}
51+
return "";
52+
};
53+
54+
// 4-byte strings that are treated as numbers in the EAPd code
55+
union str32_t {
56+
u32 number;
57+
char text[4];
58+
} [[sealed]];
59+
60+
// Universal parser for versions (see which value v1 uses to find out why)
61+
fn parse_version(auto value) {
62+
str32_t version;
63+
try {
64+
version.number = u32(value);
65+
}
66+
catch {
67+
try {
68+
version.number = u32(value.value);
69+
}
70+
catch {
71+
return default_version;
72+
}
73+
}
74+
if (version.text == " 1.0") return 1;
75+
return version.number;
76+
};
77+
78+
// 4-byte EAPd Binary Patch version number, similar to str32_t (see which value v1 uses to find out why)
79+
union version_t {
80+
u32 value [[hidden]];
81+
if (parse_version(value) == 1) char text[4];
82+
else u32 number;
83+
} [[sealed]];
84+
85+
// Platform type (from "PlatformType" enum)
86+
enum platform_t: s32 {
87+
NONE, // Likely unused?
88+
GENERIC,
89+
WINDOWS, // The only known platform type so far
90+
XENON, // Xbox 360?
91+
PS3,
92+
REVOLUTION // Wii?
93+
};
94+
95+
// Fancy platform names
96+
fn parse_platform(auto platform) {
97+
match (platform) {
98+
(0): return "None";
99+
(1): return "Generic";
100+
(2): return "Windows";
101+
(3): return "Xenon";
102+
(4): return "PlayStation 3";
103+
(5): return "Revolution";
104+
}
105+
return "Unknown";
106+
};
107+
108+
// .pdr header
109+
struct header_t {
110+
char signature[4] [[name("Signature"), comment("EAPd signature/magic")]];
111+
version_t version [[format("parse_version"), name("Version"), comment("EAPd Binary Patch version")]];
112+
platform_t platform [[format("parse_platform"), name("Platform"), comment("EAPd Binary Patch platform type"), color("a0a0a0")]];
113+
u32 length [[name("Length"), comment("Full length, including signature and header"), color("a000a0")]];
114+
u32 patch_count [[name("Patch Count"), comment("Number of EAPd patches stored inside a Binary Patch")]];
115+
u32 sample_count [[name("Sample Count"), comment("Number of SNR/SNS audio samples stored inside a Binary Patch")]];
116+
};
117+
118+
// Join the asset name and hash, treating it as a file extension
119+
fn get_asset_name(ref auto asset) {
120+
try {
121+
if (parse_string(asset.hash) != "") return parse_string(asset.name) + "." + parse_string(asset.hash);
122+
else return parse_string(asset.name);
123+
}
124+
catch {
125+
return parse_string(asset.name);
126+
}
127+
return "";
128+
};
129+
130+
// Length + Payload; same for all versions
131+
struct asset_chunk_t {
132+
u32 length [[name("Asset Data Length"), comment("Length of data of an asset")]];
133+
u8 data[length] [[name("Asset Data"), sealed, comment("Raw data of an asset")]];
134+
} [[inline]];
135+
136+
// Asset record: v1 (1.0)
137+
struct asset_v1_t<auto predefined_hash> {
138+
char name[0x19] [[format("parse_string"), name("Asset Name"), comment("Unique name that is used both internally (by EAPd engine) and externally (in patches)")]];
139+
asset_chunk_t chunk;
140+
auto hash = predefined_hash;
141+
} [[name(get_asset_name(this))]];
142+
143+
// Asset record: v2
144+
struct asset_v2_t<auto predefined_hash> {
145+
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)")]];
146+
asset_chunk_t chunk;
147+
auto hash = predefined_hash;
148+
} [[name(get_asset_name(this))]];
149+
150+
// Asset record: v3
151+
struct asset_v3_t {
152+
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)")]];
153+
str32_t hash [[format("parse_string"), name("Asset Hash"), comment("Hardcoded value that determines the type of an asset")]];
154+
asset_chunk_t chunk;
155+
} [[name(get_asset_name(this))]];
156+
157+
158+
159+
struct EAPdResource {
160+
header_t header [[name("Header"), comment("EAPd Resource Header")]];
161+
u32 version = parse_version(header.version);
162+
s32 platform = header.platform;
163+
std::assert(platform == 2, std::format("Unsupported EAPd Binary Patch platform ({}): only Windows (2) is supported for now", version));
164+
match (version) {
165+
(1): {
166+
asset_v1_t<pd> patches[header.patch_count] [[name("Patches"), comment("Pd Patches")]];
167+
asset_v1_t<snr> samples[header.sample_count] [[name("Samples"), comment("SNR/SNS Audio Samples")]];
168+
}
169+
(2): {
170+
asset_v2_t<pd> patches[header.patch_count] [[name("Patches"), comment("Pd Patches")]];
171+
asset_v2_t<snr> samples[header.sample_count] [[name("Samples"), comment("SNR/SNS Audio Samples")]];
172+
}
173+
(3): {
174+
asset_v3_t patches[header.patch_count] [[name("Patches"), comment("Pd Patches")]];
175+
asset_v3_t samples[header.sample_count] [[name("Samples"), comment("SNR/SNS Audio Samples")]];
176+
}
177+
(_): std::assert(version >= 1 && version <= 3, std::format("Unsupported EAPd Binary Patch version ({})", version));
178+
}
179+
};
180+
181+
EAPdResource eapdResource @0x00 [[name("EAPd Resource")]];
16.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)