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]);
62 LOG_INFO(
"ZsprLoader",
"ZSPR v%d: sprite@0x%04X (%d bytes), palette@0x%04X (%d bytes), type=%d",
69 size_t checksum_start = sprite_offset;
70 size_t checksum_length = sprite_size + palette_size + 4;
71 if (checksum_start + checksum_length <= data.size()) {
73 std::vector<uint8_t>(data.begin() + checksum_start,
74 data.begin() + checksum_start + checksum_length),
76 LOG_WARN(
"ZsprLoader",
"ZSPR checksum mismatch (expected 0x%08X)", checksum);
83 size_t string_offset = 0x13;
84 size_t bytes_read = 0;
87 if (string_offset < data.size()) {
89 &data[string_offset], data.size() - string_offset, bytes_read);
90 string_offset += bytes_read;
94 if (string_offset < data.size()) {
96 &data[string_offset], data.size() - string_offset, bytes_read);
97 string_offset += bytes_read;
101 if (string_offset < data.size() && string_offset < sprite_offset) {
103 &data[string_offset], data.size() - string_offset, bytes_read);
104 string_offset += bytes_read;
107 LOG_INFO(
"ZsprLoader",
"ZSPR: '%s' by %s",
112 if (sprite_offset + sprite_size > data.size()) {
113 return absl::InvalidArgumentError(
114 absl::StrFormat(
"ZSPR sprite data extends beyond file: offset=%d, size=%d, file_size=%zu",
115 sprite_offset, sprite_size, data.size()));
117 zspr.
sprite_data.assign(data.begin() + sprite_offset,
118 data.begin() + sprite_offset + sprite_size);
122 LOG_WARN(
"ZsprLoader",
"Unexpected sprite data size for Link sprite: %d (expected %zu)",
127 if (palette_offset + palette_size > data.size()) {
128 return absl::InvalidArgumentError(
129 absl::StrFormat(
"ZSPR palette data extends beyond file: offset=%d, size=%d, file_size=%zu",
130 palette_offset, palette_size, data.size()));
133 data.begin() + palette_offset + palette_size);
136 size_t glove_offset = palette_offset + palette_size;
137 if (glove_offset + 4 <= data.size()) {
140 LOG_INFO(
"ZsprLoader",
"Glove colors: 0x%04X, 0x%04X",
149 return absl::FailedPreconditionError(
"ROM not loaded");
153 return absl::InvalidArgumentError(
"ZSPR is not a Link sprite (type != 0)");
157 return absl::InvalidArgumentError(
158 absl::StrFormat(
"Invalid sprite data size: %zu (expected %zu)",
172 constexpr uint32_t kLinkGfxBaseUS = 0x80000;
173 constexpr size_t kBytesPerSheet = 2048;
175 LOG_INFO(
"ZsprLoader",
"Applying ZSPR '%s' to ROM (%zu bytes of sprite data)",
180 uint32_t rom_offset = kLinkGfxBaseUS + (sheet * kBytesPerSheet);
181 size_t data_offset = sheet * kBytesPerSheet;
184 if (data_offset + kBytesPerSheet > zspr.
sprite_data.size()) {
185 LOG_WARN(
"ZsprLoader",
"Sheet %zu data incomplete, stopping", sheet);
190 for (
size_t i = 0; i < kBytesPerSheet; i++) {
193 return absl::InternalError(
194 absl::StrFormat(
"Failed to write byte at 0x%06X: %s",
195 rom_offset + i, status.message()));
199 LOG_INFO(
"ZsprLoader",
"Wrote Link sheet %zu at ROM offset 0x%06X",
203 return absl::OkStatus();
208 return absl::FailedPreconditionError(
"ROM not loaded");
212 return absl::InvalidArgumentError(
213 absl::StrFormat(
"Invalid palette data size: %zu (expected %zu)",
225 constexpr uint32_t kLinkPaletteBase = 0x0DD308;
226 constexpr size_t kPaletteSize = 30;
229 LOG_INFO(
"ZsprLoader",
"Applying ZSPR palette data (%zu bytes)",
234 uint32_t rom_offset = kLinkPaletteBase + (pal * kPaletteSize);
235 size_t data_offset = pal * kPaletteSize;
237 for (
size_t i = 0; i < kPaletteSize; i++) {
240 return absl::InternalError(
241 absl::StrFormat(
"Failed to write palette byte at 0x%06X", rom_offset + i));
245 LOG_INFO(
"ZsprLoader",
"Wrote palette %zu at ROM offset 0x%06X", pal, rom_offset);
252 LOG_INFO(
"ZsprLoader",
"Glove colors loaded but not yet written (TODO: find offsets)");
254 return absl::OkStatus();