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 "api.h"
24#include "core.h"
25#include "tilesheet.h"
26
27#include <assert.h>
28#include <string.h>
29#include <stdlib.h>
30#include <stdio.h>
31#include <ctype.h>
32#include <stddef.h>
33#include <time.h>
34
35#include "tic_assert.h"
36
37#ifdef _3DS
38#include <3ds.h>
39#endif
40
41static_assert(TIC_BANK_BITS == 3, "tic_bank_bits");
42static_assert(sizeof(tic_map) < 1024 * 32, "tic_map");
43static_assert(sizeof(tic_rgb) == 3, "tic_rgb");
44static_assert(sizeof(tic_palette) == 48, "tic_palette");
45static_assert(sizeof(((tic_vram *)0)->vars) == 4, "tic_vram vars");
46static_assert(sizeof(tic_vram) == TIC_VRAM_SIZE, "tic_vram");
47static_assert(sizeof(tic_ram) == TIC_RAM_SIZE, "tic_ram");
48
49u8 tic_api_peek(tic_mem* memory, s32 address, s32 bits)
50{
51 if (address < 0)
52 return 0;
53
54 const u8* ram = (u8*)memory->ram;
55 enum{RamBits = sizeof(tic_ram) * BITS_IN_BYTE};
56
57 switch(bits)
58 {
59 case 1: if(address < RamBits / 1) return tic_tool_peek1(ram, address);
60 case 2: if(address < RamBits / 2) return tic_tool_peek2(ram, address);
61 case 4: if(address < RamBits / 4) return tic_tool_peek4(ram, address);
62 case 8: if(address < RamBits / 8) return ram[address];
63 }
64
65 return 0;
66}
67
68void tic_api_poke(tic_mem* memory, s32 address, u8 value, s32 bits)
69{
70 if (address < 0)
71 return;
72
73 tic_core* core = (tic_core*)memory;
74 u8* ram = (u8*)memory->ram;
75 enum{RamBits = sizeof(tic_ram) * BITS_IN_BYTE};
76
77 switch(bits)
78 {
79 case 1: if(address < RamBits / 1) tic_tool_poke1(ram, address, value); break;
80 case 2: if(address < RamBits / 2) tic_tool_poke2(ram, address, value); break;
81 case 4: if(address < RamBits / 4) tic_tool_poke4(ram, address, value); break;
82 case 8: if(address < RamBits / 8) ram[address] = value; break;
83 }
84}
85
86u8 tic_api_peek4(tic_mem* memory, s32 address)
87{
88 return tic_api_peek(memory, address, 4);
89}
90
91u8 tic_api_peek1(tic_mem* memory, s32 address)
92{
93 return tic_api_peek(memory, address, 1);
94}
95
96void tic_api_poke1(tic_mem* memory, s32 address, u8 value)
97{
98 tic_api_poke(memory, address, value, 1);
99}
100
101u8 tic_api_peek2(tic_mem* memory, s32 address)
102{
103 return tic_api_peek(memory, address, 2);
104}
105
106void tic_api_poke2(tic_mem* memory, s32 address, u8 value)
107{
108 tic_api_poke(memory, address, value, 2);
109}
110
111void tic_api_poke4(tic_mem* memory, s32 address, u8 value)
112{
113 tic_api_poke(memory, address, value, 4);
114}
115
116void tic_api_memcpy(tic_mem* memory, s32 dst, s32 src, s32 size)
117{
118 tic_core* core = (tic_core*)memory;
119 s32 bound = sizeof(tic_ram) - size;
120
121 if (size >= 0
122 && size <= sizeof(tic_ram)
123 && dst >= 0
124 && src >= 0
125 && dst <= bound
126 && src <= bound)
127 {
128 u8* base = (u8*)memory->ram;
129 memcpy(base + dst, base + src, size);
130 }
131}
132
133void tic_api_memset(tic_mem* memory, s32 dst, u8 val, s32 size)
134{
135 tic_core* core = (tic_core*)memory;
136 s32 bound = sizeof(tic_ram) - size;
137
138 if (size >= 0
139 && size <= sizeof(tic_ram)
140 && dst >= 0
141 && dst <= bound)
142 {
143 u8* base = (u8*)memory->ram;
144 memset(base + dst, val, size);
145 }
146}
147
148void tic_api_trace(tic_mem* memory, const char* text, u8 color)
149{
150 tic_core* core = (tic_core*)memory;
151 core->data->trace(core->data->data, text ? text : "nil", color);
152}
153
154u32 tic_api_pmem(tic_mem* tic, s32 index, u32 value, bool set)
155{
156 u32 old = tic->ram->persistent.data[index];
157
158 if (set)
159 tic->ram->persistent.data[index] = value;
160
161 return old;
162}
163
164void tic_api_exit(tic_mem* tic)
165{
166 tic_core* core = (tic_core*)tic;
167 core->data->exit(core->data->data);
168}
169
170static inline void sync(void* dst, void* src, s32 size, bool rev)
171{
172 if(rev)
173 SWAP(dst, src, void*);
174
175 memcpy(dst, src, size);
176}
177
178void tic_api_sync(tic_mem* tic, u32 mask, s32 bank, bool toCart)
179{
180 tic_core* core = (tic_core*)tic;
181
182 static const struct { s32 bank; s32 ram; s32 size; u8 mask; } Sections[] =
183 {
184#define TIC_SYNC_DEF(CART, RAM, ...) { offsetof(tic_bank, CART), offsetof(tic_ram, RAM), sizeof(tic_##CART), tic_sync_##CART },
185 TIC_SYNC_LIST(TIC_SYNC_DEF)
186#undef TIC_SYNC_DEF
187 };
188
189 enum { Count = COUNT_OF(Sections), Mask = (1 << Count) - 1 };
190
191 if (mask == 0) mask = Mask;
192
193 mask &= ~core->state.synced & Mask;
194
195 assert(bank >= 0 && bank < TIC_BANKS);
196
197 for (s32 i = 0; i < Count; i++)
198 if(mask & Sections[i].mask)
199 sync((u8*)tic->ram + Sections[i].ram, (u8*)&tic->cart.banks[bank] + Sections[i].bank, Sections[i].size, toCart);
200
201 core->state.synced |= mask;
202}
203
204double tic_api_time(tic_mem* memory)
205{
206 tic_core* core = (tic_core*)memory;
207 return (clock() - core->data->start) * 1000.0 / CLOCKS_PER_SEC;
208}
209
210s32 tic_api_tstamp(tic_mem* memory)
211{
212 tic_core* core = (tic_core*)memory;
213 return (s32)time(NULL);
214}
215
216static bool compareMetatag(const char* code, const char* tag, const char* value, const char* comment)
217{
218 bool result = false;
219
220 char* str = tic_tool_metatag(code, tag, comment);
221
222 if (str)
223 {
224 result = strcmp(str, value) == 0;
225 free(str);
226 }
227
228 return result;
229}
230
231const tic_script_config* tic_core_script_config(tic_mem* memory)
232{
233 FOR_EACH_LANG(it)
234 {
235 if(it->id == memory->cart.lang || compareMetatag(memory->cart.code.data, "script", it->name, it->singleComment))
236 return it;
237 }
238 FOR_EACH_LANG_END
239
240 return Languages[0];
241}
242
243static void updateSaveid(tic_mem* memory)
244{
245 memset(memory->saveid, 0, sizeof memory->saveid);
246 char* saveid = tic_tool_metatag(memory->cart.code.data, "saveid", tic_core_script_config(memory)->singleComment);
247 if (saveid)
248 {
249 strncpy(memory->saveid, saveid, TIC_SAVEID_SIZE - 1);
250 free(saveid);
251 }
252}
253
254static void soundClear(tic_mem* memory)
255{
256 tic_core* core = (tic_core*)memory;
257
258 for (s32 i = 0; i < TIC_SOUND_CHANNELS; i++)
259 {
260 static const tic_channel_data EmptyChannel =
261 {
262 .tick = -1,
263 .pos = NULL,
264 .index = -1,
265 .note = 0,
266 .volume = {0, 0},
267 .speed = 0,
268 .duration = -1,
269 };
270
271 memcpy(&core->state.music.channels[i], &EmptyChannel, sizeof EmptyChannel);
272 memcpy(&core->state.sfx.channels[i], &EmptyChannel, sizeof EmptyChannel);
273
274 memset(core->state.sfx.channels[i].pos = &memory->ram->sfxpos[i], -1, sizeof(tic_sfx_pos));
275 memset(core->state.music.channels[i].pos = &core->state.music.sfxpos[i], -1, sizeof(tic_sfx_pos));
276 }
277
278 memset(&memory->ram->registers, 0, sizeof memory->ram->registers);
279 memset(memory->product.samples.buffer, 0, memory->product.samples.count * TIC80_SAMPLESIZE);
280
281 tic_api_music(memory, -1, 0, 0, false, false, -1, -1);
282}
283
284static void resetVbank(tic_mem* memory)
285{
286 ZEROMEM(memory->ram->vram.vars);
287
288 static const u8 DefaultMapping[] = { 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe };
289 memcpy(memory->ram->vram.mapping, DefaultMapping, sizeof DefaultMapping);
290 memory->ram->vram.palette = memory->cart.bank0.palette.vbank0;
291 memory->ram->vram.blit.segment = TIC_DEFAULT_BLIT_MODE;
292}
293
294static void font2ram(tic_mem* memory)
295{
296 memory->ram->font = (tic_font) {
297 .regular =
298 {
299 .data =
300 {
301 #include "font.inl"
302 },
303 {
304 {
305 .width = TIC_FONT_WIDTH,
306 .height = TIC_FONT_HEIGHT,
307 }
308 }
309 },
310
311 .alt =
312 {
313 .data =
314 {
315 #include "altfont.inl"
316 },
317 {
318 {
319 .width = TIC_ALTFONT_WIDTH,
320 .height = TIC_FONT_HEIGHT,
321 }
322 }
323 },
324 };
325}
326
327void tic_api_reset(tic_mem* memory)
328{
329 tic_core* core = (tic_core*)memory;
330
331 // keyboard state is critical and must be preserved across API resets.
332 // Often `tic_api_reset` is called to effect transitions between modes
333 // yet we still need to know when the key WAS pressed after the
334 // transition - to prevent it from counting as a second keypress.
335 //
336 // So why presev `now` not `previous`? this is most often called in
337 // the middle of a tick... so we preserve now, which during `tick_end`
338 // is copied to previous. This duplicates the prior behavior of
339 // `ram.input.keyboard` (which existing outside `state`).
340 u32 kb_now = core->state.keyboard.now.data;
341 ZEROMEM(core->state);
342 core->state.keyboard.now.data = kb_now;
343 tic_api_clip(memory, 0, 0, TIC80_WIDTH, TIC80_HEIGHT);
344
345 resetVbank(memory);
346
347 VBANK(memory, 1)
348 {
349 resetVbank(memory);
350
351 // init VBANK1 palette with VBANK0 palette if it's empty
352 // for backward compatibility
353 if(!EMPTY(memory->cart.bank0.palette.vbank1.data))
354 memcpy(&memory->ram->vram.palette, &memory->cart.bank0.palette.vbank1, sizeof(tic_palette));
355 }
356
357 memory->ram->input.mouse.relative = 0;
358
359 soundClear(memory);
360 updateSaveid(memory);
361 font2ram(memory);
362}
363
364static void cart2ram(tic_mem* memory)
365{
366 font2ram(memory);
367
368 enum
369 {
370#define TIC_SYNC_DEF(NAME, _, INDEX) sync_##NAME = INDEX,
371 TIC_SYNC_LIST(TIC_SYNC_DEF)
372#undef TIC_SYNC_DEF
373 count,
374 all = (1 << count) - 1,
375 noscreen = BITCLEAR(all, sync_screen)
376 };
377
378 // don't sync empty screen
379 tic_api_sync(memory, EMPTY(memory->cart.bank0.screen.data) ? noscreen : all, 0, false);
380}
381
382static void tic_close_current_vm(tic_core* core)
383{
384 // close previous VM if any
385 if(core->currentVM)
386 {
387 // printf("Closing VM of %s, %d\n", core->currentScript->name, core->currentVM);
388 core->currentScript->close( (tic_mem*)core );
389 core->currentVM = NULL;
390 }
391 if (core->memory.ram == NULL) {
392 core->memory.ram = core->memory.base_ram;
393 }
394}
395
396static bool tic_init_vm(tic_core* core, const char* code, const tic_script_config* config)
397{
398 tic_close_current_vm(core);
399 // set current script config and init
400 core->currentScript = config;
401 bool done = config->init( (tic_mem*) core , code);
402 if(!done)
403 {
404 // if it couldn't init, make sure the VM is not left dirty by the implementation
405 core->currentVM = NULL;
406 }
407 else
408 {
409 //printf("Initialized VM of %s, %d\n", core->currentScript->name, core->currentVM);
410 }
411 return done;
412}
413
414s32 tic_api_vbank(tic_mem* tic, s32 bank)
415{
416 tic_core* core = (tic_core*)tic;
417
418 s32 prev = core->state.vbank.id;
419
420 switch(bank)
421 {
422 case 0:
423 case 1:
424 if(core->state.vbank.id != bank)
425 {
426 SWAP(tic->ram->vram, core->state.vbank.mem, tic_vram);
427 core->state.vbank.id = bank;
428 }
429 }
430
431 return prev;
432}
433
434void tic_core_tick(tic_mem* tic, tic_tick_data* data)
435{
436 tic_core* core = (tic_core*)tic;
437
438 core->data = data;
439
440 if (!core->state.initialized)
441 {
442 const char* code = tic->cart.code.data;
443
444 bool done = false;
445 const tic_script_config* config = tic_core_script_config(tic);
446
447 if (strlen(code))
448 {
449 cart2ram(tic);
450
451 core->state.synced = 0;
452 tic->input.data = 0;
453
454 if (compareMetatag(code, "input", "mouse", config->singleComment))
455 tic->input.mouse = 1;
456 else if (compareMetatag(code, "input", "gamepad", config->singleComment))
457 tic->input.gamepad = 1;
458 else if (compareMetatag(code, "input", "keyboard", config->singleComment))
459 tic->input.keyboard = 1;
460 else tic->input.data = -1; // default is all enabled
461
462 data->start = clock();
463
464 // TODO: does where to fetch code from need to be a config option so this isn't hard
465 // coded for just a single langage? perhaps change it later when we have a second script
466 // engine that uses BINARY?
467 if (strcmp(config->name,"wasm")==0) {
468 code = tic->cart.binary.data;
469 }
470
471 done = tic_init_vm(core, code, config);
472 }
473 else
474 {
475 core->data->error(core->data->data, "the code is empty");
476 }
477
478 if (done)
479 {
480 config->boot(tic);
481 core->state.tick = config->tick;
482 core->state.callback = config->callback;
483 core->state.initialized = true;
484 }
485 else return;
486 }
487
488 core->state.tick(tic);
489}
490
491void tic_core_pause(tic_mem* memory)
492{
493 tic_core* core = (tic_core*)memory;
494
495 memcpy(&core->pause.state, &core->state, sizeof(tic_core_state_data));
496 memcpy(&core->pause.ram, memory->ram, sizeof(tic_ram));
497 core->pause.input = memory->input.data;
498
499 if (core->data)
500 {
501 core->pause.time.start = core->data->start;
502 core->pause.time.paused = clock();
503 }
504}
505
506void tic_core_resume(tic_mem* memory)
507{
508 tic_core* core = (tic_core*)memory;
509
510 if (core->data)
511 {
512 memcpy(&core->state, &core->pause.state, sizeof(tic_core_state_data));
513 memcpy(memory->ram, &core->pause.ram, sizeof(tic_ram));
514 core->data->start = core->pause.time.start + clock() - core->pause.time.paused;
515 memory->input.data = core->pause.input;
516 }
517}
518
519void tic_core_close(tic_mem* memory)
520{
521 tic_core* core = (tic_core*)memory;
522
523 core->state.initialized = false;
524
525 tic_close_current_vm(core);
526
527 blip_delete(core->blip.left);
528 blip_delete(core->blip.right);
529
530 free(memory->product.screen);
531 free(memory->product.samples.buffer);
532 free(core);
533}
534
535void tic_core_tick_start(tic_mem* memory)
536{
537 tic_core* core = (tic_core*)memory;
538 tic_core_sound_tick_start(memory);
539 tic_core_tick_io(memory);
540
541 // SECURITY: preserve the system keyboard/game controller input state
542 // (and restore it post-tick, see below) to prevent user cartridges
543 // from being able to corrupt and take control of the inputs in
544 // nefarious ways.
545 //
546 // Related: https://github.com/nesbox/TIC-80/issues/1785
547 core->state.keyboard.now.data = core->memory.ram->input.keyboard.data;
548 core->state.gamepads.now.data = core->memory.ram->input.gamepads.data;
549
550 core->state.synced = 0;
551}
552
553void tic_core_tick_end(tic_mem* memory)
554{
555 tic_core* core = (tic_core*)memory;
556 tic80_input* input = &core->memory.ram->input;
557
558 core->state.gamepads.previous.data = input->gamepads.data;
559 // SECURITY: we do not use `memory.ram.input` here because it is
560 // untrustworthy since the cartridge could have modified it to
561 // inject artificial keyboard/gamepad events.
562 core->state.keyboard.previous.data = core->state.keyboard.now.data;
563 core->state.gamepads.previous.data = core->state.gamepads.now.data;
564
565 tic_core_sound_tick_end(memory);
566}
567
568// copied from SDL2
569static inline void memset4(void* dst, u32 val, u32 dwords)
570{
571#if defined(__GNUC__) && defined(i386)
572 s32 u0, u1, u2;
573 __asm__ __volatile__(
574 "cld \n\t"
575 "rep ; stosl \n\t"
576 : "=&D" (u0), "=&a" (u1), "=&c" (u2)
577 : "0" (dst), "1" (val), "2" (dwords)
578 : "memory"
579 );
580#else
581 u32 _n = (dwords + 3) / 4;
582 u32* _p = (u32*)dst;
583 u32 _val = (val);
584 if (dwords == 0)
585 return;
586 switch (dwords % 4)
587 {
588 case 0: do {
589 *_p++ = _val;
590 case 3: *_p++ = _val;
591 case 2: *_p++ = _val;
592 case 1: *_p++ = _val;
593 } while (--_n);
594 }
595#endif
596}
597
598static inline tic_vram* vbank0(tic_core* core)
599{
600 return core->state.vbank.id ? &core->state.vbank.mem : &core->memory.ram->vram;
601}
602
603static inline tic_vram* vbank1(tic_core* core)
604{
605 return core->state.vbank.id ? &core->memory.ram->vram : &core->state.vbank.mem;
606}
607
608static inline void updpal(tic_mem* tic, tic_blitpal* pal0, tic_blitpal* pal1)
609{
610 tic_core* core = (tic_core*)tic;
611 *pal0 = tic_tool_palette_blit(&vbank0(core)->palette, core->screen_format);
612 *pal1 = tic_tool_palette_blit(&vbank1(core)->palette, core->screen_format);
613}
614
615static inline void updbdr(tic_mem* tic, s32 row, u32* ptr, tic_blit_callback clb, tic_blitpal* pal0, tic_blitpal* pal1)
616{
617 tic_core* core = (tic_core*)tic;
618
619 if(clb.border) clb.border(tic, row, clb.data);
620
621 if(clb.scanline)
622 {
623 if(row == 0) clb.scanline(tic, 0, clb.data);
624 else if(row > TIC80_MARGIN_TOP && row < (TIC80_HEIGHT + TIC80_MARGIN_TOP))
625 clb.scanline(tic, row - TIC80_MARGIN_TOP, clb.data);
626 }
627
628 if(clb.border || clb.scanline)
629 updpal(tic, pal0, pal1);
630
631 memset4(ptr, pal0->data[vbank0(core)->vars.border], TIC80_FULLWIDTH);
632}
633
634static inline u32 blitpix(tic_mem* tic, s32 offset0, s32 offset1, const tic_blitpal* pal0, const tic_blitpal* pal1)
635{
636 tic_core* core = (tic_core*)tic;
637 u32 pix = tic_tool_peek4(vbank1(core)->screen.data, offset1);
638
639 return pix != vbank1(core)->vars.clear
640 ? pal1->data[pix]
641 : pal0->data[tic_tool_peek4(vbank0(core)->screen.data, offset0)];
642}
643
644void tic_core_blit_ex(tic_mem* tic, tic_blit_callback clb)
645{
646 tic_core* core = (tic_core*)tic;
647
648 tic_blitpal pal0, pal1;
649 updpal(tic, &pal0, &pal1);
650
651 s32 row = 0;
652 u32* rowPtr = tic->product.screen;
653
654#define UPDBDR() updbdr(tic, row, rowPtr, clb, &pal0, &pal1)
655
656 for(; row != TIC80_MARGIN_TOP; ++row, rowPtr += TIC80_FULLWIDTH)
657 UPDBDR();
658
659 for(; row != TIC80_FULLHEIGHT - TIC80_MARGIN_BOTTOM; ++row)
660 {
661 UPDBDR();
662 rowPtr += TIC80_MARGIN_LEFT;
663
664 if(*(u16*)&vbank0(core)->vars.offset == 0 && *(u16*)&vbank1(core)->vars.offset == 0)
665 {
666 // render line without XY offsets
667 for(s32 x = (row - TIC80_MARGIN_TOP) * TIC80_WIDTH, end = x + TIC80_WIDTH; x != end; ++x)
668 *rowPtr++ = blitpix(tic, x, x, &pal0, &pal1);
669 }
670 else
671 {
672 // render line with XY offsets
673 enum{OffsetY = TIC80_HEIGHT - TIC80_MARGIN_TOP};
674 s32 start0 = (row - vbank0(core)->vars.offset.y + OffsetY) % TIC80_HEIGHT * TIC80_WIDTH;
675 s32 start1 = (row - vbank1(core)->vars.offset.y + OffsetY) % TIC80_HEIGHT * TIC80_WIDTH;
676 s32 offsetX0 = vbank0(core)->vars.offset.x;
677 s32 offsetX1 = vbank1(core)->vars.offset.x;
678
679 for(s32 x = TIC80_WIDTH; x != 2 * TIC80_WIDTH; ++x)
680 *rowPtr++ = blitpix(tic, (x - offsetX0) % TIC80_WIDTH + start0,
681 (x - offsetX1) % TIC80_WIDTH + start1, &pal0, &pal1);
682 }
683
684 rowPtr += TIC80_MARGIN_RIGHT;
685 }
686
687 for(; row != TIC80_FULLHEIGHT; ++row, rowPtr += TIC80_FULLWIDTH)
688 UPDBDR();
689
690#undef UPDBDR
691}
692
693static inline void scanline(tic_mem* memory, s32 row, void* data)
694{
695 tic_core* core = (tic_core*)memory;
696
697 if (core->state.initialized)
698 core->state.callback.scanline(memory, row, data);
699}
700
701static inline void border(tic_mem* memory, s32 row, void* data)
702{
703 tic_core* core = (tic_core*)memory;
704
705 if (core->state.initialized)
706 core->state.callback.border(memory, row, data);
707}
708
709void tic_core_blit(tic_mem* tic)
710{
711 tic_core_blit_ex(tic, (tic_blit_callback){scanline, border, NULL});
712}
713
714tic_mem* tic_core_create(s32 samplerate, tic80_pixel_color_format format)
715{
716 tic_core* core = (tic_core*)malloc(sizeof(tic_core));
717 memset(core, 0, sizeof(tic_core));
718
719 tic80* product = &core->memory.product;
720
721 core->screen_format = format;
722 core->memory.ram = (tic_ram*)malloc(TIC_RAM_SIZE);
723 core->memory.base_ram = core->memory.ram;
724 core->samplerate = samplerate;
725
726 memset(core->memory.ram, 0, sizeof(tic_ram));
727#ifdef _3DS
728 // To feed texture data directly to the 3DS GPU, linearly allocated memory is required, which is
729 // not guaranteed by malloc.
730 // Additionally, allocate TIC80_FULLHEIGHT + 1 lines to minimize glitches in linear scaling mode.
731 product->screen = linearAlloc(TIC80_FULLWIDTH * (TIC80_FULLHEIGHT + 1) * sizeof(u32));
732#else
733 product->screen = malloc(TIC80_FULLWIDTH * TIC80_FULLHEIGHT * sizeof product->screen[0]);
734#endif
735 product->samples.count = samplerate * TIC80_SAMPLE_CHANNELS / TIC80_FRAMERATE;
736 product->samples.buffer = malloc(product->samples.count * TIC80_SAMPLESIZE);
737
738 core->blip.left = blip_new(samplerate / 10);
739 core->blip.right = blip_new(samplerate / 10);
740
741 blip_set_rates(core->blip.left, CLOCKRATE, samplerate);
742 blip_set_rates(core->blip.right, CLOCKRATE, samplerate);
743
744 tic_api_reset(&core->memory);
745
746 return &core->memory;
747}
748