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 "project.h"
24#include "tools.h"
25#include "api.h"
26#include <stdlib.h>
27#include <stdio.h>
28#include <string.h>
29#include <ctype.h>
30#include <stddef.h>
31
32static const struct BinarySection{const char* tag; s32 count; s32 offset; s32 size; bool flip;} BinarySections[] =
33{
34 {"TILES", TIC_BANK_SPRITES, offsetof(tic_bank, tiles), sizeof(tic_tile), true},
35 {"SPRITES", TIC_BANK_SPRITES, offsetof(tic_bank, sprites), sizeof(tic_tile), true},
36 {"MAP", TIC_MAP_HEIGHT, offsetof(tic_bank, map), TIC_MAP_WIDTH, true},
37 {"WAVES", WAVES_COUNT, offsetof(tic_bank, sfx.waveforms), sizeof(tic_waveform), true},
38 {"SFX", SFX_COUNT, offsetof(tic_bank, sfx.samples), sizeof(tic_sample), true},
39 {"PATTERNS", MUSIC_PATTERNS, offsetof(tic_bank, music.patterns), sizeof(tic_track_pattern), true},
40 {"TRACKS", MUSIC_TRACKS, offsetof(tic_bank, music.tracks), sizeof(tic_track), true},
41 {"FLAGS", TIC_SPRITE_BANKS, offsetof(tic_bank, flags), TIC_BANK_SPRITES, true},
42 {"SCREEN", TIC80_HEIGHT, offsetof(tic_bank, screen), sizeof(tic_screen) / TIC80_HEIGHT, true},
43 {"PALETTE", TIC_PALETTES, offsetof(tic_bank, palette), sizeof(tic_palette), false},
44};
45
46static const struct BinarySection LangSection = {"LANG", 1, 0, sizeof (tic_cartridge){0}.lang, false};
47
48static void makeTag(const char* tag, char* out, s32 bank)
49{
50 if(bank) sprintf(out, "%s%i", tag, bank);
51 else strcpy(out, tag);
52}
53
54static void buf2str(const void* data, s32 size, char* ptr, bool flip)
55{
56 enum {Len = 2};
57
58 for(s32 i = 0; i < size; i++, ptr+=Len)
59 {
60 sprintf(ptr, "%02x", ((u8*)data)[i]);
61 if(flip) SWAP(ptr[0], ptr[1], char);
62 }
63}
64
65static bool bufferEmpty(const u8* data, s32 size)
66{
67 for(s32 i = 0; i < size; i++)
68 if(*data++)
69 return false;
70
71 return true;
72}
73
74static char* saveTextSection(char* ptr, const char* data)
75{
76 if(data[0] == '\0')
77 return ptr;
78
79 sprintf(ptr, "%s\n", data);
80 ptr += strlen(ptr);
81
82 return ptr;
83}
84
85static char* saveBinaryBuffer(char* ptr, const char* comment, const void* data, s32 size, s32 row, bool flip)
86{
87 if(bufferEmpty(data, size))
88 return ptr;
89
90 sprintf(ptr, "%s %03i:", comment, row);
91 ptr += strlen(ptr);
92
93 buf2str(data, size, ptr, flip);
94 ptr += strlen(ptr);
95
96 sprintf(ptr, "\n");
97 ptr += strlen(ptr);
98
99 return ptr;
100}
101
102static char* saveBinarySection(char* ptr, const char* comment, const char* tag, s32 count, const void* data, s32 size, bool flip)
103{
104 if(bufferEmpty(data, size * count))
105 return ptr;
106
107 sprintf(ptr, "%s <%s>\n", comment, tag);
108 ptr += strlen(ptr);
109
110 for(s32 i = 0; i < count; i++, data = (u8*)data + size)
111 ptr = saveBinaryBuffer(ptr, comment, data, size, i, flip);
112
113 sprintf(ptr, "%s </%s>\n\n", comment, tag);
114 ptr += strlen(ptr);
115
116 return ptr;
117}
118
119static const char* projectComment(const char* name)
120{
121 FOR_EACH_LANG(ln)
122 {
123 if(tic_tool_has_ext(name, ln->fileExtension))
124 return ln->projectComment;
125 }
126 FOR_EACH_LANG_END
127 return Languages[0]->projectComment;
128}
129
130s32 tic_project_save(const char* name, void* data, const tic_cartridge* cart)
131{
132 const char* comment = projectComment(name);
133 char* stream = data;
134 char* ptr = saveTextSection(stream, cart->code.data);
135 char tag[16];
136
137 FOR(const struct BinarySection*, section, BinarySections)
138 for(s32 b = 0; b < TIC_BANKS; b++)
139 {
140 makeTag(section->tag, tag, b);
141
142 ptr = saveBinarySection(ptr, comment, tag, section->count,
143 (u8*)&cart->banks[b] + section->offset, section->size, section->flip);
144 }
145
146 if(cart->lang)
147 ptr = saveBinarySection(ptr, comment, LangSection.tag, LangSection.count, &cart->lang, LangSection.size, LangSection.flip);
148
149 return (s32)strlen(stream);
150}
151
152static bool loadTextSection(const char* project, const char* comment, char* dst, s32 size)
153{
154 bool done = false;
155
156 const char* start = project;
157 const char* end = project + strlen(project);
158
159 {
160 char tagstart[16];
161 sprintf(tagstart, "\n%s <", comment);
162
163 const char* ptr = strstr(project, tagstart);
164
165 if(ptr && ptr < end)
166 end = ptr;
167 }
168
169 if(end > start)
170 {
171 memcpy(dst, start, MIN(size, end - start));
172 done = true;
173 }
174
175 return done;
176}
177
178static inline const char* getLineEnd(const char* ptr)
179{
180 while(*ptr && isspace(*ptr) && *ptr++ != '\n');
181
182 return ptr;
183}
184
185static bool loadBinarySection(const char* project, const char* comment, const char* tag, s32 count, void* dst, s32 size, bool flip)
186{
187 char tagbuf[64];
188 sprintf(tagbuf, "%s <%s>", comment, tag);
189
190 const char* start = strstr(project, tagbuf);
191 bool done = false;
192
193 if(start)
194 {
195 start += strlen(tagbuf);
196 start = getLineEnd(start);
197
198 sprintf(tagbuf, "\n%s </%s>", comment, tag);
199 const char* end = strstr(start, tagbuf);
200
201 if(end > start)
202 {
203 const char* ptr = start;
204
205 if(size > 0)
206 {
207 while(ptr < end)
208 {
209 static char lineStr[] = "999";
210 memcpy(lineStr, ptr + strlen(comment) + 1, sizeof lineStr - 1);
211
212 s32 index = atoi(lineStr);
213
214 if(index < count)
215 {
216 ptr += strlen(comment) + sizeof(" 999:") - 1;
217 tic_tool_str2buf(ptr, size*2, (u8*)dst + size*index, flip);
218 ptr += size*2 + 1;
219
220 ptr = getLineEnd(ptr);
221 }
222 else break;
223 }
224 }
225 else
226 {
227 ptr += strlen(comment) + sizeof(" 999:") - 1;
228 tic_tool_str2buf(ptr, (s32)(end - ptr), (u8*)dst, flip);
229 }
230
231 done = true;
232 }
233 }
234
235 return done;
236}
237
238bool tic_project_load(const char* name, const char* data, s32 size, tic_cartridge* dst)
239{
240 char* project = (char*)malloc(size+1);
241
242 bool done = false;
243
244 if(project)
245 {
246 memcpy(project, data, size);
247 project[size] = '\0';
248
249 // remove all the '\r' chars
250 {
251 char *s, *d;
252 for(s = d = project; (*d = *s); d += (*s++ != '\r'));
253 }
254
255 tic_cartridge* cart = calloc(1, sizeof(tic_cartridge));
256
257 if(cart)
258 {
259 const char* comment = projectComment(name);
260 char tag[16];
261
262 if(loadTextSection(project, comment, cart->code.data, sizeof(tic_code)))
263 done = true;
264
265 if(done)
266 {
267 FOR(const struct BinarySection*, section, BinarySections)
268 for(s32 b = 0; b < TIC_BANKS; b++)
269 {
270 makeTag(section->tag, tag, b);
271 loadBinarySection(project, comment, tag, section->count, (u8*)&cart->banks[b] + section->offset, section->size, section->flip);
272 }
273
274 loadBinarySection(project, comment, LangSection.tag, LangSection.count, &cart->lang, LangSection.size, LangSection.flip);
275 }
276
277 if(done)
278 memcpy(dst, cart, sizeof(tic_cartridge));
279
280 free(cart);
281 }
282
283 free(project);
284 }
285
286 return done;
287}
288