···11+// MIT License
22+33+// Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com
44+55+// Permission is hereby granted, free of charge, to any person obtaining a copy
66+// of this software and associated documentation files (the "Software"), to deal
77+// in the Software without restriction, including without limitation the rights
88+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+// copies of the Software, and to permit persons to whom the Software is
1010+// furnished to do so, subject to the following conditions:
1111+1212+// The above copyright notice and this permission notice shall be included in all
1313+// copies or substantial portions of the Software.
1414+1515+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+// SOFTWARE.
2222+2323+#include "cart.h"
2424+#include <string.h>
2525+#include <stdlib.h>
2626+2727+typedef enum
2828+{
2929+ CHUNK_DUMMY, // 0
3030+ CHUNK_TILES, // 1
3131+ CHUNK_SPRITES, // 2
3232+ CHUNK_COVER, // 3
3333+ CHUNK_MAP, // 4
3434+ CHUNK_CODE, // 5
3535+ CHUNK_FLAGS, // 6
3636+ CHUNK_TEMP2, // 7
3737+ CHUNK_TEMP3, // 8
3838+ CHUNK_SAMPLES, // 9
3939+ CHUNK_WAVEFORM, // 10
4040+ CHUNK_TEMP4, // 11
4141+ CHUNK_PALETTE, // 12
4242+ CHUNK_PATTERNS_DEP, // 13 - deprecated chunk
4343+ CHUNK_MUSIC, // 14
4444+ CHUNK_PATTERNS, // 15
4545+} ChunkType;
4646+4747+typedef struct
4848+{
4949+ ChunkType type:5;
5050+ u32 bank:TIC_BANK_BITS;
5151+ u32 size:16; // max chunk size is 64K
5252+ u32 temp:8;
5353+} Chunk;
5454+5555+STATIC_ASSERT(tic_chunk_size, sizeof(Chunk) == 4);
5656+5757+void tic_cart_load(tic_cartridge* cart, const u8* buffer, s32 size)
5858+{
5959+ const u8* end = buffer + size;
6060+ memset(cart, 0, sizeof(tic_cartridge));
6161+6262+ #define LOAD_CHUNK(to) memcpy(&to, buffer, MIN(sizeof(to), chunk.size))
6363+6464+ bool paletteExists = false;
6565+6666+ tic_code* code = calloc(TIC_BANKS, TIC_CODE_SIZE);
6767+6868+ if(!code) return;
6969+7070+ while(buffer < end)
7171+ {
7272+ Chunk chunk;
7373+ memcpy(&chunk, buffer, sizeof(Chunk));
7474+ buffer += sizeof(Chunk);
7575+7676+ switch(chunk.type)
7777+ {
7878+ case CHUNK_TILES: LOAD_CHUNK(cart->banks[chunk.bank].tiles); break;
7979+ case CHUNK_SPRITES: LOAD_CHUNK(cart->banks[chunk.bank].sprites); break;
8080+ case CHUNK_MAP: LOAD_CHUNK(cart->banks[chunk.bank].map); break;
8181+ case CHUNK_SAMPLES: LOAD_CHUNK(cart->banks[chunk.bank].sfx.samples); break;
8282+ case CHUNK_WAVEFORM: LOAD_CHUNK(cart->banks[chunk.bank].sfx.waveforms); break;
8383+ case CHUNK_MUSIC: LOAD_CHUNK(cart->banks[chunk.bank].music.tracks); break;
8484+ case CHUNK_PATTERNS: LOAD_CHUNK(cart->banks[chunk.bank].music.patterns); break;
8585+ case CHUNK_PALETTE: LOAD_CHUNK(cart->banks[chunk.bank].palette); break;
8686+ case CHUNK_FLAGS: LOAD_CHUNK(cart->banks[chunk.bank].flags); break;
8787+ case CHUNK_CODE: LOAD_CHUNK(code[chunk.bank].data); break;
8888+ case CHUNK_COVER:
8989+ LOAD_CHUNK(cart->cover.data);
9090+ cart->cover.size = chunk.size;
9191+ break;
9292+ case CHUNK_PATTERNS_DEP:
9393+ {
9494+ // workaround to load deprecated music patterns section
9595+ // and automatically convert volume value to a command
9696+ tic_patterns* ptrns = &cart->banks[chunk.bank].music.patterns;
9797+ LOAD_CHUNK(*ptrns);
9898+ for(s32 i = 0; i < MUSIC_PATTERNS; i++)
9999+ for(s32 r = 0; r < MUSIC_PATTERN_ROWS; r++)
100100+ {
101101+ tic_track_row* row = &ptrns->data[i].rows[r];
102102+ if(row->note >= NoteStart && row->command == tic_music_cmd_empty)
103103+ {
104104+ row->command = tic_music_cmd_volume;
105105+ row->param2 = row->param1 = MAX_VOLUME - row->param1;
106106+ }
107107+ }
108108+ }
109109+ break;
110110+ default: break;
111111+ }
112112+113113+ buffer += chunk.size;
114114+115115+ if(chunk.bank == 0 && chunk.type == CHUNK_PALETTE)
116116+ paletteExists = true;
117117+ }
118118+119119+ #undef LOAD_CHUNK
120120+121121+ // workaround to load code from banks
122122+ if(!strlen(cart->code.data))
123123+ for(s32 i = TIC_BANKS-1; i >= 0; i--)
124124+ {
125125+ // add new line to split code banks
126126+ {
127127+ s32 len = strlen(code[i].data);
128128+ if(len && len < TIC_CODE_SIZE && code[i].data[len - 1] != '\n')
129129+ strcat(code[i].data, "\n");
130130+ }
131131+132132+ if(strlen(code[i].data) + strlen(cart->code.data) < TIC_CODE_SIZE)
133133+ strcat(cart->code.data, code[i].data);
134134+ else break;
135135+ }
136136+137137+ free(code);
138138+139139+ // workaround to support ancient carts without palette
140140+ // load DB16 palette if it not exists
141141+ if(!paletteExists)
142142+ {
143143+ 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};
144144+ memcpy(cart->bank0.palette.data, DB16, sizeof(tic_palette));
145145+ }
146146+}
147147+148148+149149+static s32 calcBufferSize(const void* buffer, s32 size)
150150+{
151151+ const u8* ptr = (u8*)buffer + size - 1;
152152+ const u8* end = (u8*)buffer;
153153+154154+ while(ptr >= end)
155155+ {
156156+ if(*ptr) break;
157157+158158+ ptr--;
159159+ size--;
160160+ }
161161+162162+ return size;
163163+}
164164+165165+static u8* saveFixedChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
166166+{
167167+ if(size)
168168+ {
169169+ Chunk chunk = {.type = type, .bank = bank, .size = size, .temp = 0};
170170+ memcpy(buffer, &chunk, sizeof(Chunk));
171171+ buffer += sizeof(Chunk);
172172+ memcpy(buffer, from, size);
173173+ buffer += size;
174174+ }
175175+176176+ return buffer;
177177+}
178178+179179+static u8* saveChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
180180+{
181181+ s32 chunkSize = calcBufferSize(from, size);
182182+183183+ return saveFixedChunk(buffer, type, from, chunkSize, bank);
184184+}
185185+186186+s32 tic_cart_save(const tic_cartridge* cart, u8* buffer)
187187+{
188188+ u8* start = buffer;
189189+190190+ #define SAVE_CHUNK(ID, FROM, BANK) saveChunk(buffer, ID, &FROM, sizeof(FROM), BANK)
191191+192192+ for(s32 i = 0; i < TIC_BANKS; i++)
193193+ {
194194+ buffer = SAVE_CHUNK(CHUNK_TILES, cart->banks[i].tiles, i);
195195+ buffer = SAVE_CHUNK(CHUNK_SPRITES, cart->banks[i].sprites, i);
196196+ buffer = SAVE_CHUNK(CHUNK_MAP, cart->banks[i].map, i);
197197+ buffer = SAVE_CHUNK(CHUNK_SAMPLES, cart->banks[i].sfx.samples, i);
198198+ buffer = SAVE_CHUNK(CHUNK_WAVEFORM, cart->banks[i].sfx.waveforms, i);
199199+ buffer = SAVE_CHUNK(CHUNK_PATTERNS, cart->banks[i].music.patterns, i);
200200+ buffer = SAVE_CHUNK(CHUNK_MUSIC, cart->banks[i].music.tracks, i);
201201+ buffer = SAVE_CHUNK(CHUNK_PALETTE, cart->banks[i].palette, i);
202202+ buffer = SAVE_CHUNK(CHUNK_FLAGS, cart->banks[i].flags, i);
203203+ }
204204+205205+ buffer = SAVE_CHUNK(CHUNK_CODE, cart->code, 0);
206206+ buffer = saveFixedChunk(buffer, CHUNK_COVER, cart->cover.data, cart->cover.size, 0);
207207+208208+ #undef SAVE_CHUNK
209209+210210+ return (s32)(buffer - start);
211211+}
+28
src/cart.h
···11+// MIT License
22+33+// Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com
44+55+// Permission is hereby granted, free of charge, to any person obtaining a copy
66+// of this software and associated documentation files (the "Software"), to deal
77+// in the Software without restriction, including without limitation the rights
88+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+// copies of the Software, and to permit persons to whom the Software is
1010+// furnished to do so, subject to the following conditions:
1111+1212+// The above copyright notice and this permission notice shall be included in all
1313+// copies or substantial portions of the Software.
1414+1515+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+// SOFTWARE.
2222+2323+#pragma once
2424+2525+#include "tic.h"
2626+2727+void tic_cart_load(tic_cartridge* rom, const u8* buffer, s32 size);
2828+s32 tic_cart_save(const tic_cartridge* rom, u8* buffer);
···11+// MIT License
22+33+// Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com
44+55+// Permission is hereby granted, free of charge, to any person obtaining a copy
66+// of this software and associated documentation files (the "Software"), to deal
77+// in the Software without restriction, including without limitation the rights
88+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+// copies of the Software, and to permit persons to whom the Software is
1010+// furnished to do so, subject to the following conditions:
1111+1212+// The above copyright notice and this permission notice shall be included in all
1313+// copies or substantial portions of the Software.
1414+1515+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+// SOFTWARE.
2222+2323+#pragma once
2424+2525+#include "cart.h"
2626+2727+bool tic_project_load(const char* name, const char* data, s32 size, tic_cartridge* dst);
2828+s32 tic_project_save(const char* name, void* data, const tic_cartridge* cart);