15 std::ifstream file(path, std::ios::binary | std::ios::ate);
16 if (!file.is_open()) {
17 return absl::NotFoundError(
18 absl::StrFormat(
"Could not open ZSPR file: %s", path));
22 std::streamsize size = file.tellg();
23 file.seekg(0, std::ios::beg);
26 std::vector<uint8_t> data(size);
27 if (!file.read(
reinterpret_cast<char*
>(data.data()), size)) {
28 return absl::InternalError(
29 absl::StrFormat(
"Failed to read ZSPR file: %s", path));
36 const std::vector<uint8_t>& data) {
38 constexpr size_t kMinHeaderSize = 0x13;
39 if (data.size() < kMinHeaderSize) {
40 return absl::InvalidArgumentError(
41 absl::StrFormat(
"ZSPR file too small: %zu bytes (minimum %zu)",
42 data.size(), kMinHeaderSize));
46 if (data[0] !=
'Z' || data[1] !=
'S' || data[2] !=
'P' || data[3] !=
'R') {
47 return absl::InvalidArgumentError(
48 "Invalid ZSPR file: missing magic bytes 'ZSPR'");
55 uint32_t checksum =
ReadU32LE(&data[0x05]);
56 uint16_t sprite_offset =
ReadU16LE(&data[0x09]);
57 uint16_t sprite_size =
ReadU16LE(&data[0x0B]);
58 uint16_t palette_offset =
ReadU16LE(&data[0x0D]);
59 uint16_t palette_size =
ReadU16LE(&data[0x0F]);
64 "ZSPR v%d: sprite@0x%04X (%d bytes), palette@0x%04X (%d bytes), type=%d",
71 size_t checksum_start = sprite_offset;
72 size_t checksum_length =
73 sprite_size + palette_size + 4;
74 if (checksum_start + checksum_length <= data.size()) {
76 data.begin() + checksum_start +
79 LOG_WARN(
"ZsprLoader",
"ZSPR checksum mismatch (expected 0x%08X)",
87 size_t string_offset = 0x13;
88 size_t bytes_read = 0;
91 if (string_offset < data.size()) {
93 &data[string_offset], data.size() - string_offset, bytes_read);
94 string_offset += bytes_read;
98 if (string_offset < data.size()) {
100 &data[string_offset], data.size() - string_offset, bytes_read);
101 string_offset += bytes_read;
105 if (string_offset < data.size() && string_offset < sprite_offset) {
107 &data[string_offset], data.size() - string_offset, bytes_read);
108 string_offset += bytes_read;
115 if (sprite_offset + sprite_size > data.size()) {
116 return absl::InvalidArgumentError(
117 absl::StrFormat(
"ZSPR sprite data extends beyond file: offset=%d, "
118 "size=%d, file_size=%zu",
119 sprite_offset, sprite_size, data.size()));
121 zspr.
sprite_data.assign(data.begin() + sprite_offset,
122 data.begin() + sprite_offset + sprite_size);
127 "Unexpected sprite data size for Link sprite: %d (expected %zu)",
132 if (palette_offset + palette_size > data.size()) {
133 return absl::InvalidArgumentError(
134 absl::StrFormat(
"ZSPR palette data extends beyond file: offset=%d, "
135 "size=%d, file_size=%zu",
136 palette_offset, palette_size, data.size()));
139 data.begin() + palette_offset + palette_size);
142 size_t glove_offset = palette_offset + palette_size;
143 if (glove_offset + 4 <= data.size()) {
155 return absl::FailedPreconditionError(
"ROM not loaded");
159 return absl::InvalidArgumentError(
"ZSPR is not a Link sprite (type != 0)");
163 return absl::InvalidArgumentError(
164 absl::StrFormat(
"Invalid sprite data size: %zu (expected %zu)",
178 constexpr uint32_t kLinkGfxBaseUS = 0x80000;
179 constexpr size_t kBytesPerSheet = 2048;
181 LOG_INFO(
"ZsprLoader",
"Applying ZSPR '%s' to ROM (%zu bytes of sprite data)",
186 uint32_t rom_offset = kLinkGfxBaseUS + (sheet * kBytesPerSheet);
187 size_t data_offset = sheet * kBytesPerSheet;
190 if (data_offset + kBytesPerSheet > zspr.
sprite_data.size()) {
191 LOG_WARN(
"ZsprLoader",
"Sheet %zu data incomplete, stopping", sheet);
196 for (
size_t i = 0; i < kBytesPerSheet; i++) {
200 return absl::InternalError(
201 absl::StrFormat(
"Failed to write byte at 0x%06X: %s",
202 rom_offset + i, status.message()));
206 LOG_INFO(
"ZsprLoader",
"Wrote Link sheet %zu at ROM offset 0x%06X", sheet,
210 return absl::OkStatus();
215 return absl::FailedPreconditionError(
"ROM not loaded");
219 return absl::InvalidArgumentError(
220 absl::StrFormat(
"Invalid palette data size: %zu (expected %zu)",
232 constexpr uint32_t kLinkPaletteBase = 0x0DD308;
233 constexpr size_t kPaletteSize = 30;
236 LOG_INFO(
"ZsprLoader",
"Applying ZSPR palette data (%zu bytes)",
241 uint32_t rom_offset = kLinkPaletteBase + (pal * kPaletteSize);
242 size_t data_offset = pal * kPaletteSize;
244 for (
size_t i = 0; i < kPaletteSize; i++) {
248 return absl::InternalError(absl::StrFormat(
249 "Failed to write palette byte at 0x%06X", rom_offset + i));
253 LOG_INFO(
"ZsprLoader",
"Wrote palette %zu at ROM offset 0x%06X", pal,
262 "Glove colors loaded but not yet written (TODO: find offsets)");
264 return absl::OkStatus();