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
35typedef enum
36{
37 CHUNK_DUMMY, // 0
38 CHUNK_TILES, // 1
39 CHUNK_SPRITES, // 2
40 CHUNK_COVER_DEP, // 3 - deprecated chunk
41 CHUNK_MAP, // 4
42 CHUNK_CODE, // 5
43 CHUNK_FLAGS, // 6
44 CHUNK_TEMP2, // 7
45 CHUNK_TEMP3, // 8
46 CHUNK_SAMPLES, // 9
47 CHUNK_WAVEFORM, // 10
48 CHUNK_TEMP4, // 11
49 CHUNK_PALETTE, // 12
50 CHUNK_PATTERNS_DEP, // 13 - deprecated chunk
51 CHUNK_MUSIC, // 14
52 CHUNK_PATTERNS, // 15
53 CHUNK_CODE_ZIP, // 16
54 CHUNK_DEFAULT, // 17
55 CHUNK_SCREEN, // 18
56 CHUNK_BINARY, // 19
57 CHUNK_LANG, // 20
58} ChunkType;
59
60typedef struct
61{
62#if RETRO_IS_BIG_ENDIAN
63 u32 bank:TIC_BANK_BITS;
64 u32 type:5; // ChunkType
65#else
66 u32 type:5; // ChunkType
67 u32 bank:TIC_BANK_BITS;
68#endif
69 u32 size:TIC_BANKSIZE_BITS; // max chunk size is 64K
70 u32 temp:8;
71} Chunk;
72
73static_assert(sizeof(Chunk) == 4, "tic_chunk_size");
74
75static 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};
76static 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};
77
78static s32 chunkSize(const Chunk* chunk)
79{
80 return chunk->size == 0 && (chunk->type == CHUNK_CODE || chunk->type == CHUNK_BINARY) ? TIC_BANK_SIZE : retro_le_to_cpu16(chunk->size);
81}
82
83void tic_cart_load(tic_cartridge* cart, const u8* buffer, s32 size)
84{
85 memset(cart, 0, sizeof(tic_cartridge));
86 const u8* end = buffer + size;
87
88#define LOAD_CHUNK(to) memcpy(&to, ptr, MIN(sizeof(to), chunk->size ? retro_le_to_cpu16(chunk->size) : TIC_BANK_SIZE))
89
90 // load palette chunk first
91 {
92 const u8* ptr = buffer;
93 while (ptr < end)
94 {
95 const Chunk* chunk = (Chunk*)ptr;
96 ptr += sizeof(Chunk);
97
98 switch (chunk->type)
99 {
100 case CHUNK_PALETTE:
101 LOAD_CHUNK(cart->banks[chunk->bank].palette);
102 break;
103 case CHUNK_DEFAULT:
104 memcpy(&cart->banks[chunk->bank].palette, Sweetie16, sizeof Sweetie16);
105 memcpy(&cart->banks[chunk->bank].sfx.waveforms, Waveforms, sizeof Waveforms);
106 break;
107 default: break;
108 }
109
110 ptr += chunkSize(chunk);
111 }
112
113#if defined(BUILD_DEPRECATED)
114 // workaround to support ancient carts without palette
115 // load DB16 palette if it not exists
116 if (EMPTY(cart->bank0.palette.vbank0.data))
117 {
118 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 };
119 memcpy(cart->bank0.palette.vbank0.data, DB16, sizeof DB16);
120 }
121#endif
122 }
123
124 struct CodeChunk {s32 size; const char* data;} code[TIC_BANKS] = {0};
125 struct BinaryChunk {s32 size; const char* data;} binary[TIC_BINARY_BANKS] = {0};
126
127 {
128 const u8* ptr = buffer;
129 while(ptr < end)
130 {
131 const Chunk* chunk = (Chunk*)ptr;
132 ptr += sizeof(Chunk);
133
134 switch(chunk->type)
135 {
136 case CHUNK_TILES: LOAD_CHUNK(cart->banks[chunk->bank].tiles); break;
137 case CHUNK_SPRITES: LOAD_CHUNK(cart->banks[chunk->bank].sprites); break;
138 case CHUNK_MAP: LOAD_CHUNK(cart->banks[chunk->bank].map); break;
139 case CHUNK_SAMPLES: LOAD_CHUNK(cart->banks[chunk->bank].sfx.samples); break;
140 case CHUNK_WAVEFORM: LOAD_CHUNK(cart->banks[chunk->bank].sfx.waveforms); break;
141 case CHUNK_MUSIC: LOAD_CHUNK(cart->banks[chunk->bank].music.tracks); break;
142 case CHUNK_PATTERNS: LOAD_CHUNK(cart->banks[chunk->bank].music.patterns); break;
143 case CHUNK_FLAGS: LOAD_CHUNK(cart->banks[chunk->bank].flags); break;
144 case CHUNK_SCREEN: LOAD_CHUNK(cart->banks[chunk->bank].screen); break;
145 case CHUNK_LANG: LOAD_CHUNK(cart->lang); break;
146 case CHUNK_BINARY:
147 binary[chunk->bank] = (struct BinaryChunk){chunkSize(chunk), ptr};
148 break;
149 case CHUNK_CODE:
150 code[chunk->bank] = (struct CodeChunk){chunkSize(chunk), ptr};
151 break;
152#if defined(BUILD_DEPRECATED)
153 case CHUNK_CODE_ZIP:
154 tic_tool_unzip(cart->code.data, TIC_CODE_SIZE, ptr, retro_le_to_cpu16(chunk->size));
155 break;
156 case CHUNK_COVER_DEP:
157 {
158 // workaround to load deprecated cover section
159 gif_image* image = gif_read_data(ptr, retro_le_to_cpu16(chunk->size));
160
161 if (image)
162 {
163 if(image->width == TIC80_WIDTH && image->height == TIC80_HEIGHT)
164 for (s32 i = 0; i < TIC80_WIDTH * TIC80_HEIGHT; i++)
165 tic_tool_poke4(cart->bank0.screen.data, i,
166 tic_nearest_color(cart->bank0.palette.vbank0.colors, (const tic_rgb*)&image->palette[image->buffer[i]], TIC_PALETTE_SIZE));
167
168 gif_close(image);
169 }
170 }
171 break;
172 case CHUNK_PATTERNS_DEP:
173 {
174 // workaround to load deprecated music patterns section
175 // and automatically convert volume value to a command
176 tic_patterns* ptrns = &cart->banks[chunk->bank].music.patterns;
177 LOAD_CHUNK(*ptrns);
178 for(s32 i = 0; i < MUSIC_PATTERNS; i++)
179 for(s32 r = 0; r < MUSIC_PATTERN_ROWS; r++)
180 {
181 tic_track_row* row = &ptrns->data[i].rows[r];
182 if(row->note >= NoteStart && row->command == tic_music_cmd_empty)
183 {
184 row->command = tic_music_cmd_volume;
185 row->param2 = row->param1 = MAX_VOLUME - row->param1;
186 }
187 }
188 }
189 break;
190#endif
191 default: break;
192 }
193
194 ptr += chunkSize(chunk);
195 }
196#undef LOAD_CHUNK
197
198 {
199 u32 total_size = 0;
200 char* ptr = cart->binary.data;
201 RFOR(const struct BinaryChunk*, chunk, binary)
202 if (chunk->size)
203 {
204 memcpy(ptr, chunk->data, chunk->size);
205 ptr += chunk->size;
206 total_size += chunk->size;
207 }
208 cart->binary.size = total_size;
209 }
210
211 if (!*cart->code.data)
212 {
213 char* ptr = cart->code.data;
214 RFOR(const struct CodeChunk*, chunk, code)
215 if (chunk->data)
216 {
217 memcpy(ptr, chunk->data, chunk->size);
218 ptr += chunk->size;
219 }
220 }
221 }
222}
223
224
225static s32 calcBufferSize(const void* buffer, s32 size)
226{
227 const u8* ptr = (u8*)buffer + size - 1;
228 const u8* end = (u8*)buffer;
229
230 while(ptr >= end)
231 {
232 if(*ptr) break;
233
234 ptr--;
235 size--;
236 }
237
238 return size;
239}
240
241static u8* saveFixedChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
242{
243 if(size)
244 {
245 Chunk chunk = {.type = type, .bank = bank, .size = retro_le_to_cpu16(size), .temp = 0};
246 memcpy(buffer, &chunk, sizeof(Chunk));
247 buffer += sizeof(Chunk);
248
249 memcpy(buffer, from, size);
250 buffer += size;
251 }
252
253 return buffer;
254}
255
256static u8* saveChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
257{
258 s32 chunkSize = calcBufferSize(from, size);
259
260 return saveFixedChunk(buffer, type, from, chunkSize, bank);
261}
262
263s32 tic_cart_save(const tic_cartridge* cart, u8* buffer)
264{
265 u8* start = buffer;
266
267#define SAVE_CHUNK(ID, FROM, BANK) saveChunk(buffer, ID, &FROM, sizeof(FROM), BANK)
268
269 tic_waveforms defaultWaveforms = {0};
270 tic_palettes defaultPalettes = {0};
271
272 memcpy(&defaultWaveforms, Waveforms, sizeof Waveforms);
273 memcpy(&defaultPalettes, Sweetie16, sizeof Sweetie16);
274
275 for(s32 i = 0; i < TIC_BANKS; i++)
276 {
277 if(memcmp(&cart->banks[i].sfx.waveforms, &defaultWaveforms, sizeof defaultWaveforms) == 0
278 && memcmp(&cart->banks[i].palette, &defaultPalettes, sizeof defaultPalettes) == 0)
279 {
280 Chunk chunk = {.type = CHUNK_DEFAULT, .bank = i, .size = 0, .temp = 0};
281 memcpy(buffer, &chunk, sizeof chunk);
282 buffer += sizeof chunk;
283 }
284 else
285 {
286 buffer = SAVE_CHUNK(CHUNK_PALETTE, cart->banks[i].palette, i);
287 buffer = SAVE_CHUNK(CHUNK_WAVEFORM, cart->banks[i].sfx.waveforms, i);
288 }
289
290 buffer = SAVE_CHUNK(CHUNK_TILES, cart->banks[i].tiles, i);
291 buffer = SAVE_CHUNK(CHUNK_SPRITES, cart->banks[i].sprites, i);
292 buffer = SAVE_CHUNK(CHUNK_MAP, cart->banks[i].map, i);
293 buffer = SAVE_CHUNK(CHUNK_SAMPLES, cart->banks[i].sfx.samples, i);
294 buffer = SAVE_CHUNK(CHUNK_PATTERNS, cart->banks[i].music.patterns, i);
295 buffer = SAVE_CHUNK(CHUNK_MUSIC, cart->banks[i].music.tracks, i);
296 buffer = SAVE_CHUNK(CHUNK_FLAGS, cart->banks[i].flags, i);
297 buffer = SAVE_CHUNK(CHUNK_SCREEN, cart->banks[i].screen, i);
298 }
299
300 const char* ptr;
301 if (cart->binary.size)
302 {
303 ptr = cart->binary.data;
304 s32 remaining = cart->binary.size;
305 for (s32 i = cart->binary.size / TIC_BANK_SIZE; i >= 0; --i, ptr += TIC_BANK_SIZE)
306 {
307 buffer = saveFixedChunk(buffer, CHUNK_BINARY, ptr, MIN(remaining, TIC_BANK_SIZE), i);
308 remaining -= TIC_BANK_SIZE;
309 }
310 }
311
312 ptr = cart->code.data;
313 for(s32 i = strlen(ptr) / TIC_BANK_SIZE; i >= 0; --i, ptr += TIC_BANK_SIZE)
314 buffer = saveFixedChunk(buffer, CHUNK_CODE, ptr, MIN(strlen(ptr), TIC_BANK_SIZE), i);
315
316 if(cart->lang)
317 SAVE_CHUNK(CHUNK_LANG, cart->lang, 0);
318
319#undef SAVE_CHUNK
320
321 return (s32)(buffer - start);
322}
323