this repo has no description
1// MIT License
2
3// Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "retro_endianness.h"
24#include "cart.h"
25
26#if defined(BUILD_DEPRECATED)
27#include "tools.h"
28#include "ext/gif.h"
29#endif
30
31#include <string.h>
32#include <stdlib.h>
33#include "tic_assert.h"
34#include "tools.h"
35
36typedef enum
37{
38 CHUNK_DUMMY, // 0
39 CHUNK_TILES, // 1
40 CHUNK_SPRITES, // 2
41 CHUNK_COVER_DEP, // 3 - deprecated chunk
42 CHUNK_MAP, // 4
43 CHUNK_CODE, // 5
44 CHUNK_FLAGS, // 6
45 CHUNK_TEMP2, // 7
46 CHUNK_TEMP3, // 8
47 CHUNK_SAMPLES, // 9
48 CHUNK_WAVEFORM, // 10
49 CHUNK_TEMP4, // 11
50 CHUNK_PALETTE, // 12
51 CHUNK_PATTERNS_DEP, // 13 - deprecated chunk
52 CHUNK_MUSIC, // 14
53 CHUNK_PATTERNS, // 15
54 CHUNK_CODE_ZIP, // 16
55 CHUNK_DEFAULT, // 17
56 CHUNK_SCREEN, // 18
57 CHUNK_BINARY, // 19
58 CHUNK_LANG, // 20
59} ChunkType;
60
61typedef struct
62{
63#if RETRO_IS_BIG_ENDIAN
64 u32 bank:TIC_BANK_BITS;
65 u32 type:5; // ChunkType
66#else
67 u32 type:5; // ChunkType
68 u32 bank:TIC_BANK_BITS;
69#endif
70 u32 size:TIC_BANKSIZE_BITS; // max chunk size is 64K
71 u32 temp:8;
72} Chunk;
73
74static_assert(sizeof(Chunk) == 4, "tic_chunk_size");
75
76static const u8 Sweetie16[] = {0x1a, 0x1c, 0x2c, 0x5d, 0x27, 0x5d, 0xb1, 0x3e, 0x53, 0xef, 0x7d, 0x57, 0xff, 0xcd, 0x75, 0xa7, 0xf0, 0x70, 0x38, 0xb7, 0x64, 0x25, 0x71, 0x79, 0x29, 0x36, 0x6f, 0x3b, 0x5d, 0xc9, 0x41, 0xa6, 0xf6, 0x73, 0xef, 0xf7, 0xf4, 0xf4, 0xf4, 0x94, 0xb0, 0xc2, 0x56, 0x6c, 0x86, 0x33, 0x3c, 0x57};
77static const u8 Waveforms[] = {0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe};
78
79static s32 chunkSize(const Chunk* chunk)
80{
81 return chunk->size == 0 && (chunk->type == CHUNK_CODE || chunk->type == CHUNK_BINARY) ? TIC_BANK_SIZE : retro_le_to_cpu16(chunk->size);
82}
83
84void tic_cart_load(tic_cartridge* cart, const u8* buffer, s32 size)
85{
86 memset(cart, 0, sizeof(tic_cartridge));
87 const u8* end = buffer + size;
88 u8 *chunk_cart = NULL;
89
90 // check if this cartridge is in PNG format
91 if (!memcmp(buffer, "\x89PNG", 4))
92 {
93 s32 siz;
94 const u8* ptr = buffer + 8;
95 // iterate on chunks until we find a cartridge
96 while (ptr < end)
97 {
98 siz = ((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]);
99 if (!memcmp(ptr + 4, "caRt", 4) && siz > 0)
100 {
101 chunk_cart = malloc(sizeof(tic_cartridge));
102 if (chunk_cart)
103 {
104 size = tic_tool_unzip(chunk_cart, sizeof(tic_cartridge), ptr + 8, siz);
105 buffer = chunk_cart;
106 end = buffer + size;
107 }
108 break;
109 }
110 ptr += siz + 12;
111 }
112 // error, no TIC-80 cartridge chunk in PNG???
113 if (!chunk_cart)
114 return;
115 }
116
117#define LOAD_CHUNK(to) memcpy(&to, ptr, MIN(sizeof(to), chunk->size ? retro_le_to_cpu16(chunk->size) : TIC_BANK_SIZE))
118
119 // load palette chunk first
120 {
121 const u8* ptr = buffer;
122 while (ptr < end)
123 {
124 const Chunk* chunk = (Chunk*)ptr;
125 ptr += sizeof(Chunk);
126
127 switch (chunk->type)
128 {
129 case CHUNK_PALETTE:
130 LOAD_CHUNK(cart->banks[chunk->bank].palette);
131 break;
132 case CHUNK_DEFAULT:
133 memcpy(&cart->banks[chunk->bank].palette, Sweetie16, sizeof Sweetie16);
134 memcpy(&cart->banks[chunk->bank].sfx.waveforms, Waveforms, sizeof Waveforms);
135 break;
136 default: break;
137 }
138
139 ptr += chunkSize(chunk);
140 }
141
142#if defined(BUILD_DEPRECATED)
143 // workaround to support ancient carts without palette
144 // load DB16 palette if it not exists
145 if (EMPTY(cart->bank0.palette.vbank0.data))
146 {
147 static const u8 DB16[] = { 0x14, 0x0c, 0x1c, 0x44, 0x24, 0x34, 0x30, 0x34, 0x6d, 0x4e, 0x4a, 0x4e, 0x85, 0x4c, 0x30, 0x34, 0x65, 0x24, 0xd0, 0x46, 0x48, 0x75, 0x71, 0x61, 0x59, 0x7d, 0xce, 0xd2, 0x7d, 0x2c, 0x85, 0x95, 0xa1, 0x6d, 0xaa, 0x2c, 0xd2, 0xaa, 0x99, 0x6d, 0xc2, 0xca, 0xda, 0xd4, 0x5e, 0xde, 0xee, 0xd6 };
148 memcpy(cart->bank0.palette.vbank0.data, DB16, sizeof DB16);
149 }
150#endif
151 }
152
153 struct CodeChunk {s32 size; const char* data;} code[TIC_BANKS] = {0};
154 struct BinaryChunk {s32 size; const u8* data;} binary[TIC_BINARY_BANKS] = {0};
155
156 {
157 const u8* ptr = buffer;
158 while(ptr < end)
159 {
160 const Chunk* chunk = (Chunk*)ptr;
161 ptr += sizeof(Chunk);
162
163 switch(chunk->type)
164 {
165 case CHUNK_TILES: LOAD_CHUNK(cart->banks[chunk->bank].tiles); break;
166 case CHUNK_SPRITES: LOAD_CHUNK(cart->banks[chunk->bank].sprites); break;
167 case CHUNK_MAP: LOAD_CHUNK(cart->banks[chunk->bank].map); break;
168 case CHUNK_SAMPLES: LOAD_CHUNK(cart->banks[chunk->bank].sfx.samples); break;
169 case CHUNK_WAVEFORM: LOAD_CHUNK(cart->banks[chunk->bank].sfx.waveforms); break;
170 case CHUNK_MUSIC: LOAD_CHUNK(cart->banks[chunk->bank].music.tracks); break;
171 case CHUNK_PATTERNS: LOAD_CHUNK(cart->banks[chunk->bank].music.patterns); break;
172 case CHUNK_FLAGS: LOAD_CHUNK(cart->banks[chunk->bank].flags); break;
173 case CHUNK_SCREEN: LOAD_CHUNK(cart->banks[chunk->bank].screen); break;
174 case CHUNK_LANG: LOAD_CHUNK(cart->lang); break;
175 case CHUNK_BINARY:
176 binary[chunk->bank] = (struct BinaryChunk){chunkSize(chunk), ptr};
177 break;
178 case CHUNK_CODE:
179 code[chunk->bank] = (struct CodeChunk){chunkSize(chunk), (char*)ptr};
180 break;
181#if defined(BUILD_DEPRECATED)
182 case CHUNK_CODE_ZIP:
183 tic_tool_unzip(cart->code.data, TIC_CODE_SIZE, ptr, retro_le_to_cpu16(chunk->size));
184 break;
185 case CHUNK_COVER_DEP:
186 {
187 // workaround to load deprecated cover section
188 gif_image* image = gif_read_data(ptr, retro_le_to_cpu16(chunk->size));
189
190 if (image)
191 {
192 if(image->width == TIC80_WIDTH && image->height == TIC80_HEIGHT)
193 for (s32 i = 0; i < TIC80_WIDTH * TIC80_HEIGHT; i++)
194 tic_tool_poke4(cart->bank0.screen.data, i,
195 tic_nearest_color(cart->bank0.palette.vbank0.colors, (const tic_rgb*)&image->palette[image->buffer[i]], TIC_PALETTE_SIZE));
196
197 gif_close(image);
198 }
199 }
200 break;
201 case CHUNK_PATTERNS_DEP:
202 {
203 // workaround to load deprecated music patterns section
204 // and automatically convert volume value to a command
205 tic_patterns* ptrns = &cart->banks[chunk->bank].music.patterns;
206 LOAD_CHUNK(*ptrns);
207 for(s32 i = 0; i < MUSIC_PATTERNS; i++)
208 for(s32 r = 0; r < MUSIC_PATTERN_ROWS; r++)
209 {
210 tic_track_row* row = &ptrns->data[i].rows[r];
211 if(row->note >= NoteStart && row->command == tic_music_cmd_empty)
212 {
213 row->command = tic_music_cmd_volume;
214 row->param2 = row->param1 = MAX_VOLUME - row->param1;
215 }
216 }
217 }
218 break;
219#endif
220 default: break;
221 }
222
223 ptr += chunkSize(chunk);
224 }
225#undef LOAD_CHUNK
226
227 {
228 u32 total_size = 0;
229 char* ptr = cart->binary.data;
230 RFOR(const struct BinaryChunk*, chunk, binary)
231 if (chunk->size)
232 {
233 memcpy(ptr, chunk->data, chunk->size);
234 ptr += chunk->size;
235 total_size += chunk->size;
236 }
237 cart->binary.size = total_size;
238 }
239
240 if (!*cart->code.data)
241 {
242 char* ptr = cart->code.data;
243 RFOR(const struct CodeChunk*, chunk, code)
244 if (chunk->data)
245 {
246 memcpy(ptr, chunk->data, chunk->size);
247 ptr += chunk->size;
248 }
249 }
250 }
251 // if we have allocated the buffer from a PNG chunk
252 if (chunk_cart)
253 free(chunk_cart);
254}
255
256
257static s32 calcBufferSize(const void* buffer, s32 size)
258{
259 const u8* ptr = (u8*)buffer + size - 1;
260 const u8* end = (u8*)buffer;
261
262 while(ptr >= end)
263 {
264 if(*ptr) break;
265
266 ptr--;
267 size--;
268 }
269
270 return size;
271}
272
273static u8* saveFixedChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
274{
275 if(size)
276 {
277 Chunk chunk = {.type = type, .bank = bank, .size = retro_le_to_cpu16(size), .temp = 0};
278 memcpy(buffer, &chunk, sizeof(Chunk));
279 buffer += sizeof(Chunk);
280
281 memcpy(buffer, from, size);
282 buffer += size;
283 }
284
285 return buffer;
286}
287
288static u8* saveChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
289{
290 s32 chunkSize = calcBufferSize(from, size);
291
292 return saveFixedChunk(buffer, type, from, chunkSize, bank);
293}
294
295s32 tic_cart_save(const tic_cartridge* cart, u8* buffer)
296{
297 u8* start = buffer;
298
299#define SAVE_CHUNK(ID, FROM, BANK) saveChunk(buffer, ID, &FROM, sizeof(FROM), BANK)
300
301 tic_waveforms defaultWaveforms = {0};
302 tic_palettes defaultPalettes = {0};
303
304 memcpy(&defaultWaveforms, Waveforms, sizeof Waveforms);
305 memcpy(&defaultPalettes, Sweetie16, sizeof Sweetie16);
306
307 for(s32 i = 0; i < TIC_BANKS; i++)
308 {
309 if(memcmp(&cart->banks[i].sfx.waveforms, &defaultWaveforms, sizeof defaultWaveforms) == 0
310 && memcmp(&cart->banks[i].palette, &defaultPalettes, sizeof defaultPalettes) == 0)
311 {
312 Chunk chunk = {.type = CHUNK_DEFAULT, .bank = i, .size = 0, .temp = 0};
313 memcpy(buffer, &chunk, sizeof chunk);
314 buffer += sizeof chunk;
315 }
316 else
317 {
318 buffer = SAVE_CHUNK(CHUNK_PALETTE, cart->banks[i].palette, i);
319 buffer = SAVE_CHUNK(CHUNK_WAVEFORM, cart->banks[i].sfx.waveforms, i);
320 }
321
322 buffer = SAVE_CHUNK(CHUNK_TILES, cart->banks[i].tiles, i);
323 buffer = SAVE_CHUNK(CHUNK_SPRITES, cart->banks[i].sprites, i);
324 buffer = SAVE_CHUNK(CHUNK_MAP, cart->banks[i].map, i);
325 buffer = SAVE_CHUNK(CHUNK_SAMPLES, cart->banks[i].sfx.samples, i);
326 buffer = SAVE_CHUNK(CHUNK_PATTERNS, cart->banks[i].music.patterns, i);
327 buffer = SAVE_CHUNK(CHUNK_MUSIC, cart->banks[i].music.tracks, i);
328 buffer = SAVE_CHUNK(CHUNK_FLAGS, cart->banks[i].flags, i);
329 buffer = SAVE_CHUNK(CHUNK_SCREEN, cart->banks[i].screen, i);
330 }
331
332 const char* ptr;
333 if (cart->binary.size)
334 {
335 ptr = cart->binary.data;
336 s32 remaining = cart->binary.size;
337 for (s32 i = cart->binary.size / TIC_BANK_SIZE; i >= 0; --i, ptr += TIC_BANK_SIZE)
338 {
339 buffer = saveFixedChunk(buffer, CHUNK_BINARY, ptr, MIN(remaining, TIC_BANK_SIZE), i);
340 remaining -= TIC_BANK_SIZE;
341 }
342 }
343
344 ptr = cart->code.data;
345 for(s32 i = strlen(ptr) / TIC_BANK_SIZE; i >= 0; --i, ptr += TIC_BANK_SIZE)
346 buffer = saveFixedChunk(buffer, CHUNK_CODE, ptr, MIN(strlen(ptr), TIC_BANK_SIZE), i);
347
348 if(cart->lang)
349 SAVE_CHUNK(CHUNK_LANG, cart->lang, 0);
350
351#undef SAVE_CHUNK
352
353 return (s32)(buffer - start);
354}