From 04b6417ddfa2bf8434b391004dafebcb97194c00 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Sat, 17 Aug 2019 14:12:14 -0400 Subject: [PATCH] msx: worked on kbd, psg, joy; added msx-libcv --- presets/msx-libcv | 1 + presets/msx/helloworld.asm | 7 +- presets/msx/helloworld.s | 34 +++ src/audio.ts | 4 + src/baseplatform.ts | 2 +- src/emu.ts | 8 +- src/platform/msx.ts | 133 +++++++-- src/worker/lib/msx-libcv/crt0-msx.lst | 91 +++++++ src/worker/lib/msx-libcv/crt0-msx.rel | 58 ++++ src/worker/lib/msx-libcv/cv.h | 62 +++++ src/worker/lib/msx-libcv/cv_graphics.h | 255 ++++++++++++++++++ src/worker/lib/msx-libcv/cv_input.h | 60 +++++ src/worker/lib/msx-libcv/cv_sound.h | 43 +++ src/worker/lib/msx-libcv/cv_support.h | 19 ++ src/worker/lib/msx-libcv/cvu.h | 32 +++ src/worker/lib/msx-libcv/cvu_c.h | 37 +++ src/worker/lib/msx-libcv/cvu_compression.h | 93 +++++++ src/worker/lib/msx-libcv/cvu_f.h | 46 ++++ src/worker/lib/msx-libcv/cvu_graphics.h | 122 +++++++++ src/worker/lib/msx-libcv/cvu_input.h | 15 ++ src/worker/lib/msx-libcv/cvu_sound.h | 45 ++++ src/worker/lib/msx-libcv/libcv-msx.lib | Bin 0 -> 27804 bytes src/worker/lib/msx-libcv/libcvu-msx.lib | Bin 0 -> 38666 bytes src/worker/lib/msx/Makefile | 3 + src/worker/lib/msx/crt0-msx.lst | 75 ++++++ src/worker/lib/msx/crt0-msx.rel | 46 ++++ src/worker/lib/msx/crt0-msx.s | 75 ++++++ src/worker/lib/msx/crt0-msx.sym | 27 ++ src/worker/lib/sms-sg1000-libcv/cv.h | 5 +- src/worker/lib/sms-sg1000-libcv/cv_graphics.h | 5 + src/worker/lib/sms-sg1000-libcv/cv_input.h | 17 +- src/worker/lib/sms-sg1000-libcv/cv_sound.h | 6 + src/worker/lib/sms-sg1000-libcv/cv_support.h | 2 + .../lib/sms-sg1000-libcv/cvu_graphics.h | 3 +- src/worker/lib/sms-sg1000-libcv/libcv-sms.lib | Bin 34462 -> 33278 bytes .../lib/sms-sg1000-libcv/libcvu-sms.lib | Bin 38750 -> 39850 bytes src/worker/workermain.ts | 38 ++- 37 files changed, 1434 insertions(+), 35 deletions(-) create mode 120000 presets/msx-libcv create mode 100644 presets/msx/helloworld.s create mode 100644 src/worker/lib/msx-libcv/crt0-msx.lst create mode 100644 src/worker/lib/msx-libcv/crt0-msx.rel create mode 100755 src/worker/lib/msx-libcv/cv.h create mode 100755 src/worker/lib/msx-libcv/cv_graphics.h create mode 100755 src/worker/lib/msx-libcv/cv_input.h create mode 100644 src/worker/lib/msx-libcv/cv_sound.h create mode 100755 src/worker/lib/msx-libcv/cv_support.h create mode 100644 src/worker/lib/msx-libcv/cvu.h create mode 100644 src/worker/lib/msx-libcv/cvu_c.h create mode 100644 src/worker/lib/msx-libcv/cvu_compression.h create mode 100644 src/worker/lib/msx-libcv/cvu_f.h create mode 100644 src/worker/lib/msx-libcv/cvu_graphics.h create mode 100644 src/worker/lib/msx-libcv/cvu_input.h create mode 100644 src/worker/lib/msx-libcv/cvu_sound.h create mode 100644 src/worker/lib/msx-libcv/libcv-msx.lib create mode 100644 src/worker/lib/msx-libcv/libcvu-msx.lib create mode 100644 src/worker/lib/msx/Makefile create mode 100644 src/worker/lib/msx/crt0-msx.lst create mode 100644 src/worker/lib/msx/crt0-msx.rel create mode 100644 src/worker/lib/msx/crt0-msx.s create mode 100644 src/worker/lib/msx/crt0-msx.sym diff --git a/presets/msx-libcv b/presets/msx-libcv new file mode 120000 index 00000000..2fb6abf1 --- /dev/null +++ b/presets/msx-libcv @@ -0,0 +1 @@ +coleco \ No newline at end of file diff --git a/presets/msx/helloworld.asm b/presets/msx/helloworld.asm index bf641e8f..21d727ae 100644 --- a/presets/msx/helloworld.asm +++ b/presets/msx/helloworld.asm @@ -1,13 +1,10 @@ - + ; Hello World example ; ROM routine for character output CHPUT: equ $00A2 -; waste space @ 0x0000 - 0x3fff - org 0x0000 - db 0 - ds 0x3fff + org 0x4000 ; MSX cartridge header @ 0x4000 - 0x400f dw 0x4241 diff --git a/presets/msx/helloworld.s b/presets/msx/helloworld.s new file mode 100644 index 00000000..808c8f04 --- /dev/null +++ b/presets/msx/helloworld.s @@ -0,0 +1,34 @@ + +; Hello World example + +; ROM routine for character output +CHGET = #0x009F +CHPUT = #0x00A2 + +; Bank 1 +.area _CODE +; MSX cartridge header @ 0x4000 - 0x400f + .dw 0x4241 + .dw Init + .dw Init + .dw 0 + .dw 0 + .dw 0 + .dw 0 + .dw 0 + +; initialize and print message +Init: + ld hl,#msg + call puts + jp Init ; loop forever +puts: ; print 0-terminated string in HL + ld a,(hl) + or a + ret z + call CHPUT ; displays one character in A + inc hl + jr puts + +; ASCII message + CR LF +msg: .ascii "Hello, world!\n\r\0" diff --git a/src/audio.ts b/src/audio.ts index b61c72e3..655252a6 100644 --- a/src/audio.ts +++ b/src/audio.ts @@ -39,6 +39,10 @@ export class AY38910_Audio { setData(val : number) { this.psg.writeRegisterAY(this.curreg, val & 0xff); } + readData() { + return this.psg.readRegister(this.curreg); + } + currentRegister() { return this.curreg; } } export class SN76489_Audio { diff --git a/src/baseplatform.ts b/src/baseplatform.ts index 70b1b21b..e96e8029 100644 --- a/src/baseplatform.ts +++ b/src/baseplatform.ts @@ -1086,7 +1086,7 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform { audio; psg; pixels : Uint32Array; - inputs = new Uint8Array(16); + inputs : Uint8Array = new Uint8Array(32); mainElement : HTMLElement; poller : ControllerPoller; diff --git a/src/emu.ts b/src/emu.ts index ae228225..3b7a45da 100644 --- a/src/emu.ts +++ b/src/emu.ts @@ -335,7 +335,8 @@ interface KeyMapEntry { type KeyCodeMap = Map; -export const Keys : {[keycode:string]:KeyDef} = { +export const Keys = { + ANYKEY: {c: 0, n: "?"}, // gamepad and keyboard (player 0) UP: {c: 38, n: "Up", plyr:0, yaxis:-1}, DOWN: {c: 40, n: "Down", plyr:0, yaxis:1}, @@ -471,7 +472,12 @@ type KeyMapFunction = (o:KeyMapEntry, key:number, code:number, flags:number) => export function setKeyboardFromMap(video:RasterVideo, switches:number[]|Uint8Array, map:KeyCodeMap, func?:KeyMapFunction) { var handler = (key,code,flags) => { + if (!map) { + func(null, key, code, flags); + return; + } var o : KeyMapEntry = map[key]; + if (!o) o = map[0]; if (o && func) { func(o, key, code, flags); } diff --git a/src/platform/msx.ts b/src/platform/msx.ts index 3468edc9..08da37b5 100644 --- a/src/platform/msx.ts +++ b/src/platform/msx.ts @@ -1,9 +1,9 @@ "use strict"; import { Platform, BasicZ80ScanlinePlatform } from "../baseplatform"; -import { PLATFORMS, newAddressDecoder, padBytes, noise, makeKeycodeMap, Keys } from "../emu"; +import { PLATFORMS, newAddressDecoder, padBytes, noise, makeKeycodeMap, KeyFlags, Keys, EmuHalt } from "../emu"; import { hex, lzgmini, stringToByteArray } from "../util"; -import { MasterAudio, SN76489_Audio } from "../audio"; +import { MasterAudio, AY38910_Audio } from "../audio"; import { TMS9918A } from "../video/tms9918a"; // https://www.konamiman.com/msx/msx-e.html#msx2th @@ -12,28 +12,48 @@ import { TMS9918A } from "../video/tms9918a"; // https://openmsx.org/manual/setup.html // https://www.msx.org/wiki/Slots // https://www.msx.org/wiki/SDCC -// http://cbios.sourceforge.net/ var MSX_PRESETS = [ {id:'helloworld.asm', name:'Hello World (ASM)'}, ]; var MSX_KEYCODE_MAP = makeKeycodeMap([ - [Keys.UP, 0, 0x1], - [Keys.DOWN, 0, 0x4], - [Keys.LEFT, 0, 0x8], - [Keys.RIGHT, 0, 0x2], - [Keys.A, 0, 0x40], - [Keys.B, 1, 0x40], + [Keys.UP, 0, 0x1], + [Keys.DOWN, 0, 0x2], + [Keys.LEFT, 0, 0x4], + [Keys.RIGHT, 0, 0x8], + [Keys.A, 0, 0x10], + [Keys.B, 0, 0x20], - [Keys.P2_UP, 2, 0x1], - [Keys.P2_DOWN, 2, 0x4], - [Keys.P2_LEFT, 2, 0x8], - [Keys.P2_RIGHT, 2, 0x2], - [Keys.P2_A, 2, 0x40], - [Keys.P2_B, 3, 0x40], + [Keys.P2_UP, 1, 0x1], + [Keys.P2_DOWN, 1, 0x2], + [Keys.P2_LEFT, 1, 0x4], + [Keys.P2_RIGHT, 1, 0x8], + [Keys.P2_A, 1, 0x10], + [Keys.P2_B, 1, 0x20], + + [Keys.ANYKEY, 2, 0x0], ]); +const JOY_INPUT_0 = 0; +const JOY_INPUT_1 = 1; +const KEYBOARD_ROW_0 = 16; + +const MSX_KEYMATRIX_INTL_NOSHIFT = [ + Keys.VK_7, Keys.VK_6, Keys.VK_5, Keys.VK_4, Keys.VK_3, Keys.VK_2, Keys.VK_1, Keys.VK_0, + Keys.VK_SEMICOLON, Keys.VK_CLOSE_BRACKET, Keys.VK_OPEN_BRACKET, Keys.VK_BACK_SLASH, Keys.VK_EQUALS, Keys.VK_MINUS, Keys.VK_9, Keys.VK_8, + Keys.VK_B, Keys.VK_A, null/*DEAD*/, Keys.VK_SLASH, Keys.VK_PERIOD, Keys.VK_COMMA, Keys.VK_ACUTE, Keys.VK_QUOTE, + Keys.VK_J, Keys.VK_I, Keys.VK_H, Keys.VK_G, Keys.VK_F, Keys.VK_E, Keys.VK_D, Keys.VK_C, + Keys.VK_R, Keys.VK_Q, Keys.VK_P, Keys.VK_O, Keys.VK_N, Keys.VK_M, Keys.VK_L, Keys.VK_K, + Keys.VK_Z, Keys.VK_Y, Keys.VK_X, Keys.VK_W, Keys.VK_V, Keys.VK_U, Keys.VK_T, Keys.VK_S, + Keys.VK_F3, Keys.VK_F2, Keys.VK_F1, null, Keys.VK_CAPS_LOCK, null, Keys.VK_CONTROL, Keys.VK_SHIFT, + Keys.VK_ENTER, null, Keys.VK_BACK_SPACE, null, Keys.VK_TAB, Keys.VK_ESCAPE, Keys.VK_F5, Keys.VK_F4, + Keys.VK_RIGHT, Keys.VK_DOWN, Keys.VK_UP, Keys.VK_LEFT, Keys.VK_DELETE, Keys.VK_INSERT, Keys.VK_HOME, Keys.VK_SPACE, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + // TODO: null keycodes +]; + /// standard emulator interface MSXSlot { @@ -47,16 +67,38 @@ class MSXPlatform extends BasicZ80ScanlinePlatform implements Platform { canvasWidth = 304; numTotalScanlines = 262; numVisibleScanlines = 240; - defaultROMSize = 0x10000; + defaultROMSize = 0x8000; vdp : TMS9918A; bios : Uint8Array; slots : MSXSlot[]; slotmask : number = 0; + ppi_c : number = 0; getPresets() { return MSX_PRESETS; } getKeyboardMap() { return MSX_KEYCODE_MAP; } + + // http://map.grauw.nl/articles/keymatrix.php + getKeyboardFunction() { + return (o,key,code,flags) => { + //console.log(o,key,code,flags); + var keymap = MSX_KEYMATRIX_INTL_NOSHIFT; + for (var i=0; i> 3; + let bit = 7 - (i & 7); + //console.log(key, row, bit); + if (flags & KeyFlags.KeyDown) { + this.inputs[KEYBOARD_ROW_0+row] |= (1<> 1) & 7; + this.ppi_c = (this.ppi_c & ~(1< { return this.rom[a] | 0; }, + read: (a) => { return this.rom[a - 0x4000] | 0; }, + write: (a,v) => { } + }, + // slot 2: cartridge + { + read: (a) => { return this.rom[a - 0x4000] | 0; }, write: (a,v) => { } }, - // slot 2 : empty - null, // slot 3 : RAM { read: (a) => { return this.ram[a] | 0; }, @@ -130,11 +199,11 @@ class MSXPlatform extends BasicZ80ScanlinePlatform implements Platform { }, ]; this.audio = new MasterAudio(); - this.psg = new SN76489_Audio(this.audio); + this.psg = new AY38910_Audio(this.audio); var cru = { setVDPInterrupt: (b) => { if (b) { - this.cpu.nonMaskableInterrupt(); + this.cpu.requestInterrupt(0x38); } else { // TODO: reset interrupt? } @@ -158,11 +227,15 @@ class MSXPlatform extends BasicZ80ScanlinePlatform implements Platform { super.loadState(state); this.vdp.restoreState(state['vdp']); this.slotmask = state['slotmask']; + this.ppi_c = state['ppi_c']; + this.psg.selectRegister(state['psgRegister']); } saveState() { var state = super.saveState(); state['vdp'] = this.vdp.getState(); state['slotmask'] = this.slotmask; + state['ppi_c'] = this.ppi_c; + state['psgRegister'] = this.psg.currentRegister(); return state; } reset() { @@ -170,6 +243,15 @@ class MSXPlatform extends BasicZ80ScanlinePlatform implements Platform { this.vdp.reset(); this.psg.reset(); this.slotmask = 0; + this.ppi_c = 0; + } + resume() { + super.resume(); + this.resetInputs(); + } + resetInputs() { + // clear keyboard matrix + this.inputs.fill(0); } getDebugCategories() { @@ -192,9 +274,18 @@ class MSXPlatform extends BasicZ80ScanlinePlatform implements Platform { /// PLATFORMS['msx'] = MSXPlatform; +PLATFORMS['msx-libcv'] = MSXPlatform; /// +/* + C-BIOS is a BIOS compatible with the MSX BIOS + C-BIOS was written from scratch by BouKiCHi + C-BIOS is available for free, including its source code (2-clause BSD license) + C-BIOS can be shipped with MSX emulators so they are usable out-of-the-box without copyright issues + + http://cbios.sourceforge.net/ +*/ var MSX1_BIOS_LZG = ` TFpHAADAAAAAI8Sp+W4NAVo7UZPzwxINvxuYmMPtEADDvyMAw/+T4QAkAMMbEQDDNJPhIZPhc5Ph JxEhAgAAAMM5EZOhk+HmGMNOEcNYEcMWAsMiAsMuAsNFAsNNAsNVAsNgAsNtAsOBAsOXAsOtAsPU diff --git a/src/worker/lib/msx-libcv/crt0-msx.lst b/src/worker/lib/msx-libcv/crt0-msx.lst new file mode 100644 index 00000000..4da0a681 --- /dev/null +++ b/src/worker/lib/msx-libcv/crt0-msx.lst @@ -0,0 +1,91 @@ + 1 ; crt0.s for Colecovision cart + 2 + 3 .module crt0 + 4 .globl _main + 5 .globl _cv_init + 6 .globl _cv_spint_handler + 7 .globl _cv_vint + 8 .globl _cv_start + 9 .globl ___sdcc_call_hl + 10 + 11 ; Ordering of segments for the linker - copied from sdcc crt0.s + 12 .area _CODE + 13 .area _INITIALIZER + 14 .area _HOME + 15 .area _GSINIT + 16 .area _GSFINAL + 17 .area _DATA + 18 .area _INITIALIZED + 19 .area _BSEG + 20 .area _BSS + 21 .area _HEAP + 22 + 23 .area _CODE + 24 ; MSX cartridge header @ 0x4000 - 0x400f + 0000 41 42 25 .dw 0x4241 + 0002r10r00 26 .dw _cv_start + 0004r10r00 27 .dw _cv_start + 0006 00 00 28 .dw 0 + 0008 00 00 29 .dw 0 + 000A 00 00 30 .dw 0 + 000C 00 00 31 .dw 0 + 000E 00 00 32 .dw 0 + 33 + 0010 34 _cv_start: + 0010 F3 [ 4] 35 di + 0011 31 00 E0 [10] 36 ld sp, #0xe000 ; Set stack pointer directly above top of memory. + 0014 ED 56 [ 8] 37 im 1 + 38 + 0016 CDr00r00 [17] 39 call gsinit ; Initialize global and static variables. + 0019 CDr00r00 [17] 40 call vinthook + 001C CDr00r00 [17] 41 call _cv_init ; Initialize Colecovision specific stuff. + 001F CDr00r00 [17] 42 call _main + 0022 C7 [11] 43 rst 0x0 ; Restart when main() returns. + 44 + 45 .area _GSINIT + 0000 46 gsinit:: + 47 + 48 ; Implicitly zeroed global and static variables. + 0000 01r00r00 [10] 49 ld bc, #l__DATA + 0003 78 [ 4] 50 ld a, b + 0004 B1 [ 4] 51 or a, c + 0005 28 0F [12] 52 jr Z, zeroed_data + 0007 21r00r00 [10] 53 ld hl, #s__DATA + 000A 36 00 [10] 54 ld (hl), #0x00 + 000C 0B [ 6] 55 dec bc + 000D 78 [ 4] 56 ld a, b + 000E B1 [ 4] 57 or a, c + 000F 28 05 [12] 58 jr Z, zeroed_data + 0011 5D [ 4] 59 ld e, l + 0012 54 [ 4] 60 ld d, h + 0013 13 [ 6] 61 inc de + 0014 ED B0 [21] 62 ldir + 0016 63 zeroed_data: + 64 + 65 ; Explicitly initialized global variables. + 0016 01r00r00 [10] 66 ld bc, #l__INITIALIZER + 0019 78 [ 4] 67 ld a, b + 001A B1 [ 4] 68 or a, c + 001B 28 08 [12] 69 jr Z, gsinit_static + 001D 11r00r00 [10] 70 ld de, #s__INITIALIZED + 0020 21r00r00 [10] 71 ld hl, #s__INITIALIZER + 0023 ED B0 [21] 72 ldir + 73 + 0025 74 gsinit_static: + 75 ; Explicitly initialized static variables inserted by compiler here. + 76 + 77 .area _GSFINAL + 0000 C9 [10] 78 ret + 79 + 80 .area _HOME + 81 + 82 ; set up timer hook + FD9F 83 H_TIMI = 0xFD9F + 0000 84 vinthook: + 0000 3E C3 [ 7] 85 ld a,#0xc3 + 0002 32 9F FD [13] 86 ld (H_TIMI),a + 0005 3Er00 [ 7] 87 ld a, #<(_cv_vint) + 0007 32 A0 FD [13] 88 ld (H_TIMI+1),a + 000A 3Es00 [ 7] 89 ld a, #>(_cv_vint) + 000C 32 A1 FD [13] 90 ld (H_TIMI+2),a + 000F C9 [10] 91 ret diff --git a/src/worker/lib/msx-libcv/crt0-msx.rel b/src/worker/lib/msx-libcv/crt0-msx.rel new file mode 100644 index 00000000..53e83abf --- /dev/null +++ b/src/worker/lib/msx-libcv/crt0-msx.rel @@ -0,0 +1,58 @@ +XL2 +H A areas D global symbols +M crt0 +S _cv_spint_handler Ref0000 +S l__DATA Ref0000 +S _main Ref0000 +S s__DATA Ref0000 +S _cv_init Ref0000 +S _cv_vint Ref0000 +S .__.ABS. Def0000 +S s__INITIALIZED Ref0000 +S ___sdcc_call_hl Ref0000 +S l__INITIALIZER Ref0000 +S s__INITIALIZER Ref0000 +A _CODE size 23 flags 0 addr 0 +S _cv_start Def0010 +A _INITIALIZER size 0 flags 0 addr 0 +A _HOME size 10 flags 0 addr 0 +A _GSINIT size 25 flags 0 addr 0 +S gsinit Def0000 +A _GSFINAL size 1 flags 0 addr 0 +A _DATA size 0 flags 0 addr 0 +A _INITIALIZED size 0 flags 0 addr 0 +A _BSEG size 0 flags 0 addr 0 +A _BSS size 0 flags 0 addr 0 +A _HEAP size 0 flags 0 addr 0 +T 00 00 41 42 10 00 10 00 00 00 00 00 00 00 00 00 +R 00 00 00 00 00 04 00 00 00 06 00 00 +T 0E 00 00 00 +R 00 00 00 00 +T 10 00 +R 00 00 00 00 +T 10 00 F3 31 00 E0 ED 56 CD 00 00 CD 00 00 CD +R 00 00 00 00 00 09 03 00 00 0C 02 00 +T 1D 00 00 00 CD 00 00 C7 +R 00 00 00 00 02 02 04 00 02 05 02 00 +T 00 00 +R 00 00 03 00 +T 00 00 01 00 00 78 B1 28 0F 21 00 00 36 00 0B 78 +R 00 00 03 00 02 03 01 00 02 0A 03 00 +T 0E 00 B1 28 05 5D 54 13 ED B0 +R 00 00 03 00 +T 16 00 +R 00 00 03 00 +T 16 00 01 00 00 78 B1 28 08 11 00 00 21 00 00 ED +R 00 00 03 00 02 03 09 00 02 0A 07 00 02 0D 0A 00 +T 24 00 B0 +R 00 00 03 00 +T 25 00 +R 00 00 03 00 +T 00 00 C9 +R 00 00 04 00 +T 00 00 +R 00 00 02 00 +T 00 00 3E C3 32 9F FD 3E 00 00 32 A0 FD 3E 00 00 +R 00 00 02 00 0B 08 05 00 8B 0E 05 00 +T 0C 00 32 A1 FD C9 +R 00 00 02 00 diff --git a/src/worker/lib/msx-libcv/cv.h b/src/worker/lib/msx-libcv/cv.h new file mode 100755 index 00000000..a6867567 --- /dev/null +++ b/src/worker/lib/msx-libcv/cv.h @@ -0,0 +1,62 @@ +// (c) 2013 Philipp Klaus Krause + +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef CV_H +#define CV_H 1 + +#include + +#define CV_LIBVERSION_MAJOR 0 +#define CV_LIBVERSION_MINOR 24 +#define CV_LIBVERSION_STRING "0.24" + +#include "cv_input.h" +#include "cv_graphics.h" +#include "cv_sound.h" + +// Set the handler for the vertical retrace interrupt. +extern void cv_set_vint_handler(void (* handler)(void)); + +// Get the handler for the vertical retrace interrupt. +extern void *cv_get_vint_handler(void); + +// Get the vertical retrace frequency in Hz. 50 for PAL, 60 for NTSC. +unsigned char cv_get_vint_frequency(void); + +#ifdef CV_CV +enum cv_machine { + CV_COLECOVISION = 0, // Coleco ColecoVision + //CV_ADAM = 1, // Coleco Adam - TODO + //CV_SUPERGAME = 2, // Coleco ColecoVision with super game module +}; +#endif +#ifdef CV_SMS +enum cv_machine { + CV_SG1000 = 0, // Sega SG-1000 + CV_SC3000 = 1, // Sega SC-3000 + CV_MARKIII = 2, // Sega Mark III or Master System + //CV_GAMEGEAR = 3, // Sega Game Gear - TODO +}; +#endif + +// Find out on which machine we are running +enum cv_machine cv_get_machine(void); + +// Get the contents of the refresh register R. Can be useful for seeding PRNGs. +uint8_t cv_get_r(void) __preserves_regs(b, c, d, e, h, iyl, iyh); + +#endif + diff --git a/src/worker/lib/msx-libcv/cv_graphics.h b/src/worker/lib/msx-libcv/cv_graphics.h new file mode 100755 index 00000000..8507da1ca --- /dev/null +++ b/src/worker/lib/msx-libcv/cv_graphics.h @@ -0,0 +1,255 @@ +#ifndef CV_GRAPHICS_H +#define CV_GRAPHICS_H 1 + +#include +#include +#include + +/* + These are the functions for controlling the graphics chip. + While a function marked as not reentrant is called no other + such function from this file may be called at the same time. +*/ + +typedef uint16_t cv_vmemp; // 14-Bit video memory address type + +#ifdef CV_SMS +typedef uint8_t cv_cmemp; // 5-bit color memory address type +#endif + +/* + Activate / deactivate screen + + This function is not reentrant! +*/ +extern void cv_set_screen_active(bool status); + +/* + Get screen status +*/ +extern bool cv_get_screen_active(void); + +/* + Enable / disable external video source. + + This function is not reentrant! +*/ +extern void cv_set_external_video(bool status); + +/* + Get external video source enabled. +*/ +extern bool cv_get_external_video(void); + +// TMS99xxA and Sega Master System screen modes +enum cv_screenmode { + CV_SCREENMODE_STANDARD = 0x00, // Standard screen modes + CV_SCREENMODE_TEXT = 0x10, + CV_SCREENMODE_MULTICOLOR = 0x08, + CV_SCREENMODE_BITMAP = 0x02, + CV_SCREENMODE_BITMAP_TEXT = 0x12, // Usefull undocumented screen modes + CV_SCREENMODE_BITMAP_MULTICOLOR = 0x0a, + CV_SCREENMODE_TEXT_MULTICOLOR = 0x18, // Useless undocumented screen modes + CV_SCREENMODE_BITMAP_TEXT_MULTICOLOR = 0x1a, + CV_SCREENMODE_4 = 0x06, // Sega Master System 315-5124 mode + CV_SCREENMODE_4_224 = 0x16, // Sega Master System 315-5246 modes + CV_SCREENMODE_4_240 = 0x0e, +}; + +/* + Set screen mode. + + This function is not reentrant! +*/ +extern void cv_set_screen_mode(enum cv_screenmode mode); + +/* + Get screen mode. +*/ +extern enum cv_screenmode cv_get_screen_mode(void); + +// TMS99xxA colors +enum cv_color { + CV_COLOR_TRANSPARENT = 0x0, + CV_COLOR_BLACK = 0x1, + CV_COLOR_GREEN = 0x2, + CV_COLOR_LIGHT_GREEN = 0x3, + CV_COLOR_BLUE = 0x4, + CV_COLOR_LIGHT_BLUE = 0x5, + CV_COLOR_DARK_RED = 0x6, + CV_COLOR_CYAN = 0x7, + CV_COLOR_RED = 0x8, + CV_COLOR_LIGHT_RED = 0x9, + CV_COLOR_YELLOW = 0xa, + CV_COLOR_LIGHT_YELLOW = 0xb, + CV_COLOR_DARK_GREEN = 0xc, + CV_COLOR_MAGENTA = 0xd, + CV_COLOR_GRAY = 0xe, + CV_COLOR_WHITE = 0xf +}; + +/* + Set colors. The foreground color is the text color in text mode, + in other modes it is unused. + The background color is used in all modes for the backdrop plane + (screen outside display area) and as the color that appears under + transparent characters. In text mode it is used for character + background. If the background color is the to cv_transparent, + the external video source is used as background image. +*/ +extern void cv_set_colors(enum cv_color foreground, enum cv_color background); + +/* + Set the location of the screen image table. + Must be a multiple of 0x400. Valid range: [0; 15360]. + When the screenmode is CV_SCREENMODE4 on the Sega 315-5124, + the location is a multible of 0x800, and the bit 0x400 is and mask to the location. + This masking functionality is undocumented. To use only the documented + graphics chip functionality always set bit 0x400. +*/ +extern void cv_set_image_table(cv_vmemp loc); + +/* + Set the location of the color table. Must be a multiple of 0x40. + Valid range: [0; 16320]. + When the screen mode ist CV_SCREENMODE_BITMAP, + CV_SCREENMODE_BITMAP_TEXT or CV_SCREENMODE_BITMAP_MULTICOLOR + this has a different meaning: The location of the color pattern table is + either 0 or 8192. The bits 4096 downto 128 are an and mask to the location. + This masking functionality is undocumented. To use only the documented + graphics chip functionality always set bits 4096 downto 128. +*/ +void cv_set_color_table(cv_vmemp loc); + +/* + Set the location of the character pattern table. Must be a multiple + of 0x800. Valid range: [0; 14336]. + When the screen mode ist CV_SCREENMODE_BITMAP, + CV_SCREENMODE_BITMAP_TEXT or CV_SCREENMODE_BITMAP_MULTICOLOR + this has a different meaning: The location of the character pattern table is + either 0 or 8192. Unless you add 4096 to the location the first third of the table + is used for the last third of the screen. Unless you add 2048 to the location the + first third of the table is used for the middle part of the screen. + Thus the bits 4096 and 2048 are and and mask to the highest bits of the + address. This masking functionality is undocumented. To use only the + documented graphics chip functionality always set bits 4096 and 2048. +*/ +// sdcc doesn't accept long function names. +extern void cv_set_character_pattern_t(cv_vmemp loc); + +/* + Set the location of the sprite pattern table. + Must be a multiple of 0x800. Valid range: [0; 14336]. + When the screenmode is CV_SCREENMODE4 the location is a multiple of 0x2000. + For the Sega 315-5124 and CV_SCREENMODE4, bits 0x800 and 0x1000 are + used as and mask. This masking functionality is undocumented. To use only the documented + graphics chip functionality always set bits 0x1000 downto 0x800. +*/ +extern void cv_set_sprite_pattern_table(cv_vmemp loc); + +/* + Set the location of the sprite attribute table. + Must be a multiple of 0x80. Valid range: [0; 16256]. +*/ +extern void cv_set_sprite_attribute_table(cv_vmemp loc); + +/* + Set sprite size; When active, sprites are 16x16 pixels instead of 8x8. + + This function is not reentrant! +*/ +extern void cv_set_sprite_big(bool status); + +/* + Get sprite size. +*/ +extern bool cv_get_sprite_big(void); + +/* + Set sprite magnification. When active, all sprites are displayed twice as big. + + This function is not reentrant! +*/ +extern void cv_set_sprite_magnification(bool status); + +/* + Get sprite magnification. +*/ +extern bool cv_get_sprite_magnification(void); + +/* + Get sprite collission. +*/ +extern bool cv_get_sprite_collission(void); + +/* + Get invalid sprite. Returns true if there was an invalid sprite. + If sprite is not 0 it will point to the number of the invalid sprite. +*/ +extern bool cv_get_sprite_invalid(uint8_t *sprite); + +extern void cv_set_write_vram_address(cv_vmemp address) __preserves_regs(b, c, d, e); + +extern void cv_set_read_vram_address(cv_vmemp address) __preserves_regs(b, c, d, e); + +#ifdef CV_SMS +extern void cv_set_write_cram_address(cv_cmemp address) __preserves_regs(b, c, d, e); +#endif + +extern void cv_memtovmemcpy_slow(const void *src, size_t n); + +extern void cv_memtovmemcpy_fast(const void *src, size_t n); + +extern void cv_vmemtomemcpy_slow(void *dest, size_t n); + +extern void cv_vmemtomemcpy_fast(void *dest, size_t n); + +extern void cv_vmemset_slow(int c, size_t n) __preserves_regs(iyl, iyh); + +extern void cv_vmemset_fast(int c, size_t n) __preserves_regs(iyl, iyh); + +#ifdef CV_MSX +static volatile __sfr __at 0x98 cv_graphics_port; +#else +static volatile __sfr __at 0xbe cv_graphics_port; +#endif + +inline void cv_voutb(const uint8_t value) +{ +__asm + cp a, (hl) + cp a, (hl) + nop +__endasm; + cv_graphics_port = value; +} + +inline uint8_t cv_vinb(void) +{ +__asm + cp a, (hl) + cp a, (hl) + nop +__endasm; + return(cv_graphics_port); +} + +#ifdef CV_SMS +void cv_set_left_column_blank(bool status); + +void cv_set_sprite_shift(bool status); + +enum cv_scroll_inhibit { + CV_HORIZONTAL_SCROLL_INHIBIT = 0x40, // Do not scroll top 2 rows + CV_VERTICAL_SCROLL_INHIBIT = 0x80, // Do not scroll right 8 columns +}; + +void cv_set_scroll_inhibit(enum cv_scroll_inhibit inhibit); + +void cv_set_hscroll(uint8_t offset); + +void cv_set_vscroll(uint8_t offset); +#endif + +#endif + diff --git a/src/worker/lib/msx-libcv/cv_input.h b/src/worker/lib/msx-libcv/cv_input.h new file mode 100755 index 00000000..68c251bb --- /dev/null +++ b/src/worker/lib/msx-libcv/cv_input.h @@ -0,0 +1,60 @@ +#ifndef CV_INPUT_H +#define CV_INPUT_H 1 + +#include + +#ifdef CV_CV + +#define CV_FIRE_0 0x40 +#define CV_FIRE_1 0x80 +#define CV_FIRE_2 0x10 +#define CV_FIRE_3 0x20 +#define CV_LEFT 0x08 +#define CV_DOWN 0x04 +#define CV_RIGHT 0x02 +#define CV_UP 0x01 + +#endif + +#ifdef CV_SMS + +#define CV_UP 0x01 +#define CV_DOWN 0x02 +#define CV_LEFT 0x04 +#define CV_RIGHT 0x08 +#define CV_FIRE_0 0x10 +#define CV_FIRE_1 0x20 + +#endif + +#ifdef CV_MSX + +#define CV_UP 0x01 +#define CV_DOWN 0x02 +#define CV_LEFT 0x04 +#define CV_RIGHT 0x08 +#define CV_FIRE_0 0x10 +#define CV_FIRE_1 0x20 + +#endif + +struct cv_controller_state +{ + uint8_t keypad; // Keypad: 0 - 9 as on keypad, * as 0xa, # as 0xb, 0xf for no key pressed or error. + uint8_t joystick;// Joystick: lowest 4 bit for joystick, higher 4 bit for fire buttons. +}; + +// Get keypad and joystick values. +void cv_get_controller_state(struct cv_controller_state *state, uint_fast8_t controller); + +#define CV_SPIN_ACTIVE 0x10 +#define CV_SPIN_RIGHT 0x20 + +// Set the handler for the spinner interrupt. +// This handler will be called when the wheel on the super action controller or the ball in the roller controller spin. +// The parameters passed to the handler correspond to the two super action controllers or +// the two axis in the roller controller. They can be anded with the above masks to test if they spinned, and which direction. +void cv_set_spint_handler(void (* handler)(uint_fast8_t, uint_fast8_t)); + +#endif + diff --git a/src/worker/lib/msx-libcv/cv_sound.h b/src/worker/lib/msx-libcv/cv_sound.h new file mode 100644 index 00000000..9099dd33 --- /dev/null +++ b/src/worker/lib/msx-libcv/cv_sound.h @@ -0,0 +1,43 @@ +#ifndef CV_SOUND_H +#define CV_SOUND_H 1 + +#include +#include + +enum cv_soundchannel { + CV_SOUNDCHANNEL_0 = 0x0, + CV_SOUNDCHANNEL_1 = 0x2, + CV_SOUNDCHANNEL_2 = 0x4, + CV_SOUNDCHANNEL_NOISE = 0x6 +}; + +enum cv_shift { + CV_NOISE_SHIFT_512 = 0, + CV_NOISE_SHIFT_1024 = 1, + CV_NOISE_SHIFT_2048 = 2, + CV_NOISE_SHIFT_CHAN2 = 3 +}; + +/* + Set attenuation for given sound channel in dezibel. Maximum attenuation is 28 db, + granularity is 2 db. +*/ +extern void cv_set_attenuation(enum cv_soundchannel channel, uint8_t dezibel); + +/* + Set frequency of a tone generator. The frequency is 3.579/frequency_divider Mhz. + This function is not reentrant. While it is called neither cv_set_attenuation() nor + cv_set_noise() may be called. n should be a multiple of 32. The valid range is [0, 32736]. +*/ + +extern void cv_set_frequency(enum cv_soundchannel channel, uint16_t frequency_divider); + +extern void cv_set_noise(bool white, enum cv_shift shift); + +#ifdef CV_MSX +static volatile __sfr __at 0xa0 psg_port_register; +static volatile __sfr __at 0xa1 psg_port_write; +static volatile __sfr __at 0xa2 psg_port_read; +#endif + +#endif diff --git a/src/worker/lib/msx-libcv/cv_support.h b/src/worker/lib/msx-libcv/cv_support.h new file mode 100755 index 00000000..20dc4a9e --- /dev/null +++ b/src/worker/lib/msx-libcv/cv_support.h @@ -0,0 +1,19 @@ +#ifndef __CV_SUPPORT_H +#define __CV_SUPPORT_H 1 + +#include + +#include "cv_graphics.h" + +extern void cv_init(void); // Initialize Colecovision library. + +extern void cv_vdpout(const uint8_t reg, const uint8_t data) __preserves_regs(d, e, iyl, iyh); // Write data to VDP control register reg. + +#ifndef CV_MSX +extern void cv_enable_nmi(void); + +extern void cv_disable_nmi(void); +#endif + +#endif + diff --git a/src/worker/lib/msx-libcv/cvu.h b/src/worker/lib/msx-libcv/cvu.h new file mode 100644 index 00000000..42e9343a --- /dev/null +++ b/src/worker/lib/msx-libcv/cvu.h @@ -0,0 +1,32 @@ +// (c) 2013 Philipp Klaus Krause + +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef CVU_H +#define CVU_H 1 + +#define CVU_LIBVERSION_MAJOR 0 +#define CVU_LIBVERSION_MINOR 17 +#define CVU_LIBVERSION_STRING "0.17" + +#include "cvu_input.h" +#include "cvu_graphics.h" +#include "cvu_sound.h" +#include "cvu_compression.h" +#include "cvu_f.h" +#include "cvu_c.h" + +#endif + diff --git a/src/worker/lib/msx-libcv/cvu_c.h b/src/worker/lib/msx-libcv/cvu_c.h new file mode 100644 index 00000000..0bb1c6a3 --- /dev/null +++ b/src/worker/lib/msx-libcv/cvu_c.h @@ -0,0 +1,37 @@ +#ifndef CVU_C_H +#define CVU_C_H 1 + +#include "cvu_f.h" + +// Complex fixed-point type +struct cvu_c +{ + cvu_f r; + cvu_f i; +}; + +// Addition +//extern void cadd(struct c *l, const struct c *r); +#define cvu_cadd(x, y) {(x)->r += (y)->r; (x)->i += (y)->i;} + +// Subtraction +//extern void csub(struct c *l, const struct c *r); +#define cvu_csub(x, y) {(x)->r -= (y)->r; (x)->i -= (y)->i;} + +// Multiplication +extern void cvu_cmul(struct cvu_c *l, const struct cvu_c *r); + +// Very inaccurate approximation +extern cvu_f cvu_cabs(const struct cvu_c *l); + +// Dot product: Returns l.r^2 + l.i^2 +extern cvu_f cvu_cdot(const struct cvu_c *l); + +// Multiplication by fixed-point number. +extern void cvu_cfmul(struct cvu_c *l, cvu_f r); + +// Convert from polar to coordinate representation +extern void cvu_c_from_polar(struct cvu_c *c, cvu_f phi, cvu_f d); + +#endif + diff --git a/src/worker/lib/msx-libcv/cvu_compression.h b/src/worker/lib/msx-libcv/cvu_compression.h new file mode 100644 index 00000000..17f581e0 --- /dev/null +++ b/src/worker/lib/msx-libcv/cvu_compression.h @@ -0,0 +1,93 @@ +// These decompression routines are rather complicated to allow customization. +// If you only want to decompress some data into graphics memory, you can use +// the functions at the end of this file instead. + +#ifndef CVU_COMPRESSION_H +#define CVU_COMPRESSION_H 1 + +#include +#include "cvu_graphics.h" + +// Huffman decompression +#undef CVU_HUFFMAN_ITERATIVE +#undef CVU_HUFFMAN_RECURSIVE + +struct cvu_huffman_node // Changing this struct will affect asm implementation of cvu_get_huffman +{ + uint8_t left; // Position of left node in tree or character. + uint8_t right; // Position of right node in tree or character. +}; + +struct cvu_huffman_state // Changing this struct will affect asm implementation of cvu_get_huffman, _read_from_array +{ + uint8_t (*input)(void); + const struct cvu_huffman_node *nodes; // Array of nodes + uint8_t root; // Position of root node among nodes + uint8_t ls, bs, rs; + unsigned char bit; // Position of currently processed bit + uint8_t buffer; // Currently processed input byte +#ifdef CVU_HUFFMAN_RECURSIVE + uint8_t current; // Currently processed node +#endif +}; + +void cvu_init_huffman(struct cvu_huffman_state *restrict state, uint8_t (* input)(void), const struct cvu_huffman_node *restrict tree, uint8_t root, uint8_t ls, uint8_t bs, uint8_t rs); +uint8_t cvu_get_huffman(struct cvu_huffman_state *state); + +// RLE decompression +struct cvu_rle_state // Changing this struct will affect asm implementation of cvu_get_rle, _read_from_array +{ + uint8_t (*input)(void); + uint8_t escape; + uint8_t left; + uint8_t buffer; +}; + +void cvu_init_rle(struct cvu_rle_state *restrict state, uint8_t (* input)(void), uint8_t escape); +uint8_t cvu_get_rle(struct cvu_rle_state *state); + +// LZK decompression +struct cvu_lzk_state +{ + uint8_t (*input)(void); + uint8_t escape; + uint8_t left; + uint8_t offset; + uint8_t start; + uint8_t buffer[64]; +}; + +void cvu_init_lzk(struct cvu_lzk_state *restrict state, uint8_t (* input)(void), uint8_t escape); +uint8_t cvu_get_lzk(struct cvu_lzk_state *state); + +// Decompression routines which will handle all the details for you. +// Just create a cvu_compression_state struct, initialize it and decompress. +struct cvu_compression_state // Changing this struct will affect asm implementation of _read_from_array +{ + struct cvu_huffman_state huffman; + struct cvu_rle_state rle; + const uint8_t *data; +}; + +// Initilization: +// data: compressed data +// state: compression struct to initialize +// tree: huffman tree generated by huffman_analyzer +// root, ls, bs, rs: constants generated by huffman_analyzer +// escape: constant generated by rle_analyzer +void cvu_init_compression(const uint8_t *restrict data, struct cvu_compression_state *restrict state, const struct cvu_huffman_node *restrict tree, uint8_t root, uint8_t ls, uint8_t bs, uint8_t rs, uint8_t escape); + +// The functions below can be mixed: + +// This function will return a decompressed octet on each invocation. +uint8_t cvu_get_compression(struct cvu_compression_state *state); + +// This function will decompress and write n octets to graphics memory at dest. +// It is not reentrant and may not be called why any function from cvu_graphics.h marked as such is called. +void cvu_memtovmemcpy_compression(cv_vmemp dest, struct cvu_compression_state *state, size_t n); + +// This function will decompress and write n octets to memory at dest. +void *cvu_memcpy_compression(void *restrict dest, struct cvu_compression_state *state, size_t n); + +#endif + diff --git a/src/worker/lib/msx-libcv/cvu_f.h b/src/worker/lib/msx-libcv/cvu_f.h new file mode 100644 index 00000000..51b99747 --- /dev/null +++ b/src/worker/lib/msx-libcv/cvu_f.h @@ -0,0 +1,46 @@ +// Fixed-point math can be useful where e.g. smooth movement is desired, but +// using float would make the application too slow and big. +// cvu_f is a 10.6 fixed point type. 10.6 has been chosen to ensure that the +// type can represent coordinates on the ColecoVision screen and in some +// "buffer" space surrounding it (to allow calculation of e.g. reflection). + +#ifndef CVU_F_H +#define CVU_F_H 1 + +#include +#include + +typedef int16_t cvu_f; // 10.6 fixed-point type. + +#define CVU_F_R 6 + +#define CVU_F_PI 201 +#define CVU_F_PI_2 100 +#define CVU_F_SQRT2 90 +#define CVU_F_SQRT1_2 45 +#define CVU_F_MIN INT16_MIN +#define CVU_F_MAX INT16_MAX + +// Convert integer to fixed-point +#define CVU_F2I(l) ((l) >> CVU_F_R) + +// Convert fixed-point to integer +#define CVU_I2F(l) ((l) << CVU_F_R) + +// Fixed-point multiplication +extern cvu_f cvu_fmul2(cvu_f l, cvu_f r); + +// Fixed-point division +extern cvu_f cvu_fdiv2(cvu_f l, cvu_f r); + +// Fixed-point sine +extern cvu_f cvu_fsin(cvu_f x); + +// Fixed-point cosine +extern cvu_f cvu_fcos(cvu_f x); + +// Fixed-point arcus tangens of x / y. +extern cvu_f cvu_fatan2(cvu_f y, cvu_f x); + +#endif + diff --git a/src/worker/lib/msx-libcv/cvu_graphics.h b/src/worker/lib/msx-libcv/cvu_graphics.h new file mode 100644 index 00000000..12e64928 --- /dev/null +++ b/src/worker/lib/msx-libcv/cvu_graphics.h @@ -0,0 +1,122 @@ +#ifndef CVU_GRAPHICS_H +#define CVU_GRAPHICS_H 1 + +#include + +#include "cv_graphics.h" + +#ifdef CV_SMS +/* + Copy n bytes of data from RAM at src to CRAM at dest. + + This function is not reentrant! + + The speed of this function depends on the active video mode + and if the screen is active. +*/ +extern void cvu_memtocmemcpy(cv_cmemp dest, const void * src, size_t n); +#endif + +/* + Copy n bytes of data from RAM at src to VRAM at dest. + + This function is not reentrant! + + The speed of this function depends on the active video mode + and if the screen is active. +*/ +extern void cvu_memtovmemcpy(cv_vmemp dest, const void * src, size_t n); + +/* + Copy n bytes of data from VRAM at src to RAM at dest. + + This function is not reentrant! + + The speed of this function depends on the active video mode + and if the screen is active. +*/ +extern void cvu_vmemtomemcpy(void *dest, cv_vmemp src, size_t n); + +/* + Set n bytes of VRAM at dest to c. + + This function is not reentrant! + + The speed of this function depends on the active video mode + and if the screen is active. +*/ +extern void cvu_vmemset(cv_vmemp dest, int c, size_t n); + +/* + Write an octet to graphics memory at dest. + + This function is not reentrant! +*/ +extern void cvu_voutb(const uint8_t value, const cv_vmemp dest); + +/* + Read an octet from graphics memory at src. + + This function is not reentrant! +*/ +extern uint8_t cvu_vinb(const cv_vmemp src); + +struct cvu_sprite +{ + uint8_t y; + uint8_t x; + uint8_t name; + uint8_t tag; +}; +#ifdef CV_SMS +struct cvu_sprite4 +{ + uint8_t y; + uint8_t x; + uint8_t name; +}; +#endif + +// Write sprite to display memory. Use the location of the sprite table as base. number should be in [0, 31]. +inline void cvu_set_sprite(const cv_vmemp base, uint_fast8_t number, const struct cvu_sprite *sprite) +{ + cv_set_write_vram_address((base) + (number) * 0x4); + cv_memtovmemcpy_slow((sprite), 4); +} + +// Write sprite to display memory (in mode 4). Use the location of the sprite table as base. number should be in [0, 63]. +#ifdef CV_SMS +inline void cvu_set_sprite4(const cv_vmemp base, uint_fast8_t number, const struct cvu_sprite4 *sprite) +{ + cvu_voutb(sprite->y, base + number); + cv_set_write_vram_address(base + 0x80 + number * 2); + cv_voutb(sprite->x); + cv_voutb(sprite->name); +} +#endif + +// Todo: is cvu_get_sprite needed? + +// Set the x coordinate of the sprite's upper left corner. x will be clamped to [-32, 255] +extern void cvu_set_sprite_x(struct cvu_sprite *sprite, int x) __preserves_regs(d, e); + +extern int cvu_get_sprite_x(const struct cvu_sprite *sprite) __preserves_regs(b, c, d, e); + +// Set the y coordinate of the sprite's upper left corner. y will be clamped to [-32, 207] +extern void cvu_set_sprite_y(struct cvu_sprite *sprite, int y) __preserves_regs(d, e); + +extern int cvu_get_sprite_y(const struct cvu_sprite *sprite) __preserves_regs(d, e); + +// Set them both at once. +extern void cvu_set_sprite_xy(struct cvu_sprite *sprite, int x, int y); + +// Set the sprite's color. +inline void cvu_set_sprite_color(struct cvu_sprite *sprite, enum cv_color color) +{ + sprite->tag = (sprite->tag & 0x80) | color; +} + +extern enum cv_color cvu_get_sprite_color(struct cvu_sprite *sprite); + +#endif + diff --git a/src/worker/lib/msx-libcv/cvu_input.h b/src/worker/lib/msx-libcv/cvu_input.h new file mode 100644 index 00000000..0353a889 --- /dev/null +++ b/src/worker/lib/msx-libcv/cvu_input.h @@ -0,0 +1,15 @@ +#ifndef CVU_INPUT_H +#define CVU_INPUT_H 1 + +#include + +#include "cv_input.h" + +// For the super action controllers this gives the spinner movement (total relative movement since last call, negative for left, positive for right) +// For the roller controller this gives the ball movement (total relative as above). +// Using this function will set a libvu-specific spint handler, so it is incompatible with using a custom spint handler. + +int_fast8_t cvu_get_spinner(uint_fast8_t controller); + +#endif + diff --git a/src/worker/lib/msx-libcv/cvu_sound.h b/src/worker/lib/msx-libcv/cvu_sound.h new file mode 100644 index 00000000..77f94468 --- /dev/null +++ b/src/worker/lib/msx-libcv/cvu_sound.h @@ -0,0 +1,45 @@ +#ifndef CVU_SOUND_H +#define CVU_SOUND 1 + +#include + +#include "cv_sound.h" + +extern const uint16_t CVU_TUNING_ISO16_EQUAL[15]; // ISO 16 pitch (A at 440 Hz) with equal tuning. +extern const uint16_t CVU_TUNING_SCIENTIFIC_EQUAL[15]; // Scientific pitch (C at 256 Hz) with equal tuning. + +extern const uint8_t CVU_VOLUME_DEFAULT[4]; + +extern const uint16_t CVU_EMPTY_MUSIC; + +// channel holds the channel that will be used to play the music. +// volume is a pointer to an array of loudnesses in Dezibel for p, mp, mf, f. +// tuning is a pointer to an arrays of frequency dividers for the halftones of oktave 0. +// sixteenth_notes_per_second should explain itself. +// notes is a pointer to the music in the following format: +// Every note cosists of 16 bits. The most significant four mark the octave, the next 4 +// the halftone (0xf means pause) the next 4 bits give the absolute length. The next 2 +// give the relative length. the last 2 bits are the loudness. +// The rest of the structure's members are for internal use by cvu_play_music(). +struct cvu_music +{ + enum cv_soundchannel channel; + const uint8_t *volume; + const uint16_t *tuning; + uint8_t sixteenth_notes_per_second; + const uint16_t *notes; + + uint16_t note_ticks_remaining; + uint16_t pause_ticks_remaining; +}; + +// This will initialize a cvu_music structure with default values for all +// members except notes. +extern void cvu_init_music(struct cvu_music *music); + +// This function should be placed inside the vint handler or triggered by the vint handler. +// It will return false once it is finished playing the music. +extern bool cvu_play_music(struct cvu_music *restrict music); + +#endif + diff --git a/src/worker/lib/msx-libcv/libcv-msx.lib b/src/worker/lib/msx-libcv/libcv-msx.lib new file mode 100644 index 0000000000000000000000000000000000000000..9199e2bfdb1e7df54c0fd20115a6c6c7b804bb39 GIT binary patch literal 27804 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 literal 0 HcmV?d00001 diff --git a/src/worker/lib/msx-libcv/libcvu-msx.lib b/src/worker/lib/msx-libcv/libcvu-msx.lib new file mode 100644 index 0000000000000000000000000000000000000000..5b873381114fc9a984e2ff765b161f6d637b4a37 GIT binary patch literal 38666 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 CV6q -#ifndef CV_SMS +#ifdef CV_CV #define CV_FIRE_0 0x40 #define CV_FIRE_1 0x80 @@ -14,7 +14,20 @@ #define CV_RIGHT 0x02 #define CV_UP 0x01 -#else +#endif + +#ifdef CV_SMS + +#define CV_UP 0x01 +#define CV_DOWN 0x02 +#define CV_LEFT 0x04 +#define CV_RIGHT 0x08 +#define CV_FIRE_0 0x10 +#define CV_FIRE_1 0x20 + +#endif + +#ifdef CV_MSX #define CV_UP 0x01 #define CV_DOWN 0x02 diff --git a/src/worker/lib/sms-sg1000-libcv/cv_sound.h b/src/worker/lib/sms-sg1000-libcv/cv_sound.h index 5241d0b9..9099dd33 100644 --- a/src/worker/lib/sms-sg1000-libcv/cv_sound.h +++ b/src/worker/lib/sms-sg1000-libcv/cv_sound.h @@ -34,4 +34,10 @@ extern void cv_set_frequency(enum cv_soundchannel channel, uint16_t frequency_di extern void cv_set_noise(bool white, enum cv_shift shift); +#ifdef CV_MSX +static volatile __sfr __at 0xa0 psg_port_register; +static volatile __sfr __at 0xa1 psg_port_write; +static volatile __sfr __at 0xa2 psg_port_read; +#endif + #endif diff --git a/src/worker/lib/sms-sg1000-libcv/cv_support.h b/src/worker/lib/sms-sg1000-libcv/cv_support.h index cfed5ca5..20dc4a9e 100644 --- a/src/worker/lib/sms-sg1000-libcv/cv_support.h +++ b/src/worker/lib/sms-sg1000-libcv/cv_support.h @@ -9,9 +9,11 @@ extern void cv_init(void); // Initialize Colecovision library. extern void cv_vdpout(const uint8_t reg, const uint8_t data) __preserves_regs(d, e, iyl, iyh); // Write data to VDP control register reg. +#ifndef CV_MSX extern void cv_enable_nmi(void); extern void cv_disable_nmi(void); +#endif #endif diff --git a/src/worker/lib/sms-sg1000-libcv/cvu_graphics.h b/src/worker/lib/sms-sg1000-libcv/cvu_graphics.h index 7aa47d90..12e64928 100644 --- a/src/worker/lib/sms-sg1000-libcv/cvu_graphics.h +++ b/src/worker/lib/sms-sg1000-libcv/cvu_graphics.h @@ -86,8 +86,7 @@ inline void cvu_set_sprite(const cv_vmemp base, uint_fast8_t number, const struc // Write sprite to display memory (in mode 4). Use the location of the sprite table as base. number should be in [0, 63]. #ifdef CV_SMS -/* TODO: used to be inline, but sdcc barfs */ -void cvu_set_sprite4(const cv_vmemp base, uint_fast8_t number, const struct cvu_sprite4 *sprite) +inline void cvu_set_sprite4(const cv_vmemp base, uint_fast8_t number, const struct cvu_sprite4 *sprite) { cvu_voutb(sprite->y, base + number); cv_set_write_vram_address(base + 0x80 + number * 2); diff --git a/src/worker/lib/sms-sg1000-libcv/libcv-sms.lib b/src/worker/lib/sms-sg1000-libcv/libcv-sms.lib index 3ed3bba50b62c0b27619a5dce36ac42dd6f0c62a..69767bc7225af081ea6f936c3e7b9b1eb049722e 100644 GIT binary patch literal 33278 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 BPA&id literal 34462 zcmeHQ>2BLd68^8J=ogqoBt=mIi~u@!40t9$oCOB^N04nf5!RNomg6~|e!r?d*e6BG z&SYm+fnh7n?&@OqS7#qOyPVHv7w6M=z24dR&-2AE?|Op&6{FFxclOi!`9Cif?|RqI ztL<+4^uzvsFUGw$tJRyy^zuz_h4S0&?q>D8-94>tA21DLy}LKFMOL2Ws2v_0xl@X*WH4tJ!fFqrRE3yg#U1-sn*;)$HcW z!&lX?8hI}TuJAC`^wZ`^zOi~K?!Cyy1Gjg-T8i90O0FtPmz-ZMMb30<=7aeg6jkOD z3@bK~ykB2`-tIP%)_C9z7|~KSQbg3_)%C;fcKeCxlx3E?Jh_!pq^P$?rZCL=-kkHZ zriSSM>G9zXU|bOjyCF-4L(-(Np&HoMjO`f2+thjQG{`yrD~y%a$npOE0N z9~(!zUW%Z_;r+vnXk5)dYVvLzuDOy&K*ASEKe!qR#G5oTk$Rg6Vmn7@TEhuky&+EJGjUqeFya~2= zP&Hf7ykCFXZEwkX7={PK!7zeF$*@yDMW5$9QeSdj<}v9}1JauQ_2=~?1PBW0%le6g ze)Y7zx>IN^$At$8Vky$3=?yS9505t0;jr&%UZz@CJ4kIWN1)296rp_+jV(rJV{-%> z9C=TxS`#%j;uyWh?bTOElr?~(GWS4h&8gR-MIVtV8s~+lP^=!D`c3p7H)}XjkL&x@ z`sU_wvsVK=9*rXd98_+i{3|P&fOOt31`*O>?G)|%&HdBEGk#xx`F*v&d-#M%o5kci9%1VLmKW}fJL|(y%b74cg+I1RfU6Xr_ zNbd9YO2v%%s48PB(y7@*^D`VO5S8yc9^_sqL+q(4O;ny8D&vvobr2O*#i8<6MaI3e z<@v?pl4520;Dp7!vk!}lcfDUO^4^bPEY?uadodHA?jEkzcVhqh{nf+WzW0+*k#z69 z_~HJKQNMR7D2V2styoul*xdH{BJUwjjqMCu&vm3O<_7{JgR}DvcQ1JA} z_dlsI(@)Ki;psmvsdu-v`+Rx+^W-93p=SO=f_d~ZLqf(SVL1FK`hEQLKFGhKpV9x4 zrFpb&ytIhcjh7Chb>pQjzub4gZ@iRSs=8u17dgEw{or%8;15F@0s3!6|DBENf#HgM z5C&Izkmji5)}W!I(wg?76f-WNelk&@AM{X)qCmOZnA-JefcOVpF%vu=*+dujsb!`) zH?J_8LMVN5AR&K15g*2e^SlH(w>CKEQUOYXp~y#KIit@$RjN0Qwx&{%;k68bDZlPW zOedlkVdR1yizpvlN==KI$j~ki+u>D0{tGJQc99lQo(txAI;8oYEX}kK8KP8p)9CWS zV@m81FCWE#dzURS(NTOYhGMyp_=?aXLi~ddn0ke>QlTI~Bl;|(Us@#>+_v4a{i0l*m>X8=patVFM(ipdp3JrI=5}bSVlg>6VhHfoOC9 zplT2f{clU8?n7EFX}(zJK$ij6vysTP#UNSW>L27^{Tn>!Dx|_7E>sb~ZijNSMB?B) zQ{HV6$GKcOxfC(L!qsgB%TWTBOxpl65N|zVJi3ca%&{}1!tzO z8i{$q6S@$izL;k6zb;i)Q0!MQETri!^w3W$k@v-7CKfSY1Kh9`XgMjA8nqP%g+$Ku z=-?whJ$`d95V0v+`Xrc3M~e*Nv*mYX<_(SOkRT?B|Mv<-BPZ&dYFTdy7(ce`2pGTl zYXD{$2aMm<2{0lZ#5uZd>4gi>_3#Pdi9xgQXT-fz#3s{-+LGpntr=SyA9HQ3g%NjW z-T4^RWC(azOQu1Z)l(HmTgdMTZ*B2rOdEooVkw5pAaG&ab<|WeBWjL2Dk-bw>tJS_ z#*E_d1eVTVp@6p%fDAmaSAvYI5(3l|%2myyuBzJ@NyKJ767y7QKPHPK#_hufjhz?F z1B)2=w(KJ|mQpqV;Rj*?)_iJYqf5c05UkHI@UvPSvs$oWzTbTpJL(YZ>3=EKAax+4@21>(z!6O+v^3DUE zI6#V*8V}!nOtf@r1Ot=183xS=mC}nPo0q0KUb1|yuXPN~qNiT%-xKu!g!l=wu z)K9rZ$Z<6%djl5*Zt$^=Erlh{3RM!z@sK-cZvmMX|LhI2t%L4G9OFS}CxEy0QxHVi z=z$s>C+N^5wg1h7dN$6cPBvsTP>IsJgI>X6e44a4I z7)vcSgq@#2))r)D>0z+ogE#5QP|SrTk5P4Q3rK2pQoxf(X-9|I}m8KZ76Ef6ScqB=SOsx6=>cgqV* z$kAd!6(?@6v72@?Oq_M7!t;ddJcN$qC==kFh9NIPs&JqVZY?{RAJ}*1R&^;yfSS?~ z*}5Zf?OcvSHax0tWuq+B`{>s$YJgR-pCa$`qq~Y zmY%?qpwtS&>DAiA8Jt9iC$M$_t7Z24Fy8!w&>x0;fYcY~vtkFA_yJC=(*A*gf08u? zO*j-J8=at8-vg9Xr*X%V(NVX4Ap{#%$E+KTPS9%$J>)g9!-=w!SPY{T05=y(o+V1d z4R%~gt7$Oh_>GMm83Le*C~@orP{h6|=YX(Fl{F(WtPDc9qP&ODU#IGfPQD$ftDYoa z?q7(>#K`~y{QU`zZQ)2}o6}4(0Fqll&4$pWykH?3xkwd>W{8-Sovu6(#+O2mSh%vT zAUi@CTCHCQyhMCLz_U6zp|1=20y_*NjLpK95yk}(t~xW}HyL51>eVeD2mC`=^{z%( zMJ3)1bw@xjPUwYbY%tC7{!YMb3ryciTL*AB*>a?nEwrQ@4`M(j|QN(Z?&lEv>}X~1L9 zl>sA^04oDOr^}_Z479QAGtb8QvK zsf#c8Hl*m?7x4bNn#J&X(j%rYB=W*JOx&YG&RN*kcb!D6TOrHOSDLsW@aIu_=K3ZI zOyvwEj1oduY@Z>#DVk0Gfy2{fWx%;LRbM6Rgm+pl7 zlSgYjJmfgFG-0n6vn5Nik`DbYJuiZ%hk|_~g>$a_*HBR28<($GO+Nb<*vTuNG;n-+`bOQuV3t#13l|m*`%PDEO@T@BvK6f@qDtUZK-7a+A z44cw%FC|3MI)|?%o6Zr2G^jLCxoySJQE2*V=wnqYUmJbsq6Fjw535$9)N;yRLABD= zXRjcJ*!>B0dRX*m;^~fna4M904Hu#ew)DoOnvIg7gKVa=Jy zS8L7LK@1OQPMk8t`GE@CrUTT!TysKOVrRBgo1W`QvIfN+ku@qkT5Ue-OYj|4sZV8R z(bHtyrYSq&c%aSq$;6)i{qMzVd7uQ>6!JF?beS78IZX}Ufv&C*k0Gq#4%C)YLz>CD zTRsTRwFIT}D^}~K32WF)?uc2Q@J2jFjR8aB`c|jd82$aF5Wa3S9^}&cs&i}{FOU$x z6)^IsC9-x4`b_t%mpCuu+5yfls1$+mrjaA01_)hC>qZ&SMQDmO>ZMD9RDFYothxqE zpr?PZqJx!0IQUq&#sML&6Hv`Lb|xMW9Uxj~u3+zujf>bk+h0l&RaEg*hSRo5034Jf zrxW$L9cm|xw@qc!j)zFiNim93IQN3j^4z zB%k3(a4GK-!8JM9Br;a}L@LM~OVvx{$$T|WM_H2J*i&zR54Hw7a)g*5DMw7{Btxxb z2suv|(ZGZ{j1P?CX^5+S!-Zi$k&!UD_2cEB3cr6Ta9DKWIqk57L}FoChZUiQjlyAH z29V8G8v;%1L9e+a1idy>O9|K+=IX`hY6A2Me%dT003VzGE+6Dy^*>eWIIm=p-RnW* zg2#8d9)yJhJmKOcbv+1|xVJGyq|7Y4L1^^;Hh2}-RE$g`=Ns{%`GydXAjhSPh^eg- z{Ecx@i=37$r)h&>{&R)O=ISI*ZH>GE7y_$Mp1$jKJ6fJ#Z~hAM#_-#VstN7z~^#_FLlY}IH zPROy(5^~}LLee%NxmiNagRHqfH%AEeFP|0tE8iys`|N$u=U_pvFI*r5`_kV;U)e_p z_SLT+Aq4wPFarC_=Lz{H0N(jKA!~0F@;VstJs5xUY3x>EdC963>sD>CI$tkZ*H_4- zWlg50FHRCZRxXz7m1X!XEH)3Jv|ec%S{2i93}BnUmMcs3Syy3xVYX>>v6Nr2%1gER zLes$f!hGEwu+q<7wzB8WUwYm;clqKoX~G51e(CJxb6G2s&86BepS?ubH^M_9!hRnf zxgb{K2rn$n&6V>D0ZFw~6mQpH2O2M2x%9Oc{ULm5YzzDIrO|8vq);g@R*SXTe8q)E zunj_dD_0h*T0LJc`al*td1e1<;N7Yg^H;68YNc%DtJVC9SY)oeR8qua&60Ke14&>S z9^u^8`DIsWt~S3=&(C_U%@ry&7YGKLr{!1c`2{5)wUz>Fa0V360D(}rT4_Gg!e(0v z`B`_xf^cTBQp#6d4+2!<62-#p^4PaY$=!p1&I)0)I~v?J^q*6);Bt|~n}dI2D%kX< z=}A{+M>Yk-(OS7SSuK|0e26c!TxR!ryH|Z_FcdcGyMfRo#5iaYK|E}f_~GEM<3|F> z!lgK?=tCWRnB5JJ%r_~zgm01p&zmGbZ(o92y=h{A&RmoAcMQ4iHa)oQilB6dRlo~P= z58iC&e)n&~Q*p&`1>#>T09jH22Al0X?Coivifg!v-&zCc9sqmTJK;Rl-v%It=_9~X zYY=$@$lKB1wN1sr$skHA5H0Dc+blNng!Ay=A=nRD-e5{gkc8~6X*;ozL*ZkQxU6p2 z(gp$)=dF%J^UF4S^vZ8aa@*9p|KB5e>Do8co9-$(@{)S?aNY)ijj=QHorm zN(x6ZGEK{9jRBJolL8)q!5^Ta+@QG}JVY+ISR4`N!`Zp?Hy;O>_cltU<#bVnS&t82jb`9)zt%bWRtcGrZwcKV|ajk`|5Z*WZ*3ICW z+*|Mp=5O2(1Oh}uWBD@&*^iEQg<(ryTZ;CXs0&A+W+<$Z%dlAS09#)g3CV0`;Q-R# z*cEXNI62X5r;_;I^l-PGeNZ=mCs_K*fb-oeRfu)f+X&%!E1#8QBi5Z$)^Lc&IlqDOS{kS=+gKti z5T3l*&YJz#b^(C5(`EqJ+^vqz)Ec{viNg7H@eRnuNC3Rp!ey4@H2l~GMNTFg1X#15 zaPG|~yM!ruxbW`K6aaC{lPC-4UiwaFM|zqj6!w>|zKf@n+?vx;jyWQ!&`AFn-yufb) zg6-VuxODHhp*Y}i*)JF28jz5-jk#{+@;OXWW5OvxI5;j!P)?z$=3)qA1Ux>onVBKBSL^D)f}_Et z=!>4J2Fvj}s_{DN`U~!RH7kL{-sDL_#iigbj8&HZ!32l_@Ea=;45c^(I>{bP3?9+2 z0%0}C!a6xD2vF6e)FxofU}I;ZF~ju)H(#e|okPN$6fOdgpB^6!>8yVy+OKn9VK;az zDJ*|=7=kd-U?68eE2`*d49EH-C<_t&}?iHj1*q#PbmX5ON z`Ves4@N({s959o9=nAg)gjV=H3fZnnF57$9k#*O6xJ)4n)Blf!9^=EZ!v1}2g#Gt+ z56dq`$Km|IGvJ)enF-r3I48iGIVWe3_eVWdaj07b81(}S2`=+U*+qb7(25eAp06s(0>cuq+bxPtkN$%$+_{TwisT z#mcpg+p4zR*49qFU3IVTo@{Nlh$Sw$?oz&RZb@`UR*1PwA3dyRv%1-CUW4z-)~?C) znAxBv|C(`%=x6(qT_R_*M!*Hs+ir5@6NhT3H|lLqgDh!EFmiih z(Y0S$cb(HOpMTXoy?$=R<}Y43v$}rTaf?nVzrK22%vmA%ZFd^=#uc~TYRZON-R_h( zh%9TS&)(03RC27F^i5-aipYxW_l`az9O;f-j+|p|f2UKcZI&B=G_7D%{R{rPmYt0% zkyXQx0(k)q-L?zTQ1}%%8Vs$e5~jwV8BZ39W&Uk^_jMxck~Y?P5Qgs`)pK-K3R2ON zg_=hBAaq$t8HeX`y%DOU(%jtIskU16Cd5B$Y0BRI>G8FrF_E5KOTzc|+UzmWbz%8t zv*EVd<@VTk(tjpWQ$&%nmaitz@~f%wA#iu9<;!kur@85tcXrCVM3F7Y=Yktt5W0`r zW@keoilS)2dcY)NmzOvfNDUuY9Iokd{TgUebuAds8cM=(t$L%Q**#qh_ki(x=XxY6>Z7Extc2_E1I2*OdEJzLF<@(!T0BW1b5 z7j)T*P&NMUNK}Yg&09icmE^Tf3kptNH^O=+wKRT70HZ z3)Hrt8vlxMYUr;glG8-h49QphUHaC1w<6$z(~IZ+$A`X%n~~vsdKvirD4XuhrJfax$O^m> z#`7QXznc43EE^T?A5~cNAIyI^6B*6gC7Xjh`w!;R^jRy>`#f_${;jA!jLs;YHaC~1 zw~Wc&ly+43N)&A{vK&b;7E!?VZW&9$H=^K>>cgW6i^ZetiFo8pFg%+}7}m|56;4I9 zjYoF?6)&tR;|e&-DFniY}@Pf}E!h-$*XuyFG2FL?MMDvH44eQ~Eke zMS8C~Lu`adl6W545k4AB4lWo5k!0e~#g!TQ(|R(^fdf}f9X-UKOs$4W;F%t@Q}m;1 zf}VO^JfTomHTfEtWxG-P_R(RD*g@SQY#0(f_`%6^?~~HmC-VOf+?x}*3O`>aJqdC~&@H-cr4FIJ{$N?G z)+8Xo^gKzAvPgpbu}EnwKoDKsp<7>{rGL6TF^|7M9t_%fxK1FEPRfY23q;Km72sJ? zC?FM;qNfv*ZgV=BM4}&5XXhDt0E8b#A;u}W1TQ#CS7u@+WN>y`0SGAX(_Yu4)ndB8>S}@9vutY7O!G~98>As6cj)IZ`tk;PO?y-Y~ zKTY2P<6?7APe{4$n#(vI;-lLZ{CL=XJnDD*k*L(ZIyYWI48jg_Fm zZ1)2G)DjkA${kb-~wpc=jHdIP;T&odn`MD*_c=uR{)1_tQsJPw1JxNR)ZyEmSs ze}*1NG~G(w?*LQyn`BM~!8iVi82NJ*i;znywY<|p02 zSCT{Dt3FNdu8Q=F+egw+W#FYHvvMO53r;pvKUNiaU4Rgcg1{2OQc@uTl|bv6mo@Kq2^W{s4I({C1ECBTYwQZBd2}02ya{ z2YT3?9>6F;R