From d4ae5f3909fa8f372ff70d11236cb38e4bd43732 Mon Sep 17 00:00:00 2001 From: Guy Perfect Date: Thu, 10 Oct 2024 18:35:16 -0500 Subject: [PATCH] 2024 reboot --- core/bus.c | 204 ++-- core/cpu.c | 2128 +++++++++-------------------------- core/vb.c | 425 ++++--- core/vb.h | 213 +--- license.txt | 2 +- makefile | 2 +- web/App.js | 691 ------------ web/Bundle.java | 203 ---- web/_boot.js | 310 ----- web/core/AudioThread.js | 88 -- web/core/Core.js | 292 ----- web/core/CoreThread.js | 338 ------ web/core/Disassembler.js | 546 --------- web/core/wasm.c | 79 -- web/debugger/CPU.js | 1439 ----------------------- web/debugger/Debugger.js | 104 -- web/debugger/ISX.js | 177 --- web/debugger/Memory.js | 574 ---------- web/locale/en-US.json | 77 -- web/template.html | 1 - web/theme/check.svg | 6 - web/theme/check2.svg | 4 - web/theme/close.svg | 6 - web/theme/collapse.svg | 6 - web/theme/dark.css | 28 - web/theme/expand.svg | 7 - web/theme/expand2.svg | 6 - web/theme/inconsolata.woff2 | Bin 40712 -> 0 bytes web/theme/kiosk.css | 554 --------- web/theme/light.css | 28 - web/theme/radio.svg | 6 - web/theme/roboto.woff2 | Bin 66064 -> 0 bytes web/theme/scroll.svg | 6 - web/theme/vbemu.css | 194 ---- web/theme/virtual.css | 63 -- web/toolkit/App.js | 249 ---- web/toolkit/Button.js | 119 -- web/toolkit/Checkbox.js | 157 --- web/toolkit/Component.js | 473 -------- web/toolkit/Desktop.js | 86 -- web/toolkit/DropDown.js | 154 --- web/toolkit/Label.js | 46 - web/toolkit/Menu.js | 92 -- web/toolkit/MenuBar.js | 106 -- web/toolkit/MenuItem.js | 455 -------- web/toolkit/Radio.js | 113 -- web/toolkit/RadioGroup.js | 109 -- web/toolkit/ScrollBar.js | 380 ------- web/toolkit/ScrollPane.js | 317 ------ web/toolkit/SplitPane.js | 363 ------ web/toolkit/TextBox.js | 67 -- web/toolkit/Toolkit.js | 51 - web/toolkit/Window.js | 479 -------- 53 files changed, 931 insertions(+), 11692 deletions(-) delete mode 100644 web/App.js delete mode 100644 web/Bundle.java delete mode 100644 web/_boot.js delete mode 100644 web/core/AudioThread.js delete mode 100644 web/core/Core.js delete mode 100644 web/core/CoreThread.js delete mode 100644 web/core/Disassembler.js delete mode 100644 web/core/wasm.c delete mode 100644 web/debugger/CPU.js delete mode 100644 web/debugger/Debugger.js delete mode 100644 web/debugger/ISX.js delete mode 100644 web/debugger/Memory.js delete mode 100644 web/locale/en-US.json delete mode 100644 web/template.html delete mode 100644 web/theme/check.svg delete mode 100644 web/theme/check2.svg delete mode 100644 web/theme/close.svg delete mode 100644 web/theme/collapse.svg delete mode 100644 web/theme/dark.css delete mode 100644 web/theme/expand.svg delete mode 100644 web/theme/expand2.svg delete mode 100644 web/theme/inconsolata.woff2 delete mode 100644 web/theme/kiosk.css delete mode 100644 web/theme/light.css delete mode 100644 web/theme/radio.svg delete mode 100644 web/theme/roboto.woff2 delete mode 100644 web/theme/scroll.svg delete mode 100644 web/theme/vbemu.css delete mode 100644 web/theme/virtual.css delete mode 100644 web/toolkit/App.js delete mode 100644 web/toolkit/Button.js delete mode 100644 web/toolkit/Checkbox.js delete mode 100644 web/toolkit/Component.js delete mode 100644 web/toolkit/Desktop.js delete mode 100644 web/toolkit/DropDown.js delete mode 100644 web/toolkit/Label.js delete mode 100644 web/toolkit/Menu.js delete mode 100644 web/toolkit/MenuBar.js delete mode 100644 web/toolkit/MenuItem.js delete mode 100644 web/toolkit/Radio.js delete mode 100644 web/toolkit/RadioGroup.js delete mode 100644 web/toolkit/ScrollBar.js delete mode 100644 web/toolkit/ScrollPane.js delete mode 100644 web/toolkit/SplitPane.js delete mode 100644 web/toolkit/TextBox.js delete mode 100644 web/toolkit/Toolkit.js delete mode 100644 web/toolkit/Window.js diff --git a/core/bus.c b/core/bus.c index 59fe586..6ee9655 100644 --- a/core/bus.c +++ b/core/bus.c @@ -3,104 +3,73 @@ -/***************************** Utility Functions *****************************/ +/********************************* Constants *********************************/ + +/* Memory access address masks by data type */ +static const uint32_t TYPE_MASKS[] = { + 0x07FFFFFF, /* S8 */ + 0x07FFFFFF, /* U8 */ + 0x07FFFFFE, /* S16 */ + 0x07FFFFFE, /* U16 */ + 0x07FFFFFC /* S32 */ +}; + + + +/*************************** Sub-Module Functions ****************************/ /* Read a typed value from a buffer in host memory */ -static int32_t busReadBuffer( - uint8_t *buffer, uint32_t size, uint32_t offset, int type) { +static int32_t busReadBuffer(uint8_t *data, int type) { - /* There is no data */ - if (buffer == NULL) - return 0; - - /* Mirror the buffer across its address range */ - offset &= size - 1; - - /* Processing by type */ + /* Processing by data type */ switch (type) { /* Generic implementation */ #ifndef VB_LITTLE_ENDIAN - case VB_S8 : - return (int8_t) buffer[offset]; - case VB_U8: - return buffer[offset]; - case VB_S16: - return (int32_t) (int8_t) - buffer[offset + 1] << 8 | buffer[offset]; - case VB_U16: - offset &= 0xFFFFFFFE; - return (int32_t) (int8_t) - buffer[offset + 1] << 8 | buffer[offset]; - case VB_S32: - offset &= 0xFFFFFFFC; - return - (int32_t) buffer[offset + 3] << 24 | - (int32_t) buffer[offset + 2] << 16 | - (int32_t) buffer[offset + 1] << 8 | - buffer[offset ] - ; + case VB_S8 : return ((int8_t *)data)[0]; + case VB_U8 : return data [0]; + case VB_S16: return (int32_t) ((int8_t *)data)[1] << 8 | data[0]; + case VB_U16: return (int32_t) data [1] << 8 | data[0]; + case VB_S32: return + (int32_t) data[3] << 24 | (int32_t) data[2] << 16 | + (int32_t) data[1] << 8 | data[0]; - /* Little-endian implementation */ + /* Little-endian host */ #else - case VB_S8 : return *(int8_t *)&buffer[offset ]; - case VB_U8 : return buffer[offset ]; - case VB_S16: return *(int16_t *)&buffer[offset & 0xFFFFFFFE]; - case VB_U16: return *(uint16_t *)&buffer[offset & 0xFFFFFFFE]; - case VB_S32: return *(int32_t *)&buffer[offset & 0xFFFFFFFC]; + case VB_S8 : return *(int8_t *) data; + case VB_U8 : return * data; + case VB_S16: return *(int16_t *) data; + case VB_U16: return *(uint16_t *) data; + case VB_S32: return *(int32_t *) data; #endif } - /* Invalid type */ - return 0; + return 0; /* Unreachable */ } /* Write a typed value to a buffer in host memory */ -static void busWriteBuffer( - uint8_t *buffer, uint32_t size, uint32_t offset, int type, int32_t value) { +static void busWriteBuffer(uint8_t *data, int type, int32_t value) { - /* There is no data */ - if (buffer == NULL) - return; - - /* Mirror the buffer across its address range */ - offset &= size - 1; - - /* Processing by type */ + /* Processing by data type */ switch (type) { /* Generic implementation */ #ifndef VB_LITTLE_ENDIAN - case VB_S32: - offset &= 0xFFFFFFFC; - buffer[offset ] = value; - buffer[offset + 1] = value >> 8; - buffer[offset + 2] = value >> 16; - buffer[offset + 3] = value >> 24; - break; - case VB_S16: - case VB_U16: - offset &= 0xFFFFFFFE; - buffer[offset ] = value; - buffer[offset + 1] = value >> 8; - break; - case VB_S8 : - case VB_U8 : - buffer[offset ] = value; + case VB_S32: data[3] = value >> 24; + data[2] = value >> 16; /* Fallthrough */ + case VB_S16: /* Fallthrough */ + case VB_U16: data[1] = value >> 8; /* Fallthrough */ + case VB_S8 : /* Fallthrough */ + case VB_U8 : data[0] = value; - /* Little-endian implementation */ + /* Little-endian host */ #else - case VB_S8 : - case VB_U8 : - buffer[offset ] = value; - break; - case VB_S16: - case VB_U16: - *(int16_t *)&buffer[offset & 0xFFFFFFFE] = value; - break; - case VB_S32: - *(int32_t *)&buffer[offset & 0xFFFFFFFC] = value; + case VB_S8 : /* Fallthrough */ + case VB_U8 : * data = value; return; + case VB_S16: /* Fallthrough */ + case VB_U16: *(uint16_t *) data = value; return; + case VB_S32: *(int32_t *) data = value; return; #endif } @@ -109,54 +78,79 @@ static void busWriteBuffer( -/***************************** Module Functions ******************************/ +/***************************** Library Functions *****************************/ /* Read a typed value from the simulation bus */ -static int32_t busRead(VB *sim, uint32_t address, int type) { +static void busRead(VB *sim, uint32_t address, int type, int32_t *value) { + + /* Working variables */ + address &= TYPE_MASKS[type]; + *value = 0; + + /* Process by address range */ + switch (address >> 24) { + case 0: break; /* VIP */ + case 1: break; /* VSU */ + case 2: break; /* Misc. I/O */ + case 3: break; /* Unmapped */ + case 4: break; /* Game Pak expansion */ + + case 5: /* WRAM */ + *value = busReadBuffer(&sim->wram[address & 0x0000FFFF], type); + break; + + case 6: /* Game Pak RAM */ + if (sim->cart.ram != NULL) { + *value = busReadBuffer( + &sim->cart.ram[address & sim->cart.ramMask], type); + } + break; + + case 7: /* Game Pak ROM */ + if (sim->cart.rom != NULL) { + *value = busReadBuffer( + &sim->cart.rom[address & sim->cart.romMask], type); + } + break; - /* Processing by address region */ - switch (address >> 24 & 7) { - case 0: return 0; /* VIP */ - case 1: return 0; /* VSU */ - case 2: return 0; /* Misc. hardware */ - case 3: return 0; /* Unmapped */ - case 4: return 0; /* Game pak expansion */ - case 5: return /* WRAM */ - busReadBuffer(sim->wram , 0x10000 , address, type); - case 6: return /* Game pak RAM */ - busReadBuffer(sim->cart.ram, sim->cart.ramSize, address, type); - case 7: return /* Game pak ROM */ - busReadBuffer(sim->cart.rom, sim->cart.romSize, address, type); } - /* Unreachable */ - return 0; } /* Write a typed value to the simulation bus */ static void busWrite(VB*sim,uint32_t address,int type,int32_t value,int debug){ - (void) debug; - /* Processing by address region */ - switch (address >> 24 & 7) { + /* Working variables */ + address &= TYPE_MASKS[type]; + + /* Process by address range */ + switch (address >> 24) { case 0: break; /* VIP */ case 1: break; /* VSU */ - case 2: break; /* Misc. hardware */ + case 2: break; /* Misc. I/O */ case 3: break; /* Unmapped */ - case 4: break; /* Game pak expansion */ - case 5: /* WRAM */ - busWriteBuffer(sim->wram ,0x10000 ,address,type,value); + case 4: break; /* Game Pak expansion */ + + case 5: /* WRAM */ + busWriteBuffer(&sim->wram[address & 0x0000FFFF], type, value); break; - case 6: /* Game pak RAM */ - busWriteBuffer(sim->cart.ram,sim->cart.ramSize,address,type,value); + + case 6: /* Game Pak RAM */ + if (sim->cart.ram != NULL) { + busWriteBuffer( + &sim->cart.ram[address & sim->cart.ramMask], type, value); + } break; - case 7: /* Game pak ROM */ - busWriteBuffer(sim->cart.rom,sim->cart.romSize,address,type,value); + + case 7: /* Game Pak ROM */ + if (debug && sim->cart.rom != NULL) { + busWriteBuffer( + &sim->cart.rom[address & sim->cart.romMask], type, value); + } break; + } } - - #endif /* VBAPI */ diff --git a/core/cpu.c b/core/cpu.c index 48135c9..375dbb5 100644 --- a/core/cpu.c +++ b/core/cpu.c @@ -5,98 +5,527 @@ /********************************* Constants *********************************/ -/* Pipeline stages */ -#define CPU_FETCH 0 -#define CPU_EXECUTE_A 1 -#define CPU_EXECUTE_B 2 -#define CPU_HALT 3 -#define CPU_FATAL 4 +/* Operation IDs */ +#define CPU_HALTING 0 +#define CPU_FATAL 1 +#define CPU_FETCH 2 +#define CPU_ILLEGAL 3 +#define CPU_BITSTRING 4 +#define CPU_FLOATENDO 5 +#define CPU_DOWN 6 +#define CPU_BITWISE 7 +#define CPU_UP 8 +#define CPU_ADD 9 +#define CPU_ADDF_S 10 +#define CPU_ADDI 11 +#define CPU_AND 12 +#define CPU_ANDI 13 +#define CPU_BCOND 14 +#define CPU_CAXI 15 +#define CPU_CLI 16 +#define CPU_CMP 17 +#define CPU_CMPF_S 18 +#define CPU_CVT_SW 19 +#define CPU_CVT_WS 20 +#define CPU_DIV 21 +#define CPU_DIVF_S 22 +#define CPU_DIVU 23 +#define CPU_HALT 24 +#define CPU_IN_B 25 +#define CPU_IN_H 26 +#define CPU_IN_W 27 +#define CPU_JAL 28 +#define CPU_JMP 29 +#define CPU_JR 30 +#define CPU_LD_B 31 +#define CPU_LD_H 32 +#define CPU_LD_W 33 +#define CPU_LDSR 34 +#define CPU_MOV 35 +#define CPU_MOVEA 36 +#define CPU_MOVHI 37 +#define CPU_MPYHW 38 +#define CPU_MUL 39 +#define CPU_MULF_S 40 +#define CPU_MULU 41 +#define CPU_NOT 42 +#define CPU_OR 43 +#define CPU_ORI 44 +#define CPU_OUT_B 45 +#define CPU_OUT_H 46 +#define CPU_OUT_W 47 +#define CPU_RETI 48 +#define CPU_REV 49 +#define CPU_SAR 50 +#define CPU_SEI 51 +#define CPU_SETF 52 +#define CPU_SHL 53 +#define CPU_SHR 54 +#define CPU_ST_B 55 +#define CPU_ST_H 56 +#define CPU_ST_W 57 +#define CPU_STSR 58 +#define CPU_SUB 59 +#define CPU_SUBF_S 60 +#define CPU_TRAP 61 +#define CPU_TRNC_SW 62 +#define CPU_XB 63 +#define CPU_XH 64 +#define CPU_XOR 65 +#define CPU_XORI 66 + +/* Abstract operand types */ +#define CPU_IMP(x) -x-1 +#define CPU_DISP9 1 +#define CPU_DISP26 2 +#define CPU_IMM16S 3 +#define CPU_IMM16U 4 +#define CPU_IMM5S 5 +#define CPU_IMM5U 6 +#define CPU_MEM 7 +#define CPU_REG1 8 +#define CPU_REG2 9 + +/* Functional operand types */ +#define CPU_LITERAL 0 +#define CPU_MEMORY 1 +#define CPU_REGISTER 2 + +/* Bit string operations */ +#define CPU_AND_BS 0 +#define CPU_ANDN_BS 1 +#define CPU_MOV_BS 2 +#define CPU_NOT_BS 3 +#define CPU_OR_BS 4 +#define CPU_ORN_BS 5 +#define CPU_XOR_BS 6 +#define CPU_XORN_BS 7 + +/* Instruction code lengths by opcode */ +static const uint8_t INST_LENGTHS[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 +}; -/*********************************** Types ***********************************/ +/********************************** Macros ***********************************/ -/* Handler types */ -typedef int (*ExecuteAProc )(VB *); -typedef void (*ExecuteBProc )(VB *); -typedef void (*OperationProc)(VB *, int32_t *, int32_t); -typedef int32_t (*OperandProc )(VB *); +/* Master clocks per CPU cycles */ +#define cpuClocks(x) x -/* Opcode descriptor */ -typedef struct { - ExecuteAProc executeA; /* Execute handler (no state change) */ - ExecuteBProc executeB; /* Execute handler (update state) */ - OperationProc operation; /* Operation handler */ - OperandProc operand; /* Operand handler */ - uint8_t size; /* Total size in halfwords */ - uint8_t aux; /* Number of clocks or access data type */ -} OpDef; - -/* Floating-point auxiliary memory */ -typedef struct { - float f32; /* 32-bit result */ - double f64; /* 64-bit result */ -} FloatAux; +/* Shorthand */ +#define auxData sim->cpu.aux.data -/***************************** Utility Functions *****************************/ +/******************************** Lookup Data ********************************/ -/* Check for an interrupt exception condition */ -static int cpuCheckIRQs(VB *sim) { - int x; /* Iterator */ +/* Opdefs by opcode */ +static const uint8_t OPDEFS[] = { + CPU_MOV , CPU_ADD , CPU_SUB , CPU_CMP , /* 000000 */ + CPU_SHL , CPU_SHR , CPU_JMP , CPU_SAR , + CPU_MUL , CPU_DIV , CPU_MULU , CPU_DIVU , + CPU_OR , CPU_AND , CPU_XOR , CPU_NOT , + CPU_MOV , CPU_ADD , CPU_SETF , CPU_CMP , /* 010000 */ + CPU_SHL , CPU_SHR , CPU_CLI , CPU_SAR , + CPU_TRAP , CPU_RETI , CPU_HALT , CPU_ILLEGAL , + CPU_LDSR , CPU_STSR , CPU_SEI , CPU_BITSTRING, + CPU_BCOND, CPU_BCOND, CPU_BCOND , CPU_BCOND , /* 100000 */ + CPU_BCOND, CPU_BCOND, CPU_BCOND , CPU_BCOND , + CPU_MOVEA, CPU_ADDI , CPU_JR , CPU_JAL , + CPU_ORI , CPU_ANDI , CPU_XORI , CPU_MOVHI , + CPU_LD_B , CPU_LD_H , CPU_ILLEGAL , CPU_LD_W , /* 110000 */ + CPU_ST_B , CPU_ST_H , CPU_ILLEGAL , CPU_ST_W , + CPU_IN_B , CPU_IN_H , CPU_CAXI , CPU_IN_W , + CPU_OUT_B, CPU_OUT_H, CPU_FLOATENDO, CPU_OUT_W +}; - /* Interrupts are masked */ - if (sim->cpu.psw.id || sim->cpu.psw.ep || sim->cpu.psw.np) - return 0; +/* Opdefs by bit string sub-opcode */ +static const uint8_t OPDEFS_BITSTRING[] = { + CPU_UP , CPU_DOWN , CPU_UP , CPU_DOWN , /* 00000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, /* 01000 */ + CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, CPU_BITWISE, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 10000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 11000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL +}; + +/* Opdefs by floating-point/Nintendo sub-opcodes */ +static const uint8_t OPDEFS_FLOATENDO[] = { + CPU_CMPF_S , CPU_ILLEGAL, CPU_CVT_WS , CPU_CVT_SW , /* 000000 */ + CPU_ADDF_S , CPU_SUBF_S , CPU_MULF_S , CPU_DIVF_S , + CPU_XB , CPU_XH , CPU_REV , CPU_TRNC_SW, + CPU_MPYHW , CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 010000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 100000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, /* 110000 */ + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, + CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL, CPU_ILLEGAL +}; + + + +/***************************** Callback Handlers *****************************/ + +/* Prepare to execute an instruction */ +#ifndef VB_DIRECT_EXECUTE + #define VB_ON_EXECUTE sim->onExecute +#else + extern int vbxOnExecute(VB *, uint32_t, const uint16_t *, int); + #define VB_ON_EXECUTE vbxOnExecute +#endif +static int cpuExecute(VB *sim) { + return + sim->onExecute != NULL && + VB_ON_EXECUTE(sim, sim->cpu.pc, sim->cpu.code, sim->cpu.length) + ; +} +#undef VB_ON_EXECUTE + +/* Read a value from the memory bus */ +#ifndef VB_DIRECT_READ + #define VB_ON_READ sim->onRead +#else + extern int vbxOnRead(VB *, uint32_t, int, int32_t *, uint32_t *); + #define VB_ON_READ vbxOnRead +#endif +static int cpuRead(VB *sim, uint32_t address, int type, int32_t *value) { + uint32_t cycles = 4; /* TODO: Research this */ + + /* Retrieve the value from the simulation state directly */ + busRead(sim, address, type, value); + + /* Invoke the callback if available */ + if ( + sim->onRead != NULL && + VB_ON_READ(sim, address, type, value, &cycles) + ) return 1; + + /* Update state */ + sim->cpu.clocks += cpuClocks(cycles); + return 0; +} +#undef VB_ON_READ + +/* Fetch a code unit from the memory bus */ +#ifndef VB_DIRECT_FETCH + #define VB_ON_FETCH sim->onFetch +#else + extern int vbxOnFetch(VB *, int, uint32_t, int32_t *, uint32_t *); + #define VB_ON_FETCH vbxOnFetch +#endif +static int cpuReadFetch(VB *sim, int fetch, uint32_t address, int32_t *value) { + uint32_t cycles = 0; + + /* Retrieve the value from the simulation state directly */ + busRead(sim, address, VB_U16, value); + + /* Invoke the callback if available */ + if ( + sim->onFetch != NULL && + VB_ON_FETCH(sim, fetch, address, value, &cycles) + ) return 1; + + /* Update state */ + sim->cpu.clocks += cpuClocks(cycles); + return 0; +} +#undef VB_ON_FETCH + +/* Write a value to the memory bus */ +#ifndef VB_DIRECT_WRITE + #define VB_ON_WRITE sim->onWrite +#else + extern int vbxOnWrite(VB *, uint32_t, int, int32_t *, uint32_t *, int *); + #define VB_ON_WRITE vbxOnWrite +#endif +static int cpuWrite(VB *sim, uint32_t address, int type, int32_t value) { + int cancel = 0; + uint32_t cycles = 3; /* TODO: Research this */ + + /* Invoke the callback if available */ + if ( + sim->onWrite != NULL && + VB_ON_WRITE(sim, address, type, &value, &cycles, &cancel) + ) return 1; + + /* Write the value to the simulation state directly */ + if (!cancel) + busWrite(sim, address, type, value, 0); + + /* Update state */ + sim->cpu.clocks += cpuClocks(cycles); + return 0; +} +#undef VB_ON_WRITE + + + +/****************************** Pipeline Stages ******************************/ + +/* Fetch the code bits for the next instruction */ +static int cpuFetch(VB *sim) { + int32_t value; + switch (sim->cpu.step) { + + case 0: + sim->cpu.pc = sim->cpu.nextPC; + /* Fallthrough */ + + case 1: + + /* Retrieve the first code unit */ + if (cpuReadFetch(sim, 0, sim->cpu.pc, &value)) { + sim->cpu.step = 1; + return 1; + } + + /* Update state */ + sim->cpu.code[0] = value; + sim->cpu.length = INST_LENGTHS[value >> 10 & 63]; + sim->cpu.step = 3 - sim->cpu.length; + + /* Wait any clocks taken */ + if (sim->cpu.clocks != 0) + return 0; + + /* Skip fetching a second code unit */ + if (sim->cpu.length == 1) + goto Step3; + + /* Fallthrough */ + case 2: + + /* Retrieve the second code unit */ + if (cpuReadFetch(sim, 1, sim->cpu.pc + 2, &value)) + return 1; + + /* Update state */ + sim->cpu.code[1] = value; + sim->cpu.step = 3; + + /* Wait any clocks taken */ + if (sim->cpu.clocks != 0) + return 0; + + /* Fallthrough */ + case 3: Step3: + + /* Prepare to execute the instruction */ + if (cpuExecute(sim)) + return 1; + + /* Select operation definition */ + sim->cpu.operation = OPDEFS[sim->cpu.code[0] >> 10]; + switch (sim->cpu.operation) { + case CPU_BITSTRING: + sim->cpu.operation = + OPDEFS_BITSTRING[sim->cpu.code[0] & 31]; + break; + case CPU_FLOATENDO: + sim->cpu.operation = + OPDEFS_FLOATENDO[sim->cpu.code[1] >> 10]; + } + + /* Update state */ + sim->cpu.step = 0; - /* Check for interrupt requests */ - for (x = 4; x >= sim->cpu.psw.i; x--) { - if (!sim->cpu.irq[x]) - continue; - sim->cpu.exception = 0xFE00 | x << 4; - return 1; } - - /* No interrupt */ return 0; } -/* Test a condition */ -static int cpuCondition(VB *sim, int index) { - switch (index) { - case 0: return sim->cpu.psw.ov; /*V */ - case 1: return sim->cpu.psw.cy; /*C,L*/ - case 2: return sim->cpu.psw.z; /*E,Z*/ - case 3: return sim->cpu.psw.cy | sim->cpu.psw.z; /*NH */ - case 4: return sim->cpu.psw.s; /*N */ - case 5: return 1; /*T */ - case 6: return sim->cpu.psw.ov ^ sim->cpu.psw.s; /*LT */ - case 7: return (sim->cpu.psw.ov^sim->cpu.psw.s)|sim->cpu.psw.z;/*LE */ + + +/**************************** Instruction Helpers ****************************/ + +/* Parse the immediate 16-bit sign-extended value */ +static int32_t cpuGetImm16S(VB *sim) { + return SignExtend(sim->cpu.code[1], 16); +} + +/* Parse the immediate 16-bit zero-filled value */ +static int32_t cpuGetImm16U(VB *sim) { + return sim->cpu.code[1]; +} + +/* Resolve the operand value for reg1 */ +static int32_t cpuGetReg1(VB *sim) { + return sim->cpu.program[sim->cpu.code[0] & 31]; +} + +/* Supply an operand value for reg2 */ +static void cpuSetReg2(VB *sim, int32_t value) { + int reg2 = sim->cpu.code[0] >> 5 & 31; + if (reg2 != 0) + sim->cpu.program[reg2] = value; +} + +/* Memory access instruction */ +static int cpuLD_IN(VB *sim, int type) { + switch (sim->cpu.step) { + + case 0: + auxData.address = cpuGetReg1(sim) + cpuGetImm16S(sim); + /* Fallthrough */ + + case 1: + + /* Read the value from memory */ + if (cpuRead(sim, auxData.address, type, &auxData.value)) { + sim->cpu.step = 1; + return 1; + } + + /* Update state */ + sim->cpu.clocks += cpuClocks(1); + + /* Wait for clocks taken */ + sim->cpu.step = 2; + return 0; + + case 2: + cpuSetReg2(sim, auxData.value); + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC += sim->cpu.pc + 4; + sim->cpu.step = 0; } - return !cpuCondition(sim, index - 8); + return 0; +} + + + +/************************** Instruction Operations ***************************/ + +/* IN.B */ +static int cpuIN_B(VB *sim) { + return cpuLD_IN(sim, VB_U8); +} + +/* IN.H */ +static int cpuIN_H(VB *sim) { + return cpuLD_IN(sim, VB_U16); +} + +/* IN.W */ +static int cpuIN_W(VB *sim) { + return cpuLD_IN(sim, VB_S32); +} + +/* JMP */ +static int cpuJMP(VB *sim) { + sim->cpu.nextPC = cpuGetReg1(sim) & 0xFFFFFFFE; + sim->cpu.clocks += cpuClocks(3); + sim->cpu.operation = CPU_FETCH; + /* TODO: Clear prefetch buffer */ + return 0; +} + +/* LD.B */ +static int cpuLD_B(VB *sim) { + return cpuLD_IN(sim, VB_S8); +} + +/* LD.H */ +static int cpuLD_H(VB *sim) { + return cpuLD_IN(sim, VB_S16); +} + +/* LD.W */ +static int cpuLD_W(VB *sim) { + return cpuLD_IN(sim, VB_S32); +} + +/* MOVEA */ +static int cpuMOVEA(VB *sim) { + cpuSetReg2(sim, cpuGetReg1(sim) + cpuGetImm16S(sim)); + sim->cpu.clocks += cpuClocks(1); + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC = sim->cpu.pc + 4; + return 0; +} + +/* MOVHI */ +static int cpuMOVHI(VB *sim) { + cpuSetReg2(sim, cpuGetReg1(sim) + (cpuGetImm16U(sim) << 16)); + sim->cpu.clocks += cpuClocks(1); + sim->cpu.operation = CPU_FETCH; + sim->cpu.nextPC = sim->cpu.pc + 4; + return 0; +} + + + +/***************************** Library Functions *****************************/ + +/* Process component */ +static int cpuEmulate(VB *sim, uint32_t clocks) { + int brk; + + /* Process until there are clocks to wait */ + for (;;) { + + /* The next event is after the time remaining */ + if (sim->cpu.clocks > clocks) { + sim->cpu.clocks -= clocks; + return 0; + } + + /* Advance forward the CPU's number of clocks */ + if (sim->cpu.clocks != 0) { + clocks -= sim->cpu.clocks; + sim->cpu.clocks = 0; + } + + /* Processing by operation ID */ + switch (sim->cpu.operation) { + case CPU_FETCH: brk = cpuFetch(sim); break; + + case CPU_IN_B : brk = cpuIN_B (sim); break; + case CPU_IN_H : brk = cpuIN_H (sim); break; + case CPU_IN_W : brk = cpuIN_W (sim); break; + case CPU_JMP : brk = cpuJMP (sim); break; + case CPU_LD_B : brk = cpuLD_B (sim); break; + case CPU_LD_H : brk = cpuLD_H (sim); break; + case CPU_LD_W : brk = cpuLD_W (sim); break; + case CPU_MOVEA: brk = cpuMOVEA(sim); break; + case CPU_MOVHI: brk = cpuMOVHI(sim); break; + + default: return -1; /* TODO: Temporary for debugging */ + } + + /* A callback requested a break */ + if (brk) + return 1; + } + + return 0; } /* Retrieve the value of a system register */ -static uint32_t cpuGetSystemRegister(VB *sim, int id) { - switch (id) { +static uint32_t cpuGetSystemRegister(VB *sim, int index) { + switch (index) { case VB_ADTRE: return sim->cpu.adtre; + case VB_CHCW : return sim->cpu.chcw.ice << 1; + case VB_ECR : return + (uint32_t) sim->cpu.ecr.fecc << 16 | + (uint32_t) sim->cpu.ecr.eicc; case VB_EIPC : return sim->cpu.eipc; case VB_EIPSW: return sim->cpu.eipsw; case VB_FEPC : return sim->cpu.fepc; case VB_FEPSW: return sim->cpu.fepsw; case VB_PIR : return 0x00005346; - case VB_TKCW : return 0x000000E0; - case 29 : return sim->cpu.sr29; - case 30 : return 0x00000004; - case 31 : return sim->cpu.sr31; - case VB_CHCW : return - (uint32_t) sim->cpu.chcw.ice << 1 - ; - case VB_ECR : return - (uint32_t) sim->cpu.ecr.fecc << 16 | - (uint32_t) sim->cpu.ecr.eicc - ; case VB_PSW : return (uint32_t) sim->cpu.psw.i << 16 | (uint32_t) sim->cpu.psw.np << 15 | @@ -112,1538 +541,65 @@ static uint32_t cpuGetSystemRegister(VB *sim, int id) { (uint32_t) sim->cpu.psw.cy << 3 | (uint32_t) sim->cpu.psw.ov << 2 | (uint32_t) sim->cpu.psw.s << 1 | - (uint32_t) sim->cpu.psw.z - ; + (uint32_t) sim->cpu.psw.z; + case VB_TKCW : return 0x000000E0; + case 29 : return sim->cpu.sr29; + case 30 : return 0x00000004; + case 31 : return sim->cpu.sr31; } - return 0; /* Invalid ID */ + return 0x00000000; /* All others */ } -/* Read a memory value from the bus */ -static int cpuRead(VB *sim, uint32_t address, int type, int32_t *value) { - VBAccess access; /* Bus access descriptor */ - - /* Retrieve the value from the simulation state */ - access.clocks = 0; /* TODO: Needs research */ - access.value = busRead(sim, address, type); - - /* Call the breakpoint handler */ - if (sim->onRead != NULL) { - access.address = address; - access.type = type; - if (sim->onRead(sim, &access)) - return 1; - } - - /* Post-processing */ - sim->cpu.clocks += access.clocks; - *value = access.value; - return 0; -} - -/* Fetch an instruction code unit from the bus */ -static int cpuReadFetch(VB *sim) { - VBAccess access; /* Bus access descriptor */ - - /* Retrieve the value from the simulation state */ - access.address = sim->cpu.pc + (sim->cpu.step << 1); - access.clocks = 0; /* TODO: Prefetch makes this tricky */ - access.value = busRead(sim, access.address, VB_U16); - - /* Call the breakpoint handler */ - if (sim->onFetch != NULL) { - access.type = VB_U16; - if (sim->onFetch(sim, sim->cpu.step, &access)) - return 1; - } - - /* Post-processing */ - sim->cpu.clocks += access.clocks; - sim->cpu.inst.code[sim->cpu.step++] = access.value; - return 0; -} - -/* Detect a floating-point reserved operand */ -#define cpuFRO(x) ( \ - ( \ - ((x) & 0x007FFFFF) != 0x00000000 && /* Is not zero */ \ - ((x) & 0x7F800000) == 0x00000000 /* Is denormal */ \ - ) || ((x) & 0x7F800000) == 0x7F800000 /* Is indefinite/NaN */ \ -) - -/* Specify a value for a system register */ -static uint32_t cpuSetSystemRegister(VB *sim,int id,uint32_t value,int debug) { - switch (id) { +/* Specify a new value for a system register */ +static uint32_t cpuSetSystemRegister(VB*sim,int index,uint32_t value,int debug){ + switch (index) { case VB_ADTRE: return sim->cpu.adtre = value & 0xFFFFFFFE; + case VB_CHCW : + /* TODO: Configure instruction cache */ + sim->cpu.chcw.ice = value >> 1 & 1; + return value & 0x00000002; + case VB_ECR : + if (debug) { + sim->cpu.ecr.fecc = value >> 16; + sim->cpu.ecr.eicc = value; + } + return + (uint32_t) sim->cpu.ecr.fecc << 16 | + (uint32_t) sim->cpu.ecr.eicc; case VB_EIPC : return sim->cpu.eipc = value & 0xFFFFFFFE; case VB_EIPSW: return sim->cpu.eipsw = value & 0x000FF3FF; case VB_FEPC : return sim->cpu.fepc = value & 0xFFFFFFFE; case VB_FEPSW: return sim->cpu.fepsw = value & 0x000FF3FF; case VB_PIR : return 0x00005346; - case VB_TKCW : return 0x000000E0; - case 29 : return sim->cpu.sr29 = value; - case 30 : return 0x00000004; - case 31 : return - sim->cpu.sr31 = debug || value < (uint32_t) 0x80000000 ? - value : (uint32_t) -(int32_t)value; - case VB_CHCW: - /* TODO: Manage cache functions */ - sim->cpu.chcw.ice = value >> 1 & 1; - return value & 0x00000002; - case VB_ECR: - if (debug) { - sim->cpu.ecr.fecc = value >> 16 & 0xFFFF; - sim->cpu.ecr.eicc = value & 0xFFFF; - } - return (uint32_t) sim->cpu.ecr.fecc << 16 | sim->cpu.ecr.eicc; - case VB_PSW: - sim->cpu.psw.i = value >> 16 & 15; - sim->cpu.psw.np = value >> 15 & 1; - sim->cpu.psw.ep = value >> 14 & 1; - sim->cpu.psw.ae = value >> 13 & 1; - sim->cpu.psw.id = value >> 12 & 1; - sim->cpu.psw.fro = value >> 9 & 1; - sim->cpu.psw.fiv = value >> 8 & 1; - sim->cpu.psw.fzd = value >> 7 & 1; - sim->cpu.psw.fov = value >> 6 & 1; - sim->cpu.psw.fud = value >> 5 & 1; - sim->cpu.psw.fpr = value >> 4 & 1; - sim->cpu.psw.cy = value >> 3 & 1; - sim->cpu.psw.ov = value >> 2 & 1; - sim->cpu.psw.s = value >> 1 & 1; - sim->cpu.psw.z = value & 1; + case VB_PSW : + sim->cpu.psw.i = value >> 16 & 0xF; + sim->cpu.psw.np = value >> 15 & 1; + sim->cpu.psw.ep = value >> 14 & 1; + sim->cpu.psw.ae = value >> 13 & 1; + sim->cpu.psw.id = value >> 12 & 1; + sim->cpu.psw.fro = value >> 9 & 1; + sim->cpu.psw.fiv = value >> 8 & 1; + sim->cpu.psw.fzd = value >> 7 & 1; + sim->cpu.psw.fov = value >> 6 & 1; + sim->cpu.psw.fud = value >> 5 & 1; + sim->cpu.psw.fpr = value >> 4 & 1; + sim->cpu.psw.cy = value >> 3 & 1; + sim->cpu.psw.ov = value >> 2 & 1; + sim->cpu.psw.s = value >> 1 & 1; + sim->cpu.psw.z = value & 1; return value & 0x000FF3FF; + case VB_TKCW : return 0x000000E0; + case 29 : return sim->cpu.sr29 = value; + case 30 : return 0x00000004; + case 31 : return sim->cpu.sr31 = debug ? value : + *(int32_t *) &value < 0 ? (uint32_t) -*(int32_t *) &value : value; } - return 0; /* Invalid ID */ + return 0x00000000; /* All others */ } -/* Prepare to write a memory value to the bus */ -static int cpuWritePre(VB *sim,uint32_t *address,int32_t *type,int32_t *value){ - VBAccess access; /* Bus access descriptor */ - - /* Determine how many clocks the access will take */ - access.clocks = 0; /* TODO: Needs research */ - - /* Call the breakpoint handler */ - if (sim->onWrite != NULL) { - - /* Query the application */ - access.address = *address; - access.value = *value; - access.type = *type; - if (sim->onWrite(sim, &access)) - return 1; - - /* Apply changes */ - *address = access.address; - *value = access.value; - if (access.type <= VB_S32) - *type = access.type; - } - - /* Post-processing */ - sim->cpu.clocks += access.clocks; - return 0; -} - - - -/**************************** Execute A Handlers *****************************/ - -/* Standard two-operand instruction */ -static int exaStdTwo(VB *sim) { - OpDef *def = (OpDef *) sim->cpu.inst.def; - sim->cpu.inst.aux[0] = def->operand(sim); - sim->cpu.clocks += def->aux; - return 0; -} - -/* Standard three-operand instruction */ -static int exaStdThree(VB *sim) { - OpDef *def = (OpDef *) sim->cpu.inst.def; - sim->cpu.inst.aux[0] = def->operand(sim); - sim->cpu.inst.aux[1] = sim->cpu.program[sim->cpu.inst.code[0] & 31]; - sim->cpu.clocks += def->aux; - return 0; -} - -/* Bit string bitwise */ -static int exaBitBitwise(VB *sim) { - int32_t bits; /* Working shift amount and bit mask */ - uint32_t length; /* Number of bits remaining in bit string */ - int32_t offDest; /* Bit offset of destination bit string */ - int32_t offSrc; /* Bit offset of source bit string */ - uint32_t result; /* Output wrdvalue */ - uint32_t valDest; /* Destination word value */ - - /* Initial invocation */ - if (sim->cpu.step == 0) - sim->cpu.step = sim->cpu.bitstring + 1; - - /* Read the low-order 32 source bits */ - if (sim->cpu.step == 1) { - if (cpuRead(sim, sim->cpu.program[30], VB_S32, &sim->cpu.inst.aux[0])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 2; - } - - /* Read the high-order 32 source bits */ - if (sim->cpu.step == 2) { - if (cpuRead(sim,sim->cpu.program[30]+4,VB_S32,&sim->cpu.inst.aux[1])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 3; - } - - /* Read the destination bits */ - if (sim->cpu.step == 3) { - if (cpuRead(sim, sim->cpu.program[29], VB_S32, &sim->cpu.inst.aux[2])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 4; - } - - /* Compute the result */ - if (sim->cpu.step == 4) { - length = sim->cpu.program[28]; - offDest = sim->cpu.program[26] & 31; - offSrc = sim->cpu.program[27] & 31; - result = sim->cpu.inst.aux[0]; - valDest = sim->cpu.inst.aux[2]; - bits = offDest - offSrc; - - /* Compose the source value */ - if (bits > 0) - result <<= bits; - else if (bits < 0) { - result >>= -bits; - #ifndef VB_SIGNED_PROPAGATE - result &= ((uint32_t) 1 << (32 + bits)) - 1; - #endif - result |= sim->cpu.inst.aux[1] << (32 + bits); - } - - /* Compose the destination value */ - ((OpDef *) sim->cpu.inst.def) - ->operation(sim, (int32_t *) &result, valDest); - bits = (1 << offDest) - 1; - if (length < 32 && offDest + length < 32) - bits |= (uint32_t) 0xFFFFFFFF << (offDest + length); - sim->cpu.inst.aux[2] = (result & ~bits) | (valDest & bits); - - /* Prepare to write the result */ - sim->cpu.inst.aux[3] = sim->cpu.program[29] & 0xFFFFFFFC; - sim->cpu.inst.aux[4] = VB_S32; - if (cpuWritePre(sim, (uint32_t *) &sim->cpu.inst.aux[3], - &sim->cpu.inst.aux[4], &sim->cpu.inst.aux[2])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - } - - return 0; -} - -/* Bit string search */ -static int exaBitSearch(VB *sim) { - if (cpuRead(sim, sim->cpu.program[30], VB_S32, &sim->cpu.inst.aux[0])) - return 1; - sim->cpu.clocks += 4; /* TODO: Needs research */ - return 0; -} - -/* Branch on condition */ -static int exaBCOND(VB *sim) { - if (cpuCondition(sim, sim->cpu.inst.code[0] >> 9 & 15)) { - sim->cpu.inst.aux[0] = - (sim->cpu.pc + SignExtend(sim->cpu.inst.code[0], 9)) & 0xFFFFFFFE; - sim->cpu.clocks += 3; - } else { - sim->cpu.inst.aux[0] = sim->cpu.pc + sim->cpu.inst.size; - sim->cpu.clocks += 1; - } - return 0; -} - -/* Compare and exchange interlocked */ -static int exaCAXI(VB *sim) { - - /* First invocation */ - if (sim->cpu.step == 0) { - exaStdThree(sim); - sim->cpu.inst.aux[0] += sim->cpu.inst.aux[1]; /* Address */ - sim->cpu.inst.aux[1] = VB_S32; /* Write type */ - sim->cpu.inst.aux[3] = sim->cpu.program[sim->cpu.inst.code[0]>>5&31]; - sim->cpu.step = 1; - } - - /* Read the lock word and determine the exchange value */ - if (sim->cpu.step == 1) { - if (cpuRead(sim, sim->cpu.inst.aux[0], VB_S32, &sim->cpu.inst.aux[4])) - return 1; - sim->cpu.inst.aux[2] = sim->cpu.inst.aux[3] == sim->cpu.inst.aux[4] ? - sim->cpu.program[30] : sim->cpu.inst.aux[4]; - sim->cpu.step = 2; - } - - /* Prepare to write the exchange value */ - if (sim->cpu.step == 2) { - if (cpuWritePre(sim, (uint32_t *) &sim->cpu.inst.aux[0], - &sim->cpu.inst.aux[1], &sim->cpu.inst.aux[2])) - return 1; - } - - return 0; -} - -/* No special action */ -static int exaDefault(VB *sim) { - sim->cpu.clocks += ((OpDef *) sim->cpu.inst.def)->aux; - return 0; -} - -/* Division */ -static int exaDivision(VB *sim) { - exaStdTwo(sim); - if (sim->cpu.inst.aux[0] == 0) { - sim->cpu.clocks = 0; /* exaStdTwo adds clocks */ - sim->cpu.exception = 0xFF80; /* Zero division */ - } - return 0; -} - -/* Exception */ -static int exaException(VB *sim) { - VBException exception; /* Exception descriptor */ - - /* Initial invocation */ - if (sim->cpu.step == 0) { - - /* Call the breakpoint handler */ - if (sim->onException != NULL) { - - /* Query the application */ - exception.address = sim->cpu.inst.aux[0]; - exception.code = sim->cpu.exception; - exception.cancel = 0; - if (sim->onException(sim, &exception)) - return 1; - - /* The application canceled the exception */ - if (exception.cancel) { - sim->cpu.exception = 0; - sim->cpu.stage = CPU_FETCH; - return 0; - } - - /* Apply changes */ - sim->cpu.inst.aux[0] = exception.address; - sim->cpu.exception = exception.code; - } - - /* Fatal exception: stage values for writing */ - if (sim->cpu.psw.np) { - sim->cpu.inst.aux[0] = 0x00000000; - sim->cpu.inst.aux[1] = VB_S32; - sim->cpu.inst.aux[2] = 0xFFFF0000 | sim->cpu.exception; - sim->cpu.inst.aux[3] = 0x00000004; - sim->cpu.inst.aux[4] = VB_S32; - sim->cpu.inst.aux[5] = cpuGetSystemRegister(sim, VB_PSW); - sim->cpu.inst.aux[6] = 0x00000008; - sim->cpu.inst.aux[7] = VB_S32; - sim->cpu.inst.aux[8] = sim->cpu.pc; - sim->cpu.step = 1; - } - - /* Other exception */ - else sim->cpu.step = 10; - } - - /* Prepare to dump fatal exception diagnostic values to memory */ - for (; sim->cpu.step < 10; sim->cpu.step += 3) { - if (cpuWritePre(sim, (uint32_t *) - &sim->cpu.inst.aux[sim->cpu.step - 1], - &sim->cpu.inst.aux[sim->cpu.step ], - &sim->cpu.inst.aux[sim->cpu.step + 1] - )) return 1; - } - - /* Common processing */ - sim->cpu.bitstring = 0; - sim->cpu.clocks += 1; /* TODO: Needs research */ - return 0; -} - -/* One-operand floating-point instruction */ -static int exaFloating1(VB *sim) { - int bits; /* Number of bits to shift */ - int32_t reg1; /* Left operand */ - int32_t result; /* Operation result */ - int32_t subop; /* Sub-opcode */ - - /* Reserved operand */ - reg1 = sim->cpu.program[sim->cpu.inst.code[0] & 31]; - if (cpuFRO(reg1)) { - sim->cpu.fpFlags = 0x00000200; /* FRO */ - sim->cpu.exception = 0xFF60; - return 0; - } - - /* Working variables */ - bits = (reg1 >> 23 & 0xFF) - 150; - result = (reg1 & 0x007FFFFF) | 0x00800000; - subop = sim->cpu.inst.code[1] >> 10 & 63; - - /* Zero */ - if ((reg1 & 0x7FFFFFFF) == 0x00000000) - result = 0; - - /* Minimum negative value */ - else if (bits == 8 && result == 0x00800000 && reg1 < 0) - result = INT32_MIN; - - /* Shifting left */ - else if (bits > 0) { - - /* Invalid operation */ - if (bits > 7) { - sim->cpu.fpFlags = 0x00000100; /* FIV */ - sim->cpu.exception = 0xFF70; - return 0; - } - - /* Compute result */ - result <<= bits; - } - - /* Shifting right */ - else if (bits < 0) { - result = - bits < -24 ? 0 : /* All bits shifted out */ - subop == 0x0B ? result >> -bits : /* Truncate */ - ((result >> (-bits - 1)) + 1) >> 1 /* Round */ - ; - } - - /* Result is negative */ - if (reg1 < 0 && result != INT32_MIN) - result = -result; - - /* Stage updates */ - sim->cpu.fpFlags = result==*(float *)®1 ? 0 : 0x00000010; /* FPR */ - sim->cpu.inst.aux[0] = result; - sim->cpu.inst.aux[1] = subop; - sim->cpu.clocks += ((OpDef *) sim->cpu.inst.def)->aux; - return 0; -} - -/* Two-operand floating-point instruction */ -static int exaFloating2(VB *sim) { - FloatAux *aux; /* Floating-point auxiliary memory */ - int32_t bits; /* Bits of testing value */ - int32_t reg1; /* Right operand */ - int32_t reg2; /* Left operand */ - float test; /* Floating-point testing value */ - OpDef *def = (OpDef *) sim->cpu.inst.def; - - /* Reserved operand */ - reg1 = sim->cpu.program[sim->cpu.inst.code[0] & 31]; - reg2 = sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31]; - if (cpuFRO(reg1) || cpuFRO(reg2)) { - sim->cpu.fpFlags = 0x00000200; /* FRO */ - sim->cpu.exception = 0xFF60; - return 0; - } - - /* Perform the operation */ - def->operation(sim, ®2, reg1); - if (sim->cpu.exception != 0) - return 0; /* Handled in opDIVF_S() */ - aux = (FloatAux *) &sim->cpu.inst.aux; - - /* Overflow */ - bits = 0x7F7FFFFF; /* Maximum value */ - test = *(float *)&bits; - if (aux->f64 > test || aux->f64 < -test) { - sim->cpu.fpFlags = 0x00000040; /* FOV */ - sim->cpu.exception = 0xFF64; - return 0; - } - - /* Process result */ - bits = *(int32_t *)&aux->f32; - sim->cpu.fpFlags = 0; - - /* Zero */ - if ((bits & 0x7FFFFFFF) == 0x00000000) - aux->f32 = bits = 0; - - /* Underflow */ - else if ((bits & 0x7F800000) == 0x00000000) { - sim->cpu.fpFlags = 0x00000020; /* FUD */ - aux->f32 = bits = 0; - } - - /* Precision degradation */ - if (aux->f32 != aux->f64) - sim->cpu.fpFlags |= 0x00000010; /* FPR */ - - /* Other state */ - sim->cpu.inst.aux[0] = bits; - sim->cpu.inst.aux[1] = sim->cpu.inst.code[1] >> 10 & 63; - sim->cpu.clocks += def->aux; - return 0; -} - -/* Illegal opcode */ -static int exaIllegal(VB *sim) { - sim->cpu.exception = 0xFF90; /* Illegal opcode */ - return 0; -} - -/* Jump relative, jump and link */ -static int exaJR(VB *sim) { - sim->cpu.inst.aux[0] = 0xFFFFFFFE & (sim->cpu.pc + SignExtend( - (int32_t) sim->cpu.inst.code[0] << 16 | sim->cpu.inst.code[1], 26)); - sim->cpu.clocks += 3; - return 0; -} - -/* Memory read */ -static int exaRead(VB *sim) { - - /* First invocation */ - if (sim->cpu.step == 0) { - exaStdThree(sim); - sim->cpu.inst.aux[1] += sim->cpu.inst.aux[0]; /* Address */ - sim->cpu.clocks += 5; /* TODO: Needs research */ - sim->cpu.step = 1; - } - - /* Read the value */ - return cpuRead(sim, sim->cpu.inst.aux[1], - ((OpDef *) sim->cpu.inst.def)->aux, &sim->cpu.inst.aux[0]); -} - -/* Trap */ -static int exaTRAP(VB *sim) { - /* TODO: Clocks is less 1 here because exaException adds 1 */ - sim->cpu.clocks += ((OpDef *) sim->cpu.inst.def)->aux - 1; - sim->cpu.exception = 0xFFA0 | (sim->cpu.inst.code[0] & 31); - return 0; -} - -/* Memory write */ -static int exaWrite(VB *sim) { - - /* First invocation */ - if (sim->cpu.step == 0) { - exaStdThree(sim); - sim->cpu.inst.aux[0] += sim->cpu.inst.aux[1]; - sim->cpu.inst.aux[1] = ((OpDef *) sim->cpu.inst.def)->aux; /* Type */ - sim->cpu.inst.aux[2] = sim->cpu.program[sim->cpu.inst.code[0]>>5&31]; - sim->cpu.clocks += 4; /* TODO: Needs research */ - sim->cpu.step = 1; - } - - /* Write the value */ - return cpuWritePre(sim, (uint32_t *) &sim->cpu.inst.aux[0], - &sim->cpu.inst.aux[1], &sim->cpu.inst.aux[2]); -} - - - -/**************************** Execute B Handlers *****************************/ - -/* Bit string bitwise */ -static void exbBitBitwise(VB *sim) { - int32_t bits; - - /* Write the output value */ - if (sim->cpu.program[28] != 0) { - busWrite(sim, sim->cpu.inst.aux[3], - sim->cpu.inst.aux[4], sim->cpu.inst.aux[2], 0); - } - - /* Prepare registers */ - sim->cpu.program[26] &= 0x0000001F; - sim->cpu.program[27] &= 0x0000001F; - sim->cpu.program[29] &= 0xFFFFFFFC; - sim->cpu.program[30] &= 0xFFFFFFFC; - - /* Determine how many bits of output have been processed */ - bits = 32 - sim->cpu.program[26]; - if ((uint32_t) sim->cpu.program[28] <= (uint32_t) bits) - bits = sim->cpu.program[28]; - - /* Update source */ - sim->cpu.program[27] += bits; - if (sim->cpu.program[27] >= 32) { - sim->cpu.program[27] &= 31; - sim->cpu.program[30] += 4; - sim->cpu.inst.aux[0] = sim->cpu.inst.aux[1]; - sim->cpu.bitstring = 1; /* Read next source word */ - } else sim->cpu.bitstring = 2; /* Skip source reads */ - - /* Update destination */ - sim->cpu.program[26] += bits; - if (sim->cpu.program[26] >= 32) { - sim->cpu.program[26] &= 31; - sim->cpu.program[29] += 4; - } - - /* Update length */ - sim->cpu.program[28] -= bits; - - /* Advance to the next instruction */ - if (sim->cpu.program[28] == 0) { - sim->cpu.bitstring = 0; - sim->cpu.pc += sim->cpu.inst.size; - } - -} - -/* Bit string search */ -static void exbBitSearch(VB *sim) { - int32_t dir = ((~sim->cpu.inst.code[0] & 1) << 1) - 1; - int32_t test = sim->cpu.inst.code[0] >> 1 & 1; - - /* Prepare registers */ - sim->cpu.program[27] &= 0x0000001F; - sim->cpu.program[30] &= 0xFFFFFFFC; - sim->cpu.psw.z = 1; - sim->cpu.bitstring = 1; - - /* Process all remaining bits in the current word */ - while (sim->cpu.psw.z && sim->cpu.program[28] != 0) { - - /* The bit does not match */ - if ( - (sim->cpu.inst.aux[0] & 1 << sim->cpu.program[27]) != - test << sim->cpu.program[27] - ) sim->cpu.program[29]++; - - /* A match was found */ - else sim->cpu.psw.z = 0; - - /* Advance to the next bit */ - sim->cpu.program[28]--; - sim->cpu.program[27] += dir; - if (sim->cpu.program[27] & 0x00000020) { - sim->cpu.program[27] &= 0x0000001F; - sim->cpu.program[30] += dir << 2; - break; - } - - } - - /* Advance to the next instruction */ - if (!sim->cpu.psw.z || sim->cpu.program[28] == 0) { - sim->cpu.bitstring = 0; - sim->cpu.pc += sim->cpu.inst.size; - } - -} - -/* Compare and exchange interlocked */ -static void exbCAXI(VB *sim) { - int32_t left = sim->cpu.inst.aux[3]; - int32_t right = sim->cpu.inst.aux[4]; - int32_t result = left - right; - sim->cpu.psw.cy = (uint32_t) left < (uint32_t) right; - sim->cpu.psw.ov = (int32_t) ((left ^ right) & (left ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31] = right; - busWrite(sim, sim->cpu.inst.aux[0], - sim->cpu.inst.aux[1], sim->cpu.inst.aux[2], 0); -} - -/* Clear interrupt disable flag */ -static void exbCLI(VB *sim) { - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.psw.id = 0; -} - -/* Exception */ -static void exbException(VB *sim) { - int x; /* Iterator */ - - /* Apply staged floating-point flags */ - if (sim->cpu.fpFlags != 0) { - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.fpFlags | - cpuGetSystemRegister(sim, VB_PSW), 0); - sim->cpu.fpFlags = 0; - } - - /* Fatal exception */ - if (sim->cpu.psw.np) { - for (x = 0; x < 9; x += 3) { - busWrite(sim, sim->cpu.inst.aux[x], - sim->cpu.inst.aux[x + 1], sim->cpu.inst.aux[x + 2], 0); - } - sim->cpu.stage = CPU_FATAL; - return; - } - - /* Duplexed exception */ - if (sim->cpu.psw.ep) { - sim->cpu.ecr.fecc = sim->cpu.exception; - sim->cpu.fepc = sim->cpu.pc; - sim->cpu.fepsw = cpuGetSystemRegister(sim, VB_PSW); - sim->cpu.pc = 0xFFFFFFD0; - sim->cpu.psw.np = 1; - } - - /* Regular exception */ - else { - sim->cpu.ecr.eicc = sim->cpu.exception; - sim->cpu.eipc = sim->cpu.pc; - sim->cpu.eipsw = cpuGetSystemRegister(sim, VB_PSW); - sim->cpu.pc = sim->cpu.inst.aux[0]; - sim->cpu.psw.ep = 1; - - /* Interrupt */ - if (sim->cpu.exception < 0xFF00) { - if ((sim->cpu.inst.code[0] & 0xFC00) == 0x6800) /* HALT */ - sim->cpu.eipc += sim->cpu.inst.size; - sim->cpu.psw.i = Min(15, (sim->cpu.exception >> 4 & 15) + 1); - } - - /* TRAP */ - if ((sim->cpu.exception & 0xFFE0) == 0xFFA0) - sim->cpu.eipc += sim->cpu.inst.size; - } - - /* Common processing */ - sim->cpu.psw.ae = 0; - sim->cpu.psw.id = 1; -} - -/* Floating-point instruction */ -static void exbFloating(VB *sim) { - int32_t result = sim->cpu.inst.aux[0]; /* Operation result */ - int32_t subop = sim->cpu.inst.aux[1]; /* Sub-opcode */ - - /* Apply staged floating-point flags */ - if (sim->cpu.fpFlags != 0) { - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.fpFlags | - cpuGetSystemRegister(sim, VB_PSW), 0); - sim->cpu.fpFlags = 0; - } - - /* Update state */ - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - if (subop != 0x03 && subop != 0x0B) /* CVT.SW, TRNC.SW */ - sim->cpu.psw.cy = result < 0; - if (subop != 0x00) /* CMPF.S */ - sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31] = result; -} - -/* Halt */ -static void exbHALT(VB *sim) { - sim->cpu.stage = CPU_HALT; -} - -/* Jump and link */ -static void exbJAL(VB *sim) { - sim->cpu.program[31] = sim->cpu.pc + sim->cpu.inst.size; - sim->cpu.pc = sim->cpu.inst.aux[0]; -} - -/* Jump */ -static void exbJMP(VB *sim) { - sim->cpu.pc = sim->cpu.inst.aux[0]; -} - -/* Return from trap or interrupt */ -static void exbRETI(VB *sim) { - if (sim->cpu.psw.np) { - sim->cpu.pc = sim->cpu.fepc; - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.fepsw, 0); - } else { - sim->cpu.pc = sim->cpu.eipc; - cpuSetSystemRegister(sim, VB_PSW, sim->cpu.eipsw, 0); - } -} - -/* Set interrupt disable flag */ -static void exbSEI(VB *sim) { - sim->cpu.pc += sim->cpu.inst.size; - sim->cpu.psw.id = 1; -} - -/* Standard two-operand instruction */ -static void exbStdTwo(VB *sim) { - ((OpDef *) sim->cpu.inst.def)->operation(sim, - &sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31], - sim->cpu.inst.aux[0] - ); - sim->cpu.pc += sim->cpu.inst.size; -} - -/* Standard three-operand instruction */ -static void exbStdThree(VB *sim) { - int32_t *reg2 = &sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31]; - *reg2 = sim->cpu.inst.aux[1]; - ((OpDef *) sim->cpu.inst.def)->operation(sim, reg2, sim->cpu.inst.aux[0]); - sim->cpu.pc += sim->cpu.inst.size; -} - - - -/**************************** Operation Handlers *****************************/ - -/* Add */ -static void opADD(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest + src; - sim->cpu.psw.cy = (uint32_t) result < (uint32_t) *dest; - sim->cpu.psw.ov = (int32_t) (~(*dest ^ src) & (*dest ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Add Floating Short */ -static void opADDF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left = *(float *)dest; - double right = *(float *)&src; - double result = left + right; - aux->f32 = result; - aux->f64 = result; -} - -/* And */ -static void opAND(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest & src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; +/* Determine how many clocks are guaranteed to process */ +static uint32_t cpuUntil(VB *sim, uint32_t clocks) { + return sim->cpu.clocks < clocks ? sim->cpu.clocks : clocks; } -/* And Bit String Upward */ -static void opANDBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest &= src; -} - -/* And Not Bit String Upward */ -static void opANDNBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = ~*dest & src; -} - -/* Compare */ -static void opCMP(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest - src; - sim->cpu.psw.cy = (uint32_t) *dest < (uint32_t) src; - sim->cpu.psw.ov = (int32_t) ((*dest ^ src) & (*dest ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; -} - -/* Convert Word Integer to Short Floating */ -static void opCVT_WS(VB *sim, int32_t *dest, int32_t src) { - float value = (float) src; - *dest = *(int32_t *)&value; - if ((double) value != (double) src) - sim->cpu.psw.fpr = 1; -} - -/* Divide signed */ -static void opDIV(VB *sim, int32_t *dest, int32_t src) { - int32_t result; - if (*dest == INT32_MIN && src == -1) { - sim->cpu.psw.ov = 1; - sim->cpu.psw.s = 1; - sim->cpu.psw.z = 0; - sim->cpu.program[30] = 0; - } else { - result = *dest / src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - sim->cpu.program[30] = *dest % src; - *dest = result; - } -} - -/* Divide Floating Short */ -static void opDIVF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left; /* Left operand */ - double right; /* Right operand */ - double result; /* Operation result */ - - /* Divisor is zero */ - if (*dest == 0) { - - /* Invalid operation */ - if (src == 0) { - sim->cpu.fpFlags = 0x00000100; /* FIV */ - sim->cpu.exception = 0xFF70; - } - - /* Zero division */ - else { - sim->cpu.fpFlags = 0x00000080; /* FZD */ - sim->cpu.exception = 0xFF68; - } - - return; - } - - /* Perform the operation */ - left = *(float *)dest; - right = *(float *)&src; - result = left / right; - aux->f32 = result; - aux->f64 = result; -} - -/* Divide unsigned */ -static void opDIVU(VB *sim, int32_t *dest, int32_t src) { - uint32_t result = (uint32_t) *dest / (uint32_t) src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = (int32_t) result < 0; - sim->cpu.psw.z = result == 0; - sim->cpu.program[30] = (int32_t) ((uint32_t) *dest % (uint32_t) src); - *dest = (int32_t) result; -} - -/* Load to system register */ -static void opLDSR(VB *sim, int32_t *dest, int32_t src) { - cpuSetSystemRegister(sim, src, *dest, 0); -} - -/* Move */ -static void opMOV(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = src; -} - -/* Move Bit String Upward */ -static void opMOVBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - (void) dest; - (void) src; -} - -/* Add Immediate */ -static void opMOVEA(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest += src; -} - -/* Multiply Halfword */ -static void opMPYHW(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest *= SignExtend(src, 17); -} - -/* Multiply signed */ -static void opMUL(VB *sim, int32_t *dest, int32_t src) { - int64_t result = (int64_t) *dest * (int64_t) src; - int32_t resultLow = (int32_t) result; - sim->cpu.psw.ov = result != resultLow; - sim->cpu.psw.s = resultLow < 0; - sim->cpu.psw.z = resultLow == 0; - sim->cpu.program[30] = (int32_t) (result >> 32); - *dest = resultLow; -} - -/* Multiply Floating Short */ -static void opMULF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left = *(float *)dest; - double right = *(float *)&src; - double result = left * right; - aux->f32 = result; - aux->f64 = result; -} - -/* Multiply unsigned */ -static void opMULU(VB *sim, int32_t *dest, int32_t src) { - uint64_t result = (uint64_t)(uint32_t)*dest * (uint64_t)(uint32_t)src; - uint32_t resultLow = (uint32_t) result; - sim->cpu.psw.ov = result != resultLow; - sim->cpu.psw.s = (int32_t) resultLow < 0; - sim->cpu.psw.z = resultLow == 0; - sim->cpu.program[30] = (int32_t) (result >> 32); - *dest = (int32_t) resultLow; -} - -/* Not */ -static void opNOT(VB *sim, int32_t *dest, int32_t src) { - int32_t result = ~src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Not Bit String Upward */ -static void opNOTBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - (void) src; - *dest = ~*dest; -} - -/* Or */ -static void opOR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest | src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Or Bit String Upward */ -static void opORBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest |= src; -} - -/* Or Not Bit String Upward */ -static void opORNBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = ~*dest | src; -} - -/* Reverse Bits in Word */ -static void opREV(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - src = (src << 16 & 0xFFFF0000) | (src >> 16 & 0x0000FFFF); - src = (src << 8 & 0xFF00FF00) | (src >> 8 & 0x00FF00FF); - src = (src << 4 & 0xF0F0F0F0) | (src >> 4 & 0x0F0F0F0F); - src = (src << 2 & 0xCCCCCCCC) | (src >> 2 & 0x33333333); - *dest = (src << 1 & 0xAAAAAAAA) | (src >> 1 & 0x55555555); -} - -/* Set flag condition */ -static void opSETF(VB *sim, int32_t *dest, int32_t src) { - *dest = cpuCondition(sim, src & 15); -} - -/* Shift right arithmetic */ -static void opSAR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest >> (src &= 31); - #ifndef VB_SIGNED_PROPAGATE - if (src != 0) - result = SignExtend(result, 32 - src); - #endif - sim->cpu.psw.cy = src != 0 && *dest & 1 << (src - 1); - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Shift left */ -static void opSHL(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest << (src &= 31); - sim->cpu.psw.cy = src != 0 && *dest & 1 << (32 - src); - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Shift right logical */ -static void opSHR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = (uint32_t) *dest >> (src &= 31); - #ifndef VB_SIGNED_PROPAGATE - if (src != 0) - result &= ((uint32_t) 1 << (32 - src)) - 1; - #endif - sim->cpu.psw.cy = src != 0 && *dest & 1 << (src - 1); - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Store */ -static void opST(VB *sim, int32_t *dest, int32_t src) { - (void) dest, (void) src; - busWrite(sim, sim->cpu.inst.aux[0], - sim->cpu.inst.aux[1], sim->cpu.inst.aux[2], 0); -} - -/* Store to system register */ -static void opSTSR(VB *sim, int32_t *dest, int32_t src) { - *dest = cpuGetSystemRegister(sim, src); -} - -/* Subtract */ -static void opSUB(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest - src; - sim->cpu.psw.cy = (uint32_t) *dest < (uint32_t) src; - sim->cpu.psw.ov = (int32_t) ((*dest ^ src) & (*dest ^ result)) < 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Subtract Floating Short */ -static void opSUBF_S(VB *sim, int32_t *dest, int32_t src) { - FloatAux *aux = (FloatAux *) sim->cpu.inst.aux; - double left = *(float *)dest; - double right = *(float *)&src; - double result = left - right; - aux->f32 = result; - aux->f64 = result; -} - -/* Exchange Byte */ -static void opXB(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = (src & 0xFFFF0000) | (src << 8 & 0xFF00) | (src >> 8 & 0x00FF); -} - -/* Exchange Halfword */ -static void opXH(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = (src << 16 & 0xFFFF0000) | (src >> 16 & 0x0000FFFF); -} - -/* Exclusive Or */ -static void opXOR(VB *sim, int32_t *dest, int32_t src) { - int32_t result = *dest ^ src; - sim->cpu.psw.ov = 0; - sim->cpu.psw.s = result < 0; - sim->cpu.psw.z = result == 0; - *dest = result; -} - -/* Exclusive Or Bit String Upward */ -static void opXORBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest ^= src; -} - -/* Exclusive Or Not Bit String Upward */ -static void opXORNBSU(VB *sim, int32_t *dest, int32_t src) { - (void) sim; - *dest = ~*dest ^ src; -} - - - -/***************************** Operand Handlers ******************************/ - -/* imm5 (sign-extended) */ -static int32_t opImm5S(VB *sim) { - return SignExtend(sim->cpu.inst.code[0] & 31, 5); -} - -/* imm5 */ -static int32_t opImm5U(VB *sim) { - return sim->cpu.inst.code[0] & 31; -} - -/* imm16 (shifted left by 16) */ -static int32_t opImm16H(VB *sim) { - return (uint32_t) sim->cpu.inst.code[1] << 16; -} - -/* imm16 (sign-extended) */ -static int32_t opImm16S(VB *sim) { - return SignExtend(sim->cpu.inst.code[1], 16); -} - -/* imm16 */ -static int32_t opImm16U(VB *sim) { - return sim->cpu.inst.code[1]; -} - -/* reg1 */ -static int32_t opReg1(VB *sim) { - return sim->cpu.program[sim->cpu.inst.code[0] & 31]; -} - -/* reg2 */ -static int32_t opReg2(VB *sim) { - return sim->cpu.program[sim->cpu.inst.code[0] >> 5 & 31]; -} - - - -/**************************** Opcode Definitions *****************************/ - -/* Forward references */ -static int exaBitString(VB *); -static int exaFloatendo(VB *); - -/* Exception processing */ -static const OpDef OPDEF_EXCEPTION = - { &exaException, &exbException, NULL, NULL, 0, 0 }; - -/* Top-level opcode definitions */ -static const OpDef OPDEFS[] = { - { &exaStdTwo , &exbStdTwo , &opMOV , &opReg1 , 1, 1 }, /* 0x00 */ - { &exaStdTwo , &exbStdTwo , &opADD , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSUB , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opCMP , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHL , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbJMP , NULL , &opReg1 , 1, 3 }, - { &exaStdTwo , &exbStdTwo , &opSAR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opMUL , &opReg1 , 1, 13 }, - { &exaDivision , &exbStdTwo , &opDIV , &opReg1 , 1, 38 }, - { &exaStdTwo , &exbStdTwo , &opMULU , &opReg1 , 1, 13 }, - { &exaDivision , &exbStdTwo , &opDIVU , &opReg1 , 1, 36 }, - { &exaStdTwo , &exbStdTwo , &opOR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opAND , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opXOR , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opNOT , &opReg1 , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opMOV , &opImm5S , 1, 1 }, /* 0x10 */ - { &exaStdTwo , &exbStdTwo , &opADD , &opImm5S , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSETF , &opImm5U , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opCMP , &opImm5S , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHL , &opImm5U , 1, 1 }, - { &exaStdTwo , &exbStdTwo , &opSHR , &opImm5U , 1, 1 }, - { &exaDefault , &exbCLI , NULL , NULL , 1, 12 }, - { &exaStdTwo , &exbStdTwo , &opSAR , &opImm5U , 1, 1 }, - { &exaTRAP , NULL , NULL , NULL , 1, 15 }, - { &exaDefault , &exbRETI , NULL , NULL , 1, 10 }, - { &exaDefault , &exbHALT , NULL , NULL , 1, 0 }, - { &exaIllegal , NULL , NULL , NULL , 1, 0 }, - { &exaStdTwo , &exbStdTwo , &opLDSR , &opImm5U , 1, 8 }, - { &exaStdTwo , &exbStdTwo , &opSTSR , &opImm5U , 1, 8 }, - { &exaDefault , &exbSEI , NULL , NULL , 1, 12 }, - { &exaBitString, NULL , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, /* 0x20 */ - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaBCOND , &exbJMP , NULL , NULL , 1, 0 }, - { &exaStdThree , &exbStdThree, &opMOVEA, &opImm16S, 2, 1 }, - { &exaStdThree , &exbStdThree, &opADD , &opImm16S, 2, 1 }, - { &exaJR , &exbJMP , NULL , NULL , 2, 0 }, - { &exaJR , &exbJAL , NULL , NULL , 2, 0 }, - { &exaStdThree , &exbStdThree, &opOR , &opImm16U, 2, 1 }, - { &exaStdThree , &exbStdThree, &opAND , &opImm16U, 2, 1 }, - { &exaStdThree , &exbStdThree, &opXOR , &opImm16U, 2, 1 }, - { &exaStdThree , &exbStdThree, &opMOVEA, &opImm16H, 2, 1 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S8 }, /* 0x30 */ - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S16 }, - { &exaIllegal , NULL , NULL , NULL , 1, 0 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S32 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S8 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S16 }, - { &exaIllegal , NULL , NULL , NULL , 1, 0 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S32 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_U8 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_U16 }, - { &exaCAXI , &exbCAXI , NULL , &opImm16S, 2, 26 }, - { &exaRead , &exbStdTwo , &opMOV , &opImm16S, 2, VB_S32 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_U8 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_U16 }, - { &exaFloatendo, NULL , NULL , NULL , 2, 0 }, - { &exaWrite , &exbStdTwo , &opST , &opImm16S, 2, VB_S32 } -}; - -/* Bit string opcode definitions */ -static const OpDef OPDEFS_BITSTRING[] = { - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, /* 0x00 */ - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, - { &exaBitSearch , &exbBitSearch , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opORBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opANDBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opXORBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opMOVBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opORNBSU , NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opANDNBSU, NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opXORNBSU, NULL, 1, 0 }, - { &exaBitBitwise, &exbBitBitwise, &opNOTBSU , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, /* 0x10 */ - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 }, - { &exaIllegal , NULL , NULL , NULL, 1, 0 } -}; - -/* Floating-point/Nintendo opcode definitions */ -static const OpDef OPDEFS_FLOATENDO[] = { - { &exaFloating2, &exbFloating, &opSUBF_S, NULL , 2, 10 }, /* 0x00 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaStdTwo , &exbStdTwo , &opCVT_WS, &opReg1, 2, 16 }, - { &exaFloating1, &exbFloating, NULL , NULL , 2, 14 }, - { &exaFloating2, &exbFloating, &opADDF_S, NULL , 2, 28 }, - { &exaFloating2, &exbFloating, &opSUBF_S, NULL , 2, 30 }, - { &exaFloating2, &exbFloating, &opMULF_S, NULL , 2, 28 }, - { &exaFloating2, &exbFloating, &opDIVF_S, NULL , 2, 44 }, - { &exaStdTwo , &exbStdTwo , &opXB , &opReg2, 2, 6 }, - { &exaStdTwo , &exbStdTwo , &opXH , &opReg2, 2, 1 }, - { &exaStdTwo , &exbStdTwo , &opREV , &opReg1, 2, 22 }, - { &exaFloating1, &exbFloating, NULL , NULL , 2, 14 }, - { &exaStdTwo , &exbStdTwo , &opMPYHW , &opReg1, 2, 22 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, /* 0x10 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, /* 0x20 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, /* 0x30 */ - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, - { &exaIllegal , NULL , NULL , NULL , 2, 0 }, -}; - -/* Bit string instructions */ -static int exaBitString(VB *sim) { - OpDef *def; /* Opcode definition */ - sim->cpu.inst.def = def = - (OpDef *) &OPDEFS_BITSTRING[sim->cpu.inst.code[0] & 31]; - return def->executeA(sim); -} - -/* Floating-point and Nintendo instruction parser */ -static int exaFloatendo(VB *sim) { - OpDef *def; /* Opcode definition */ - sim->cpu.inst.def = def = - (OpDef *) &OPDEFS_FLOATENDO[sim->cpu.inst.code[1] >> 10 & 63]; - return def->executeA(sim); -} - - - -/****************************** Pipeline Stages ******************************/ - -/* Raise an exception */ -static void cpuException(VB *sim) { - sim->cpu.inst.def = (OpDef *) &OPDEF_EXCEPTION; - sim->cpu.stage = CPU_EXECUTE_A; - sim->cpu.inst.aux[0] = 0xFFFF0000 | (sim->cpu.exception & 0xFFF0); - if (sim->cpu.inst.aux[0] == (int32_t) 0xFFFFFF70) - sim->cpu.inst.aux[0] = 0xFFFFFF60; -} - -/* Execute: Pre-processing, does not update state */ -static int cpuExecuteA(VB *sim) { - OpDef *def; /* Opcode descriptor */ - VBInstruction inst; /* Instruction descriptor */ - - /* First invocation */ - if (sim->cpu.step == 0 && sim->cpu.exception == 0) { - - /* Call the breakpoint handler */ - if (sim->onExecute != NULL) { - - /* Query the application */ - inst.address = sim->cpu.pc; - inst.code[0] = sim->cpu.inst.code[0]; - inst.code[1] = sim->cpu.inst.code[1]; - inst.size = sim->cpu.inst.size; - if (sim->onExecute(sim, &inst)) - return 1; - - /* Apply changes */ - sim->cpu.inst.code[0] = inst.code[0]; - sim->cpu.inst.code[1] = inst.code[1]; - sim->cpu.inst.size = inst.size; - sim->cpu.inst.def = - (OpDef *) &OPDEFS[sim->cpu.inst.code[0] >> 10 & 63]; - } - - /* Detect non-bit string instruction */ - if ((sim->cpu.inst.code[0] & 0xFC00) != 0x7C00) - sim->cpu.bitstring = 0; - } - - /* Processing before updating simulation state */ - def = sim->cpu.inst.def; - for (;;) { - if (def->executeA(sim)) - return 1; - sim->cpu.step = 0; - - /* Advance to exception processing */ - if (sim->cpu.exception == 0 || def == &OPDEF_EXCEPTION) - break; - def = (OpDef *) &OPDEF_EXCEPTION; - cpuException(sim); - } - - /* Advance to execute B */ - sim->cpu.stage = CPU_EXECUTE_B; - return 0; -} - -/* Execute: Post-processing, updates state */ -static void cpuExecuteB(VB *sim) { - - /* Perform the operation and update state */ - ((OpDef *) sim->cpu.inst.def)->executeB(sim); - sim->cpu.program[0] = 0; - - /* Advance to next pipeline stage */ - if (sim->cpu.stage == CPU_EXECUTE_B) { - if (cpuCheckIRQs(sim)) - cpuException(sim); - else if (sim->cpu.bitstring != 0) - sim->cpu.stage = CPU_EXECUTE_A; - else sim->cpu.stage = CPU_FETCH; - } - -} - -/* Retrieve instruction data from the bus */ -static int cpuFetch(VB *sim) { - OpDef *def; /* Opcode definition */ - - /* First fetch */ - if (sim->cpu.step == 0) { - if (cpuReadFetch(sim)) - return 1; - sim->cpu.inst.def = def = - (OpDef *) &OPDEFS[sim->cpu.inst.code[0] >> 10 & 0x003F]; - sim->cpu.inst.size = def->size << 1; - sim->cpu.step = 1; - } else def = (OpDef *) sim->cpu.inst.def; - - /* Second fetch */ - for (; sim->cpu.step < def->size; sim->cpu.step++) { - if (cpuReadFetch(sim)) - return 1; - } - - /* Advance to execute A */ - sim->cpu.stage = CPU_EXECUTE_A; - sim->cpu.step = 0; - return 0; -} - - - -/***************************** Module Functions ******************************/ - -/* Process a simulation for a given number of clocks */ -static int cpuEmulate(VB *sim, uint32_t clocks) { - - /* Process all clocks */ - for (;;) { - - /* Processing by pipeline stage */ - switch (sim->cpu.stage) { - - /* Fetch: Retrive instruction code from memory */ - case CPU_FETCH: - if (cpuFetch(sim)) - return 1; - break; - - /* Execute A: Check for exceptions, configure CPU clocks */ - case CPU_EXECUTE_A: - if (cpuExecuteA(sim)) - return 1; - break; - - /* Execute B: Wait clocks and update state */ - case CPU_EXECUTE_B: - - /* Clocks remaining exceeds emulation clocks */ - if (clocks < sim->cpu.clocks) { - sim->cpu.clocks -= clocks; - return 0; - } - - /* Update simulation state */ - clocks -= sim->cpu.clocks; - sim->cpu.clocks = 0; - cpuExecuteB(sim); - break; - - /* Halt: Wait for an interrupt */ - case CPU_HALT: - if (!cpuCheckIRQs(sim)) - return 0; - cpuException(sim); - break; - - /* Fatal exception: Cannot recover */ - case CPU_FATAL: - return 0; - } - - }; - - /* Unreachable */ - return 0; -} - -/* Compute clocks without breakpoint or state change */ -static int cpuUntil(VB *sim, uint32_t clocks) { - return sim->cpu.stage == CPU_HALT || sim->cpu.stage == CPU_FATAL ? - clocks : Min(sim->cpu.clocks, clocks); -} - - - #endif /* VBAPI */ diff --git a/core/vb.c b/core/vb.c index f2996a6..9bb249c 100644 --- a/core/vb.c +++ b/core/vb.c @@ -5,37 +5,117 @@ -/***************************** Utility Functions *****************************/ +/*********************************** Types ***********************************/ -/* Select the lesser of two unsigned numbers */ -static uint32_t Min(uint32_t a, uint32_t b) { +/* Simulation state */ +struct VB { + + /* Game Pak */ + struct { + uint8_t *ram; /* Save RAM */ + uint8_t *rom; /* Program ROM */ + uint32_t ramMask; /* Size of SRAM - 1 */ + uint32_t romMask; /* Size of ROM - 1 */ + } cart; + + /* CPU */ + struct { + + /* Cache Control Word */ + struct { + uint8_t ice; /* Instruction Cache Enable */ + } chcw; + + /* Exception Cause Register */ + struct { + uint16_t eicc; /* Exception/Interrupt Cause Code */ + uint16_t fecc; /* Fatal Error Cause Code */ + } ecr; + + /* Program Status Word */ + struct { + uint8_t ae; /* Address Trap Enable */ + uint8_t cy; /* Carry */ + uint8_t ep; /* Exception Pending */ + uint8_t fiv; /* Floating Invalid */ + uint8_t fov; /* Floating Overflow */ + uint8_t fpr; /* Floading Precision */ + uint8_t fro; /* Floating Reserved Operand */ + uint8_t fud; /* Floading Underflow */ + uint8_t fzd; /* Floating Zero Divide */ + uint8_t i; /* Interrupt Level */ + uint8_t id; /* Interrupt Disable */ + uint8_t np; /* NMI Pending */ + uint8_t ov; /* Overflow */ + uint8_t s; /* Sign */ + uint8_t z; /* Zero */ + } psw; + + /* Other registers */ + uint32_t adtre; /* Address Trap Register for Execution */ + uint32_t eipc; /* Exception/Interrupt PC */ + uint32_t eipsw; /* Exception/Interrupt PSW */ + uint32_t fepc; /* Fatal Error PC */ + uint32_t fepsw; /* Fatal Error PSW */ + uint32_t pc; /* Program Counter */ + int32_t program[32]; /* Program registers */ + uint32_t sr29; /* System register 29 */ + uint32_t sr31; /* System register 31 */ + + /* Working data */ + union { + struct { + uint32_t address; + int32_t value; + } data; + } aux; + + /* Other state */ + uint32_t clocks; /* Master clocks to wait */ + uint16_t code[2]; /* Instruction code units */ + uint16_t irq; /* Interrupt request lines */ + int length; /* Instruction code length */ + uint32_t nextPC; /* Address of next instruction */ + int operation; /* Current operation ID */ + int step; /* Operation sub-task ID */ + } cpu; + + /* Other system state */ + uint8_t wram[0x10000]; /* System RAM */ + + /* Application callbacks */ + vbOnExecute onExecute; /* CPU instruction execute */ + vbOnFetch onFetch; /* CPU instruction fetch */ + vbOnRead onRead; /* CPU instruction read */ + vbOnWrite onWrite; /* CPU instruction write */ +}; + + + +/***************************** Library Functions *****************************/ + +/* Determine the lesser of two clocks figures */ +static uint32_t MinClocks(uint32_t a, uint32_t b) { return a < b ? a : b; } -/* Sign-extend an integer, masking upper bits if positive */ -#ifndef VB_SIGNED_PROPAGATE - /* Generic implementation */ - static int32_t SignExtend(int32_t value, int bits) { - return value & 1 << (bits - 1) ? - value | ~0 << bits : - value & ((1 << bits) - 1) - ; - } -#else - /* Sign-propagating implementation */ - #define SignExtend(v, b) ((int32_t) (v) << (32 - (b)) >> (32 - (b))) -#endif +/* Sign-extend an integer of variable width */ +static int32_t SignExtend(int32_t value, int32_t bits) { + value &= ~((uint32_t) 0xFFFFFFFF << bits); + bits = (int32_t) 1 << (bits - (int32_t) 1); + return (value ^ bits) - bits; +} -/**************************** Sub-Module Imports *****************************/ +/******************************** Sub-Modules ********************************/ #include "bus.c" #include "cpu.c" -/***************************** Module Functions ******************************/ +/***************************** Library Functions *****************************/ /* Process a simulation for a given number of clocks */ static int sysEmulate(VB *sim, uint32_t clocks) { @@ -44,7 +124,7 @@ static int sysEmulate(VB *sim, uint32_t clocks) { ; } -/* Determine how many clocks can be simulated without a breakpoint */ +/* Determine how many clocks are guaranteed to process */ static uint32_t sysUntil(VB *sim, uint32_t clocks) { clocks = cpuUntil(sim, clocks); return clocks; @@ -52,127 +132,121 @@ static uint32_t sysUntil(VB *sim, uint32_t clocks) { -/************************************ API ************************************/ +/******************************* API Commands ********************************/ -/* Process a simulation */ -int vbEmulate(VB *sim, uint32_t *clocks) { - int brk; /* A break was requested */ - uint32_t until; /* Number of clocks during which no break will occur */ - - /* Process all clocks */ - for (brk = 0; *clocks != 0 && !brk; *clocks -= until) { - until = sysUntil (sim, *clocks); - brk = sysEmulate(sim, until ); +/* Process one simulation */ +VBAPI int vbEmulate(VB *sim, uint32_t *clocks) { + int brk; /* A callback requested a break */ + uint32_t until; /* Clocks guaranteed to process */ + while (*clocks != 0) { + until = sysUntil(sim, *clocks); + brk = sysEmulate(sim, until); + *clocks -= until; + if (brk) + return brk; /* TODO: return 1 */ } - - return brk; + return 0; } /* Process multiple simulations */ -int vbEmulateEx(VB **sims, int count, uint32_t *clocks) { - int brk; /* A break was requested */ - uint32_t until; /* Number of clocks during which no break will occur */ +VBAPI int vbEmulateEx(VB **sims, int count, uint32_t *clocks) { + int brk; /* A callback requested a break */ + uint32_t until; /* Clocks guaranteed to process */ int x; /* Iterator */ - - /* Process all clocks */ - for (brk = 0; *clocks != 0 && !brk; *clocks -= until) { + while (*clocks != 0) { until = *clocks; - for (x = 0; x < count; x++) - until = sysUntil (sims[x], until); - for (x = 0; x < count; x++) - brk |= sysEmulate(sims[x], until); - } + for (x = count - 1; x >= 0; x--) + until = sysUntil(sims[x], until); - return brk; + brk = 0; + for (x = count - 1; x >= 0; x--) + brk |= sysEmulate(sims[x], until); + + *clocks -= until; + if (brk) + return brk; /* TODO: return 1 */ + } + return 0; } -/* Retrieve a current breakpoint handler */ -void* vbGetCallback(VB *sim, int id) { +/* Retrieve a callback handler */ +VBAPI void* vbGetCallback(VB *sim, int id) { switch (id) { - case VB_ONEXCEPTION: return *(void **)&sim->onException; - case VB_ONEXECUTE : return *(void **)&sim->onExecute; - case VB_ONFETCH : return *(void **)&sim->onFetch; - case VB_ONREAD : return *(void **)&sim->onRead; - case VB_ONWRITE : return *(void **)&sim->onWrite; + /*case VB_EXCEPTION: return *(void **) &sim->onException;*/ + case VB_EXECUTE : return *(void **) &sim->onExecute; + case VB_FETCH : return *(void **) &sim->onFetch; + /*case VB_FRAME : return *(void **) &sim->onFrame;*/ + case VB_READ : return *(void **) &sim->onRead; + case VB_WRITE : return *(void **) &sim->onWrite; } return NULL; } -/* Retrieve the value of a register */ -int32_t vbGetRegister(VB *sim, int type, int id) { - switch (type) { - case VB_PROGRAM: - return id < 0 || id > 31 ? 0 : sim->cpu.program[id]; - case VB_SYSTEM: - return cpuGetSystemRegister(sim, id); - case VB_OTHER: - switch (id) { - case VB_PC: return sim->cpu.pc; - } - } - return 0; /* Invalid type */ -} - -/* Retrieve a handle to the current cartridge ROM data */ -uint8_t* vbGetROM(VB *sim, uint32_t *size) { +/* Retrieve the game pack RAM buffer */ +VBAPI void* vbGetCartRAM(VB *sim, uint32_t *size) { if (size != NULL) - *size = sim->cart.romSize; - return sim->cart.rom; -} - -/* Retrieve a handle to the current cartridge RAM data */ -uint8_t* vbGetSRAM(VB *sim, uint32_t *size) { - if (size != NULL) - *size = sim->cart.ramSize; + *size = sim->cart.ram == NULL ? 0 : sim->cart.ramMask + 1; return sim->cart.ram; } -/* Prepare a simulation instance for use */ -void vbInit(VB *sim) { +/* Retrieve the game pack ROM buffer */ +VBAPI void* vbGetCartROM(VB *sim, uint32_t *size) { + if (size != NULL) + *size = sim->cart.rom == NULL ? 0 : sim->cart.romMask + 1; + return sim->cart.rom; +} - /* Breakpoint handlers */ - sim->onException = NULL; - sim->onExecute = NULL; - sim->onFetch = NULL; - sim->onRead = NULL; - sim->onWrite = NULL; +/* Retrieve the value of the program counter */ +VBAPI uint32_t vbGetProgramCounter(VB *sim) { + return sim->cpu.pc; +} - /* Game pak */ - sim->cart.ram = NULL; - sim->cart.ramSize = 0; - sim->cart.rom = NULL; - sim->cart.romSize = 0; +/* Retrieve the value in a program register */ +VBAPI int32_t vbGetProgramRegister(VB *sim, int index) { + return index < 1 || index > 31 ? 0 : sim->cpu.program[index]; +} - /* Hardware reset */ +/* Retrieve the value in a system register */ +VBAPI uint32_t vbGetSystemRegister(VB *sim, int index) { + return index < 0 || index > 31 ? 0 : cpuGetSystemRegister(sim, index); +} + +/* Initialize a simulation instance */ +VBAPI VB* vbInit(VB *sim) { + sim->cart.ram = NULL; + sim->cart.rom = NULL; + sim->onExecute = NULL; + sim->onFetch = NULL; + sim->onRead = NULL; + sim->onWrite = NULL; vbReset(sim); + return sim; } -/* Read a value from memory */ -int32_t vbRead(VB *sim, uint32_t address, int type) { - return busRead(sim, address, type); -} - -/* Read multiple bytes from memory */ -void vbReadEx(VB *sim, uint32_t address, uint8_t *buffer, uint32_t length) { - while (length--) *buffer++ = busRead(sim, address++, VB_U8); +/* Read a value from the memory bus */ +VBAPI int32_t vbRead(VB *sim, uint32_t address, int type) { + int32_t value; + if (type < 0 || type > 4) + return 0; + busRead(sim, address, type, &value); + return value; } /* Simulate a hardware reset */ -void vbReset(VB *sim) { - int x; /* Iterator */ +VBAPI VB* vbReset(VB *sim) { + uint32_t x; /* Iterator */ - /* Reset WRAM (the hardware does not do this) */ + /* WRAM (the hardware does not do this) */ for (x = 0; x < 0x10000; x++) sim->wram[x] = 0x00; /* CPU (normal) */ - sim->cpu.pc = 0xFFFFFFF0; + sim->cpu.irq = 0; + sim->cpu.pc = 0xFFFFFFF0; cpuSetSystemRegister(sim, VB_ECR, 0x0000FFF0, 1); cpuSetSystemRegister(sim, VB_PSW, 0x00008000, 1); - for (x = 0; x < 5; x++) - sim->cpu.irq[x] = 0; - /* CPU (extra, hardware doesn't do this) */ + /* CPU (extra, hardware does not do this) */ sim->cpu.adtre = 0x00000000; sim->cpu.eipc = 0x00000000; sim->cpu.eipsw = 0x00000000; @@ -184,99 +258,88 @@ void vbReset(VB *sim) { for (x = 0; x < 32; x++) sim->cpu.program[x] = 0x00000000; - /* CPU (internal) */ - sim->cpu.bitstring = 0; + /* CPU (other) */ sim->cpu.clocks = 0; - sim->cpu.exception = 0; - sim->cpu.stage = CPU_FETCH; + sim->cpu.nextPC = 0xFFFFFFF0; + sim->cpu.operation = CPU_FETCH; sim->cpu.step = 0; + + return sim; } -/* Specify a breakpoint handler */ -void* vbSetCallback(VB *sim, int id, void *proc) { - void *prev = vbGetCallback(sim, id); +/* Specify a new callback handler */ +VBAPI void* vbSetCallback(VB *sim, int id, void *callback) { + void *prev = NULL; + void **target = NULL; + + /* Select callback by ID */ switch (id) { - case VB_ONEXCEPTION: *(void **)&sim->onException = proc; break; - case VB_ONEXECUTE : *(void **)&sim->onExecute = proc; break; - case VB_ONFETCH : *(void **)&sim->onFetch = proc; break; - case VB_ONREAD : *(void **)&sim->onRead = proc; break; - case VB_ONWRITE : *(void **)&sim->onWrite = proc; break; + /*case VB_EXCEPTION: target = (void **) &sim->onException; break;*/ + case VB_EXECUTE : target = (void **) &sim->onExecute ; break; + case VB_FETCH : target = (void **) &sim->onFetch ; break; + /*case VB_FRAME : target = (void **) &sim->onFrame ; break;*/ + case VB_READ : target = (void **) &sim->onRead ; break; + case VB_WRITE : target = (void **) &sim->onWrite ; break; + } + + /* Retrieve current state and update new state */ + if (target != NULL) { + prev = *target; + *target = callback; } return prev; } -/* Specify a value for a register */ -int32_t vbSetRegister(VB *sim, int type, int id, int32_t value) { - switch (type) { - case VB_PROGRAM: - return id < 1 || id > 31 ? 0 : (sim->cpu.program[id] = value); - case VB_SYSTEM: - return cpuSetSystemRegister(sim, id, value, 1); - case VB_OTHER: - switch (id) { - case VB_PC: - sim->cpu.bitstring = 0; - sim->cpu.clocks = 0; - sim->cpu.exception = 0; - sim->cpu.stage = CPU_FETCH; - return sim->cpu.pc = value & 0xFFFFFFFE; - } +/* Specify a game pak RAM buffer */ +VBAPI int vbSetCartRAM(VB *sim, void *sram, uint32_t size) { + if (sram != NULL) { + if (size < 16 || size > 0x1000000 || (size & (size - 1)) != 0) + return 1; + sim->cart.ramMask = size - 1; } - return 0; /* Invalid type or ID */ -} - -/* Specify a cartridge ROM buffer */ -int vbSetROM(VB *sim, uint8_t *data, uint32_t size) { - - /* Specifying no ROM */ - if (data == NULL) { - sim->cart.rom = NULL; - sim->cart.romSize = 0; - return 0; - } - - /* Error checking */ - if ( - size < 4 || - size > 0x1000000 || - (size & (size - 1)) /* Power of 2 */ - ) return 1; - - /* Register the ROM data */ - sim->cart.rom = data; - sim->cart.romSize = size; + sim->cart.ram = sram; return 0; } -/* Specify a cartridge RAM buffer */ -int vbSetSRAM(VB *sim, uint8_t *data, uint32_t size) { - - /* Specifying no SRAM */ - if (data == NULL) { - sim->cart.ram = NULL; - sim->cart.ramSize = 0; - return 0; +/* Specify a game pak ROM buffer */ +VBAPI int vbSetCartROM(VB *sim, void *rom, uint32_t size) { + if (rom != NULL) { + if (size < 16 || size > 0x1000000 || (size & (size - 1)) != 0) + return 1; + sim->cart.romMask = size - 1; } - - /* Error checking */ - if ( - size < 4 || - size > 0x1000000 || - (size & (size - 1)) /* Power of 2 */ - ) return 1; - - /* Register the SRAM data */ - sim->cart.ram = data; - sim->cart.ramSize = size; + sim->cart.rom = rom; return 0; } -/* Write a value to memory */ -void vbWrite(VB *sim, uint32_t address, int type, int32_t value) { +/* Specify a new value for the program counter */ +VBAPI uint32_t vbSetProgramCounter(VB *sim, uint32_t value) { + sim->cpu.operation = CPU_FETCH; + sim->cpu.pc = sim->cpu.nextPC = value & 0xFFFFFFFE; + sim->cpu.step = 0; + return sim->cpu.pc; +} + +/* Specify a new value for a program register */ +VBAPI int32_t vbSetProgramRegister(VB *sim, int index, int32_t value) { + return index < 1 || index > 31 ? 0 : (sim->cpu.program[index] = value); +} + +/* Specify a new value for a system register */ +VBAPI uint32_t vbSetSystemRegister(VB *sim, int index, uint32_t value) { + return index < 0 || index > 31 ? 0 : + cpuSetSystemRegister(sim, index, value, 1); +} + +/* Determine the size of a simulation instance */ +VBAPI size_t vbSizeOf() { + return sizeof (VB); +} + +/* Write a value to the memory bus */ +VBAPI int32_t vbWrite(VB *sim, uint32_t address, int type, int32_t value) { + if (type < 0 || type > 4) + return 0; busWrite(sim, address, type, value, 1); -} - -/* Write multiple values to memory */ -void vbWriteEx(VB *sim, uint32_t address, uint8_t *buffer, uint32_t length) { - while (length--) busWrite(sim, address++, VB_U8, *buffer++, 1); + return vbRead(sim, address, type); } diff --git a/core/vb.h b/core/vb.h index aeedea6..6edc937 100644 --- a/core/vb.h +++ b/core/vb.h @@ -15,38 +15,15 @@ extern "C" { -/**************************** Compilation Control ****************************/ - -/* - VB_LITTLE_ENDIAN - - Accelerates simulated memory reads and writes on hosts with little-endian - byte ordering. If left undefined, a generic implementation is used instead. -*/ - -/* - VB_SIGNED_PROPAGATE - - Accelerates sign extension by assuming the >> operator propagates the sign - bit for signed types and does not for unsigned types. If left undefined, a - generic implementation is used instead. -*/ - - - /********************************* Constants *********************************/ -/* Data types */ -#define VB_S8 0 -#define VB_U8 1 -#define VB_S16 2 -#define VB_U16 3 -#define VB_S32 4 - -/* Register types */ -#define VB_PROGRAM 0 -#define VB_SYSTEM 1 -#define VB_OTHER 2 +/* Callback IDs */ +#define VB_EXCEPTION 0 +#define VB_EXECUTE 1 +#define VB_FETCH 2 +#define VB_FRAME 3 +#define VB_READ 4 +#define VB_WRITE 5 /* System registers */ #define VB_ADTRE 25 @@ -60,157 +37,55 @@ extern "C" { #define VB_PSW 5 #define VB_TKCW 7 -/* Other registers */ -#define VB_PC 0 +/* Memory access data types */ +#define VB_S8 0 +#define VB_U8 1 +#define VB_S16 2 +#define VB_U16 3 +#define VB_S32 4 -/* Callbacks */ -#define VB_ONEXCEPTION 0 -#define VB_ONEXECUTE 1 -#define VB_ONFETCH 2 -#define VB_ONREAD 3 -#define VB_ONWRITE 4 +/* CPU modes */ +#define VB_ACCURACY 0 +#define VB_DEBUG 1 +#define VB_WHOLE 2 /*********************************** Types ***********************************/ -/* Forward references */ +/* Simulation state */ typedef struct VB VB; -/* Memory access descriptor */ -typedef struct { - uint32_t address; /* Memory address */ - uint32_t clocks; /* Number of clocks taken */ - int32_t value; /* Data loaded/to store */ - uint8_t type; /* Data type */ -} VBAccess; - -/* Exception descriptor */ -typedef struct { - uint32_t address; /* Memory address of handler routine */ - uint16_t code; /* Cause code */ - uint8_t cancel; /* Set to cancel the exception */ -} VBException; - -/* Instruction descriptor */ -typedef struct { - uint32_t address; /* Memory address */ - uint16_t code[2]; /* Fetched code units */ - uint8_t size; /* Size in halfwords */ -} VBInstruction; - -/* Breakpoint handlers */ -typedef int (*VBExceptionProc)(VB *, VBException *); -typedef int (*VBExecuteProc )(VB *, VBInstruction *); -typedef int (*VBFetchProc )(VB *, int, VBAccess *); -typedef int (*VBMemoryProc )(VB *, VBAccess *); - -/* Simulation state */ -struct VB { - - /* Game pak */ - struct { - uint8_t *ram; /* (S)RAM data */ - uint8_t *rom; /* ROM data */ - uint32_t ramSize; /* Size of RAM in bytes */ - uint32_t romSize; /* Size of ROM in bytes */ - } cart; - - /* CPU */ - struct { - - /* Main registers */ - int32_t program[32]; /* Program registers */ - uint32_t pc; /* Program counter */ - - /* System registers */ - uint32_t adtre; /* Address trap target address */ - uint32_t eipc; /* Exception return PC */ - uint32_t eipsw; /* Exception return PSW */ - uint32_t fepc; /* Duplexed exception return PC */ - uint32_t fepsw; /* Duplexed exception return PSW */ - uint32_t sr29; /* System register 29 */ - uint32_t sr31; /* System register 31 */ - - /* Exception cause register */ - struct { - uint16_t eicc; /* Exception/interrupt cause code */ - uint16_t fecc; /* Duplexed exception cause code */ - } ecr; - - /* Cache control word */ - struct { - uint8_t ice; /* Instruction cache enable */ - } chcw; - - /* Program status word */ - struct { - uint8_t ae; /* Address trap enable */ - uint8_t cy; /* Carry */ - uint8_t ep; /* Exception pending */ - uint8_t fiv; /* Floating-point invalid operation */ - uint8_t fov; /* Floating-point overflow */ - uint8_t fpr; /* Floating point precision degradation */ - uint8_t fro; /* Floating-point reserved operand */ - uint8_t fud; /* Floating-point underflow */ - uint8_t fzd; /* Floating-point zero division */ - uint8_t i; /* Interrupt level */ - uint8_t id; /* Interrupt disable */ - uint8_t np; /* Duplexed exception pending */ - uint8_t ov; /* Overflow */ - uint8_t s; /* Sign */ - uint8_t z; /* Zero */ - } psw; - - /* Instruction */ - struct { - int32_t aux[9]; /* Auxiliary storage */ - void *def; /* Operation descriptor */ - uint16_t code[2]; /* Fetched code units */ - uint8_t size; /* Size in bytes */ - } inst; - - /* Other state */ - uint32_t clocks; /* Clocks until next activity */ - uint32_t fpFlags; /* Floating-point exception flags */ - uint16_t exception; /* Current exception cause code */ - uint8_t irq[5]; /* Interrupt requests */ - uint8_t bitstring; /* Processing a bit string instruction */ - uint8_t stage; /* Pipeline stage handler */ - uint8_t step; /* General processing step index */ - } cpu; - - /* Breakpoint hooks */ - VBExceptionProc onException; /* Exception raised */ - VBExecuteProc onExecute; /* Instruction execute */ - VBFetchProc onFetch; /* Bus read (fetch) */ - VBMemoryProc onRead; /* Bus read (execute) */ - VBMemoryProc onWrite; /* Bus write */ - - /* Other state */ - uint8_t wram[0x10000]; /* System memory */ -}; +/* Callbacks */ +typedef int (*vbOnExecute)(VB *sim, uint32_t address, const uint16_t *code, int length); +typedef int (*vbOnFetch )(VB *sim, int fetch, uint32_t address, int32_t *value, uint32_t *cycles); +typedef int (*vbOnRead )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles); +typedef int (*vbOnWrite )(VB *sim, uint32_t address, int type, int32_t *value, uint32_t *cycles, int *cancel); -/************************************ API ************************************/ -VBAPI int vbEmulate (VB *sim, uint32_t *clocks); -VBAPI int vbEmulateEx (VB **sims, int count, uint32_t *clocks); -VBAPI void* vbGetCallback(VB *sim, int id); -VBAPI int32_t vbGetRegister(VB *sim, int type, int id); -VBAPI uint8_t* vbGetROM (VB *sim, uint32_t *size); -VBAPI uint8_t* vbGetSRAM (VB *sim, uint32_t *size); -VBAPI void vbInit (VB *sim); -VBAPI int32_t vbRead (VB *sim, uint32_t address, int type); -VBAPI void vbReadEx (VB *sim, uint32_t address, uint8_t *buffer, uint32_t length); -VBAPI void vbReset (VB *sim); -VBAPI void* vbSetCallback(VB *sim, int id, void *proc); -VBAPI int32_t vbSetRegister(VB *sim, int type, int id, int32_t value); -VBAPI int vbSetROM (VB *sim, uint8_t *data, uint32_t size); -VBAPI int vbSetSRAM (VB *sim, uint8_t *data, uint32_t size); -VBAPI void vbWrite (VB *sim, uint32_t address, int type, int32_t value); -VBAPI void vbWriteEx (VB *sim, uint32_t address, uint8_t *buffer, uint32_t length); +/******************************* API Commands ********************************/ + +VBAPI int vbEmulate (VB *sim, uint32_t *clocks); +VBAPI int vbEmulateEx (VB **sims, int count, uint32_t *clocks); +VBAPI void* vbGetCallback (VB *sim, int id); +VBAPI void* vbGetCartRAM (VB *sim, uint32_t *size); +VBAPI void* vbGetCartROM (VB *sim, uint32_t *size); +VBAPI uint32_t vbGetProgramCounter (VB *sim); +VBAPI int32_t vbGetProgramRegister(VB *sim, int index); +VBAPI uint32_t vbGetSystemRegister (VB *sim, int index); +VBAPI VB* vbInit (VB *sim); +VBAPI int32_t vbRead (VB *sim, uint32_t address, int type); +VBAPI VB* vbReset (VB *sim); +VBAPI void* vbSetCallback (VB *sim, int index, void *callback); +VBAPI int vbSetCartRAM (VB *sim, void *sram, uint32_t size); +VBAPI int vbSetCartROM (VB *sim, void *rom, uint32_t size); +VBAPI uint32_t vbSetProgramCounter (VB *sim, uint32_t value); +VBAPI int32_t vbSetProgramRegister(VB *sim, int index, int32_t value); +VBAPI uint32_t vbSetSystemRegister (VB *sim, int index, uint32_t value); +VBAPI size_t vbSizeOf (); +VBAPI int32_t vbWrite (VB *sim, uint32_t address, int type, int32_t value); diff --git a/license.txt b/license.txt index 1b40ee3..48747ee 100644 --- a/license.txt +++ b/license.txt @@ -1,4 +1,4 @@ -Copyright (C) 2023 Guy Perfect +Copyright (C) 2024 Guy Perfect This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/makefile b/makefile index 2f8c750..4b8d91c 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,7 @@ .PHONY: help help: @echo - @echo "Virtual Boy Emulator - March 12, 2023" + @echo "Virtual Boy Emulator - October 10, 2024" @echo @echo "Target build environment is any Debian with the following packages:" @echo " emscripten" diff --git a/web/App.js b/web/App.js deleted file mode 100644 index 5199c16..0000000 --- a/web/App.js +++ /dev/null @@ -1,691 +0,0 @@ -import { Core } from /**/"./core/Core.js"; -import { Debugger } from /**/"./debugger/Debugger.js"; -import { Disassembler } from /**/"./core/Disassembler.js"; -import { Toolkit } from /**/"./toolkit/Toolkit.js"; - -// Front-end emulator application -class App extends Toolkit.App { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(bundle) { - super({ - style: { - display : "grid", - gridTemplateRows: "max-content auto" - }, - visibility: true, - visible : false - }); - - // Configure instance fields - this.bundle = bundle; - this.debugMode = true; - this.dualMode = false; - this.text = null; - } - - async init() { - - // Theme - Object.assign(document.body.style, { margin:"0", overflow:"hidden" }); - this.stylesheet(/**/"web/theme/kiosk.css", false); - this.stylesheet(/**/"web/theme/vbemu.css", false); - this._theme = "auto"; - this.themes = { - dark : this.stylesheet(/**/"web/theme/dark.css" ), - light : this.stylesheet(/**/"web/theme/light.css" ), - virtual: this.stylesheet(/**/"web/theme/virtual.css") - }; - - // Watch for dark mode preference changes - this.isDark = window.matchMedia("(prefers-color-scheme: dark)"); - this.isDark.addEventListener("change", e=>this.onDark()); - this.onDark(); - - // Locales - await this.addLocale(/**/"web/locale/en-US.json"); - for (let id of [].concat(navigator.languages, ["en-US"])) { - if (this.setLocale(id)) - break; - } - this.setTitle("{app.title}", true); - - // Element - document.body.append(this.element); - window.addEventListener("resize", e=>{ - this.element.style.height = window.innerHeight + "px"; - this.element.style.width = window.innerWidth + "px"; - }); - window.dispatchEvent(new Event("resize")); - this.addEventListener("keydown", e=>this.onKeyDown(e)); - - // Menus - this.menuBar = new Toolkit.MenuBar(this); - this.menuBar.setLabel("{menu._}", true); - this.add(this.menuBar); - this.initFileMenu (); - this.initEmulationMenu(); - this.initDebugMenu (0, this.debugMode); - this.initDebugMenu (1, this.debugMode && this.dualMode); - this.initThemeMenu (); - - // Fallback for bubbled key events - document.body.addEventListener("focusout", e=>this.onBlur(e)); - window .addEventListener("keydown" , e=>this.onKey (e)); - window .addEventListener("keyup" , e=>this.onKey (e)); - - // Temporary: Faux game mode display - this.display = new Toolkit.Component(this, { - class : "tk display", - style : { position: "relative" }, - visible: !this.debugMode - }); - this.image1 = new Toolkit.Component(this, { style: { - background: "#000000", - position : "absolute" - }}); - this.display.add(this.image1); - this.image2 = new Toolkit.Component(this, { style: { - background: "#000000", - position : "absolute" - }}); - this.display.add(this.image2); - this.display.addEventListener("resize", e=>this.onDisplay()); - this.add(this.display); - - // Temporary: Faux debug mode display - this.desktop = new Toolkit.Desktop(this, { - visible: this.debugMode - }); - this.add(this.desktop); - - // Emulation core - this.core = await new Core().init(); - let sims = (await this.core.create(2)).sims; - this.core.onsubscription = (k,m)=>this.onSubscription(k, m); - - // Debugging managers - this.dasm = new Disassembler(); - this.debug = new Array(sims.length); - for (let x = 0; x < sims.length; x++) { - let dbg = this.debug[x] = new Debugger(this, sims[x], x); - if (x == 0 && !this.dualMode) { - dbg.cpu .substitute("#", ""); - dbg.memory.substitute("#", ""); - } - this.desktop.add(dbg.cpu); - this.desktop.add(dbg.memory); - } - - // Reveal the application - this.visible = true; - this.restoreFocus(); - - console.log( - "CPU window shortcuts:\n" + - " F11 Single step\n" + - " F10 Run to next\n" + - " Ctrl+B Toggle bytes column\n" + - " Ctrl+F Fit columns\n" + - " Ctrl+G Goto" - ); - } - - // Initialize File menu - initFileMenu() { - let bar = this.menuBar; - let item = bar.file = new Toolkit.MenuItem(this); - item.setText("{menu.file._}"); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = bar.file.loadROM0 = new Toolkit.MenuItem(this); - item.setText("{menu.file.loadROM}"); - item.substitute("#", this.dualMode ? " 1" : "", false); - item.addEventListener("action", e=>this.onLoadROM(0)); - menu.add(item); - - item = bar.file.loadROM1 = new Toolkit.MenuItem(this, - { visible: this.dualMode }); - item.setText("{menu.file.loadROM}"); - item.substitute("#", " 2", false); - item.addEventListener("action", e=>this.onLoadROM(1)); - menu.add(item); - - item = bar.file.dualMode = new Toolkit.MenuItem(this, - { checked: this.dualMode, type: "checkbox" }); - item.setText("{menu.file.dualMode}"); - item.addEventListener("action", e=>this.onDualMode()); - menu.add(item); - - item = bar.file.debugMode = new Toolkit.MenuItem(this, - { checked: this.debugMode, disabled: true, type: "checkbox" }); - item.setText("{menu.file.debugMode}"); - item.addEventListener("action", e=>this.onDebugMode()); - menu.add(item); - - menu.addSeparator(); - - item = new Toolkit.MenuItem(this); - item.setText("Export source...", false); - item.addEventListener("action", ()=>this.bundle.save()); - menu.add(item); - } - - // Initialize Emulation menu - initEmulationMenu() { - let bar = this.menuBar; - let item = bar.emulation = new Toolkit.MenuItem(this); - item.setText("{menu.emulation._}"); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = bar.emulation.run = - new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.emulation.run}"); - menu.add(item); - - item = bar.emulation.reset0 = new Toolkit.MenuItem(this); - item.setText("{menu.emulation.reset}"); - item.substitute("#", this.dualMode ? " 1" : "", false); - item.addEventListener("action", e=>this.onReset(0)); - menu.add(item); - - item = bar.emulation.reset1 = new Toolkit.MenuItem(this, - { visible: this.dualMode }); - item.setText("{menu.emulation.reset}"); - item.substitute("#", " 2", false); - item.addEventListener("action", e=>this.onReset(1)); - menu.add(item); - - item = bar.emulation.linkSims = new Toolkit.MenuItem(this, - { disabled: true, type: "checkbox", visible: this.dualMode }); - item.setText("{menu.emulation.linkSims}"); - menu.add(item); - } - - // Initialize Debug menu - initDebugMenu(index, visible) { - let bar = this.menuBar; - let item = bar["debug" + index] = new Toolkit.MenuItem(this, - { visible: visible }), top = item; - item.setText("{menu.debug._}"); - item.substitute("#", - index == 0 ? this.dualMode ? "1" : "" : " 2", false); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = top.console = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.console}"); - menu.add(item); - - item = top.memory = new Toolkit.MenuItem(this); - item.setText("{menu.debug.memory}"); - item.addEventListener("action", - e=>this.showWindow(this.debug[index].memory)); - menu.add(item); - - item = top.cpu = new Toolkit.MenuItem(this); - item.setText("{menu.debug.cpu}"); - item.addEventListener("action", - e=>this.showWindow(this.debug[index].cpu)); - menu.add(item); - - item=top.breakpoints = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.breakpoints}"); - menu.add(item); - - menu.addSeparator(); - - item = top.palettes = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.palettes}"); - menu.add(item); - - item = top.characters = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.characters}"); - menu.add(item); - - item = top.bgMaps = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.bgMaps}"); - menu.add(item); - - item = top.backgrounds = new Toolkit.MenuItem(this, { disabled:true }); - item.setText("{menu.debug.backgrounds}"); - menu.add(item); - - item = top.objects = new Toolkit.MenuItem(this, { disabled: true }); - item.setText("{menu.debug.objects}"); - menu.add(item); - - item = top.frameBuffers = new Toolkit.MenuItem(this, {disabled: true}); - item.setText("{menu.debug.frameBuffers}"); - menu.add(item); - } - - // Initialize Theme menu - initThemeMenu() { - let bar = this.menuBar; - let item = bar.theme = new Toolkit.MenuItem(this); - item.setText("{menu.theme._}"); - bar.add(item); - - let menu = item.menu = new Toolkit.Menu(this); - - item = bar.theme.auto = new Toolkit.MenuItem(this, - { checked: true, type: "checkbox" }); - item.setText("{menu.theme.auto}"); - item.theme = "auto"; - item.addEventListener("action", e=>this.theme = "auto"); - menu.add(item); - - item = bar.theme.light = new Toolkit.MenuItem(this, - { checked: false, type: "checkbox" }); - item.setText("{menu.theme.light}"); - item.theme = "light"; - item.addEventListener("action", e=>this.theme = "light"); - menu.add(item); - - item = bar.theme.dark = new Toolkit.MenuItem(this, - { checked: false, type: "checkbox" }); - item.setText("{menu.theme.dark}"); - item.theme = "dark"; - item.addEventListener("action", e=>this.theme = "dark"); - menu.add(item); - - item = bar.theme.light = new Toolkit.MenuItem(this, - { checked: false, type: "checkbox" }); - item.setText("{menu.theme.virtual}"); - item.theme = "virtual"; - item.addEventListener("action", e=>this.theme = "virtual"); - menu.add(item); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // All elements have lost focus - onBlur(e) { - if ( - e.relatedTarget == null || - e.relatedTarget == document.body - ) this.restoreFocus(); - } - - // Dark mode preference changed - onDark() { - if (this._theme != "auto") - return; - let isDark = this.isDark.matches; - this.themes.light.disabled = isDark; - this.themes.dark .disabled = !isDark; - } - - // Game mode display resized - onDisplay() { - let bounds = this.display.element.getBoundingClientRect(); - let width = Math.max(1, bounds.width); - let height = Math.max(1, bounds.height); - let scale, x1, y1, x2, y2; - - // Single mode - if (!this.dualMode) { - this.image2.visible = false; - scale = Math.max(1, Math.min( - Math.floor(width / 384), - Math.floor(height / 224) - )); - x1 = Math.max(0, Math.floor((width - 384 * scale) / 2)); - y1 = Math.max(0, Math.floor((height - 224 * scale) / 2)); - x2 = y2 = 0; - } - - // Dual mode - else { - this.image2.visible = true; - - // Horizontal orientation - if (true) { - scale = Math.max(1, Math.min( - Math.floor(width / 768), - Math.floor(height / 224) - )); - let gap = Math.max(0, width - 768 * scale); - gap = gap < 0 ? 0 : Math.floor(gap / 3) + (gap%3==2 ? 1 : 0); - x1 = gap; - x2 = Math.max(384 * scale, width - 384 * scale - gap); - y1 = y2 = Math.max(0, Math.floor((height - 224 * scale) / 2)); - } - - // Vertical orientation - else { - scale = Math.max(1, Math.min( - Math.floor(width / 384), - Math.floor(height / 448) - )); - let gap = Math.max(0, height - 448 * scale); - gap = gap < 0 ? 0 : Math.floor(gap / 3) + (gap%3==2 ? 1 : 0); - x1 = x2 = Math.max(0, Math.floor((width - 384 * scale) / 2)); - y1 = gap; - y2 = Math.max(224 * scale, height - 224 * scale - gap); - } - - } - - width = 384 * scale + "px"; - height = 224 * scale + "px"; - Object.assign(this.image1.element.style, - { left: x1+"px", top: y1+"px", width: width, height: height }); - Object.assign(this.image2.element.style, - { left: x2+"px", top: y2+"px", width: width, height: height }); - } - - // File -> Debug mode - onDebugMode() { - this.debugMode =!this.debugMode; - this.display.visible =!this.debugMode; - this.desktop.visible = this.debugMode; - this.configMenus(); - this.onDisplay(); - } - - // Emulation -> Dual mode - onDualMode() { - this.setDualMode(!this.dualMode); - this.configMenus(); - this.onDisplay(); - } - - // Key press - onKeyDown(e) { - - // Take no action - if (!e.altKey || e.key != "F10" || - this.menuBar.contains(document.activeElement)) - return; - - // Move focus into the menu bar - this.menuBar.focus(); - Toolkit.handle(e); - } - - // File -> Load ROM - async onLoadROM(index) { - - // Add a file picker to the document - let file = document.createElement("input"); - file.type = "file"; - file.style.position = "absolute"; - file.style.visibility = "hidden"; - document.body.appendChild(file); - - // Prompt the user to select a file - await new Promise(resolve=>{ - file.addEventListener("input", resolve); - file.click(); - }); - file.remove(); - - // No file was selected - file = file.files[0]; - if (!file) - return; - - // Load the file - let data = null; - try { data = new Uint8Array(await file.arrayBuffer()); } - catch { - alert(this.localize("{menu.file.loadROMError}")); - return; - } - - // Attempt to process the file as an ISX binary - try { data = Debugger.isx(data).toROM(); } catch { } - - // Error checking - if ( - data.length < 1024 || - data.length > 0x1000000 || - (data.length & data.length - 1) - ) { - alert(this.localize("{menu.file.loadROMInvalid}")); - return; - } - - // Load the ROM into simulation memory - let rep = await this.core.setROM(this.debug[index].sim, data, - { refresh: true }); - if (!rep.success) { - alert(this.localize("{menu.file.loadROMError}")); - return; - } - this.debug[index].followPC(0xFFFFFFF0); - } - - // Emulation -> Reset - async onReset(index) { - await this.core.reset(this.debug[index].sim, { refresh: true }); - this.debug[index].followPC(0xFFFFFFF0); - } - - // Core subscription - onSubscription(key, msg) { - let target = this.debug; // Handler object - for (let x = 1; x < key.length - 1; x++) - target = target[key[x]]; - target[key[key.length - 1]].call(target, msg); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Specify document title - setTitle(title, localize) { - this.setString("text", title, localize); - } - - // Specify the color theme - get theme() { return this._theme; } - set theme(theme) { - switch (theme) { - case "light": case "dark": case "virtual": - this._theme = theme; - for (let entry of Object.entries(this.themes)) - entry[1].disabled = entry[0] != theme; - break; - default: - this._theme = "auto"; - this.themes["virtual"].disabled = true; - this.onDark(); - } - for (let item of this.menuBar.theme.menu.children) - item.checked = item.theme == theme; - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Configure components for automatic localization, or localize a message - localize(a, b) { - - // Default behavior - if (a && a != this) - return super.localize(a, b); - - // Update localization strings - if (this.text != null) { - let text = this.text; - document.title = !text[1] ? text[0] : - this.localize(text[0], this.substitutions); - } - - } - - // Return focus to the most recent focused element - restoreFocus() { - - // Focus was successfully restored - if (super.restoreFocus()) - return true; - - // Select the foremost visible window - let wnd = this.desktop.getActiveWindow(); - if (wnd) { - wnd.focus(); - return true; - } - - // Select the menu bar - this.menuBar.focus(); - return true; - } - - // Perform a Run Next command on one of the simulations - runToNext(index, options) { - let debugs = [ this.debug[index] ]; - if (this.dualMode) - debugs.push(this.debug[index ^ 1]); - - let ret = this.core.runToNext(debugs.map(d=>d.sim), options); - - if (ret instanceof Promise) ret.then(msg=>{ - for (let x = 0; x < debugs.length; x++) - debugs[x].followPC(msg.pcs[x]); - }); - } - - // Perform a Single Step command on one of the simulations - singleStep(index, options) { - let debugs = [ this.debug[index] ]; - if (this.dualMode) - debugs.push(this.debug[index ^ 1]); - - let ret = this.core.singleStep(debugs.map(d=>d.sim), options); - - if (ret instanceof Promise) ret.then(msg=>{ - for (let x = 0; x < debugs.length; x++) - debugs[x].followPC(msg.pcs[x]); - }); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Configure menu item visibility - configMenus() { - let bar = this.menuBar; - bar.file.debugMode .checked = this.debugMode; - bar.file.loadROM1 .visible = this.dualMode; - bar.emulation.reset1 .visible = this.dualMode; - bar.emulation.linkSims.visible = this.dualMode; - bar.debug0 .visible = this.debugMode; - bar.debug1 .visible = this.debugMode && this.dualMode; - } - - // Specify whether dual mode is active - setDualMode(dualMode) { - - // Update state - if (dualMode == this.dualMode) - return; - this.dualMode = dualMode; - - // Working variables - let index = dualMode ? " 1" : ""; - - // Update menus - let bar = this.menuBar; - bar.file.loadROM0 .substitute("#", index, false); - bar.debug0 .substitute("#", index, false); - bar.emulation.reset0.substitute("#", index, false); - bar.file.dualMode .checked = this.dualMode; - - // Update sim 1 debug windows - let dbg = this.debug[0]; - dbg.cpu .substitute("#", index); - dbg.memory.substitute("#", index); - - // Re-show any sim 2 debug windows that were previously visible - if (dualMode) { - for (let wnd of this.desktop.children) { - if (wnd.index == 1 && wnd.wasVisible) - wnd.visible = true; - } - } - - // Hide any visible sim 2 debug windows - else for (let wnd of this.desktop.children) { - if (wnd.index == 0) - continue; - wnd.wasVisible = wnd.visible; - wnd.visible = false; - } - - } - - // Ensure a debugger window is visible - showWindow(wnd) { - let adjust = false; - - // The window is already visible - if (wnd.visible) { - - // The window is already in the foreground - if (wnd == this.desktop.getActiveWindow()) - ;//adjust = true; - - // Bring the window to the foreground - else this.desktop.bringToFront(wnd); - - } - - // The window is not visible - else { - adjust = !wnd.shown; - if (adjust && wnd.firstShow) - wnd.firstShow(); - wnd.visible = true; - this.desktop.bringToFront(wnd); - } - - // Adjust the window position - if (adjust) { - let bounds = this.desktop.element.getBoundingClientRect(); - wnd.left = Math.max(0, (bounds.width - wnd.outerWidth ) / 2); - wnd.top = Math.max(0, (bounds.height - wnd.outerHeight) / 2); - } - - // Transfer focus into the window - wnd.focus(); - } - - // Install a stylesheet. Returns the resulting element - stylesheet(filename, disabled = true) { - let ret = document.createElement("link"); - ret.href = filename; - ret.rel = "stylesheet"; - ret.type = "text/css"; - ret.disabled = disabled; - document.head.append(ret); - return ret; - } - - - - ///////////////////////////// Program Methods ///////////////////////////// - - // Program entry point - static main(bundle) { - new App(bundle).init(); - } - -} - -export { App }; diff --git a/web/Bundle.java b/web/Bundle.java deleted file mode 100644 index 7f72e46..0000000 --- a/web/Bundle.java +++ /dev/null @@ -1,203 +0,0 @@ -import java.awt.image.*; -import java.io.*; -import java.nio.charset.*; -import java.util.*; -import javax.imageio.*; - -public class Bundle { - - /////////////////////////////// BundledFile /////////////////////////////// - - // Individual packaged resource file - static class BundledFile implements Comparable { - - // Instance fields - byte[] data; // File data loaded from disk - File file; // Source file - String filename; // Logical filename - - // Constructor - BundledFile(BundledFile parent, File file) { - - // Configure instance fields - this.file = file; - filename = parent == null ? "" : parent.filename + file.getName(); - - // Load file data if file - if (file.isFile()) { - try (var stream = new FileInputStream(file)) { - data = stream.readAllBytes(); - } catch (Exception e) { } - } - - // Update filename if directory - else if (parent != null) filename += "/"; - } - - // Comparator - public int compareTo(BundledFile o) { - return - filename.equals("web/_boot.js") ? -1 : - o.filename.equals("web/_boot.js") ? +1 : - filename.compareTo(o.filename) - ; - } - - // Produce a list of child files or directories - BundledFile[] listFiles(String name, boolean isDirectory) { - - // Produce a filtered list of files - var files = this.file.listFiles(f->{ - - // Does not satisfy the directory requirement - if (f.isDirectory() != isDirectory) - return false; - - // Current directory is not root - if (!filename.equals("")) - return true; - - // Filter specific files from being bundled - String filename = f.getName(); - return !( - filename.startsWith(".git" ) || - filename.startsWith(name + "_") && - filename.endsWith (".html" ) - ); - }); - - // Process all files for bundling - var ret = new BundledFile[files.length]; - for (int x = 0; x < files.length; x++) - ret[x] = new BundledFile(this, files[x]); - return ret; - } - - } - - - - ///////////////////////////// Program Methods ///////////////////////////// - - // Program entry point - public static void main(String[] args) { - String name = name(args[0]); - var files = listFiles(args[0]); - var prepend = prepend(name, files); - var bundle = bundle(prepend, files); - var image = image(bundle); - var url = url(image); - patch(name, url); - } - - // Produce a buffer of the bundled files - static byte[] bundle(byte[] prepend, BundledFile[] files) { - try (var stream = new ByteArrayOutputStream()) { - stream.write(prepend); - stream.write(files[0].data); // web/_boot.js - stream.write(0); - for (int x = 1; x < files.length; x++) - stream.write(files[x].data); - return stream.toByteArray(); - } catch (Exception e) { return null; } - } - - // Convert a bundle buffer into a PNG-encoded image buffer - static byte[] image(byte[] bundle) { - int width = (int) Math.ceil(Math.sqrt(bundle.length)); - int height = (bundle.length + width - 1) / width; - var pixels = new int[width * height]; - - // Encode the buffer as a pixel array - for (int x = 0; x < bundle.length; x++) { - int b = bundle[x] & 0xFF; - pixels[x] = 0xFF000000 | b << 16 | b << 8 | b; - } - - // Produce an image using the pixels - var image = new BufferedImage( - width, height, BufferedImage.TYPE_INT_RGB); - image.setRGB(0, 0, width, height, pixels, 0, width); - - // Encode the image as a PNG buffer - try (var stream = new ByteArrayOutputStream()) { - ImageIO.write(image, "png", stream); - return stream.toByteArray(); - } catch (Exception e) { return null; } - } - - // List all files - static BundledFile[] listFiles(String name) { - var dirs = new ArrayList(); - var files = new ArrayList(); - - // Propagate down the file system tree - dirs.add(new BundledFile(null, new File("."))); - while (!dirs.isEmpty()) { - var dir = dirs.remove(0); - for (var sub : dir.listFiles(name, true )) - dirs.add(sub ); - for (var file : dir.listFiles(name, false)) - files.add(file); - } - - // Return the set of files as a sorted array - Collections.sort(files); - return files.toArray(new BundledFile[files.size()]); - } - - // Generate a filename for the bundle - static String name(String name) { - var calendar = Calendar.getInstance(); - return String.format("%s_%04d%02d%02d", - name, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH) + 1, - calendar.get(Calendar.DAY_OF_MONTH) - ); - } - - // Produce the output HTML from the template - static void patch(String name, String url) { - String markup = null; - try (var stream = new FileInputStream("web/template.html")) { - markup = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - } catch (Exception e) { } - markup = markup.replace("src=\"\"", "src=\"" + url + "\""); - try (var stream = new FileOutputStream(name + ".html")) { - stream.write(markup.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { } - } - - // Produce source data to prepend to web/_boot.js - static byte[] prepend(String name, BundledFile[] files) { - var ret = new StringBuilder(); - - // Arguments - ret.append("let " + - "buffer=arguments[0]," + - "image=arguments[1]," + - "name=\"" + name + "\"" + - ";"); - - // Bundle manifest - ret.append("let manifest=["); - for (var file : files) { - if (file != files[0]) - ret.append(","); - ret.append("[\"" + - file.filename + "\", " + file.data.length + "]"); - } - ret.append("];"); - - // Convert to byte array - return ret.toString().getBytes(StandardCharsets.UTF_8); - } - - // Convert an image buffer to a data URL - static String url(byte[] image) { - return "data:image/png;base64," + - Base64.getMimeEncoder(0, new byte[0]).encodeToString(image); - } - -} diff --git a/web/_boot.js b/web/_boot.js deleted file mode 100644 index 591d8ec..0000000 --- a/web/_boot.js +++ /dev/null @@ -1,310 +0,0 @@ -// Running as an async function -// Prepended by Bundle.java: buffer, image, manifest, name - - - -/////////////////////////////////////////////////////////////////////////////// -// Bundle // -/////////////////////////////////////////////////////////////////////////////// - -// Resource manager for bundled files -class Bundle extends Array { - - //////////////////////////////// Constants //////////////////////////////// - - // .ZIP support - static CRC_LOOKUP = new Uint32Array(256); - - // Text processing - static DECODER = new TextDecoder(); - static ENCODER = new TextEncoder(); - - static initializer() { - - // Generate the CRC32 lookup table - for (let x = 0; x <= 255; x++) { - let l = x; - for (let j = 7; j >= 0; j--) - l = ((l >>> 1) ^ (0xEDB88320 & -(l & 1))); - this.CRC_LOOKUP[x] = l; - } - - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(name, url, settings, isDebug) { - super(); - this.isDebug = isDebug; - this.name = name; - this.settings = settings; - this.url = url; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Export all bundled resources to a .ZIP file - save() { - let centrals = new Array(this.length); - let locals = new Array(this.length); - let offset = 0; - let size = 0; - - // Encode file and directory entries - for (let x = 0; x < this.length; x++) { - let file = this[x]; - let sum = Bundle.crc32(file.data); - locals [x] = file.toZipHeader(sum); - centrals[x] = file.toZipHeader(sum, offset); - offset += locals [x].length; - size += centrals[x].length; - } - - // Encode end of central directory - let end = []; - Bundle.writeInt(end, 4, 0x06054B50); // Signature - Bundle.writeInt(end, 2, 0); // Disk number - Bundle.writeInt(end, 2, 0); // Central dir start disk - Bundle.writeInt(end, 2, this.length); // # central dir this disk - Bundle.writeInt(end, 2, this.length); // # central dir total - Bundle.writeInt(end, 4, size); // Size of central dir - Bundle.writeInt(end, 4, offset); // Offset of central dir - Bundle.writeInt(end, 2, 0); // .ZIP comment length - - // Prompt the user to save the resulting file - let a = document.createElement("a"); - a.download = this.name + ".zip"; - a.href = URL.createObjectURL(new Blob( - locals.concat(centrals).concat([Uint8Array.from(end)]), - { type: "application/zip" } - )); - a.style.visibility = "hidden"; - document.body.appendChild(a); - a.click(); - a.remove(); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Add a BundledFile to the collection - add(file) { - file.bundle = this; - this.push(this[file.name] = file); - } - - // Write a byte array into an output buffer - static writeBytes(data, bytes) { - for (let b of bytes) - data.push(b); - } - - // Write an integer into an output buffer - static writeInt(data, size, value) { - for (; size > 0; size--, value >>= 8) - data.push(value & 0xFF); - } - - // Write a string of text as bytes into an output buffer - static writeString(data, text) { - this.writeBytes(data, this.ENCODER.encode(text)); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Calculate the CRC32 checksum for a byte array - static crc32(data) { - let c = 0xFFFFFFFF; - for (let x = 0; x < data.length; x++) - c = ((c >>> 8) ^ this.CRC_LOOKUP[(c ^ data[x]) & 0xFF]); - return ~c & 0xFFFFFFFF; - } - -} -Bundle.initializer(); - - - -/////////////////////////////////////////////////////////////////////////////// -// BundledFile // -/////////////////////////////////////////////////////////////////////////////// - -// Individual file in the bundled data -class BundledFile { - - //////////////////////////////// Constants //////////////////////////////// - - // MIME types - static MIMES = { - ".css" : "text/css;charset=UTF-8", - ".frag" : "text/plain;charset=UTF-8", - ".js" : "text/javascript;charset=UTF-8", - ".json" : "application/json;charset=UTF-8", - ".png" : "image/png", - ".svg" : "image/svg+xml;charset=UTF-8", - ".txt" : "text/plain;charset=UTF-8", - ".vert" : "text/plain;charset=UTF-8", - ".wasm" : "application/wasm", - ".woff2": "font/woff2" - }; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(name, buffer, offset, length) { - - // Configure instance fields - this.data = buffer.slice(offset, offset + length); - this.name = name; - - // Resolve the MIME type - let index = name.lastIndexOf("."); - this.mime = index != -1 && BundledFile.MIMES[name.substring(index)] || - "application/octet-stream"; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Represent the file with a blob URL - toBlobURL() { - return this.blobURL || (this.blobURL = URL.createObjectURL( - new Blob([ this.data ], { type: this.mime }))); - } - - // Encode the file data as a data URL - toDataURL() { - return "data:" + this.mime + ";base64," + btoa( - Array.from(this.data).map(b=>String.fromCharCode(b)).join("")); - } - - // Pre-process URLs in a bundled file's contents - toProcURL(asDataURL = false) { - - // The URL has already been computed - if (this.url) - return this.url; - - // Working variables - let content = this.toString(); - let pattern = /\/\*\*?\*\//g; - let parts = content.split(pattern); - let ret = [ parts.shift() ]; - - // Process all URLs prefixed with /**/ or /***/ - for (let part of parts) { - let start = part.indexOf("\""); - let end = part.indexOf("\"", start + 1); - let filename = part.substring(start + 1, end); - let asData = pattern.exec(content)[0] == "/***/"; - - // Relative to current file - if (filename.startsWith(".")) { - let path = this.name.split("/"); - path.pop(); // Current filename - - // Navigate to the path of the target file - for (let dir of filename.split("/")) { - switch (dir) { - case "..": path.pop(); // Fallthrough - case "." : break; - default : path.push(dir); - } - } - - // Produce the fully-qualified filename - filename = path.join("/"); - } - - // Append the file as a data URL - let file = this.bundle[filename]; - ret.push( - part.substring(0, start + 1), - file[ - file.mime.startsWith("text/javascript") || - file.mime.startsWith("text/css") ? - "toProcURL" : asData ? "toDataURL" : "toBlobURL" - ](asData), - part.substring(end) - ); - } - - // Represent the transformed source as a URL - return this.url = asDataURL ? - "data:" + this.mime + ";base64," + btoa(ret.join("")) : - URL.createObjectURL(new Blob(ret, { type: this.mime })) - ; - } - - // Decode the file data as a UTF-8 string - toString() { - return Bundle.DECODER.decode(this.data); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Produce a .ZIP header for export - toZipHeader(crc32, offset) { - let central = offset !== undefined; - let ret = []; - if (central) { - Bundle.writeInt (ret, 4, 0x02014B50); // Signature - Bundle.writeInt (ret, 2, 20); // Version created by - } else - Bundle.writeInt (ret, 4, 0x04034B50); // Signature - Bundle.writeInt (ret, 2, 20); // Version required - Bundle.writeInt (ret, 2, 0); // Bit flags - Bundle.writeInt (ret, 2, 0); // Compression method - Bundle.writeInt (ret, 2, 0); // Modified time - Bundle.writeInt (ret, 2, 0); // Modified date - Bundle.writeInt (ret, 4, crc32); // Checksum - Bundle.writeInt (ret, 4, this.data.length); // Compressed size - Bundle.writeInt (ret, 4, this.data.length); // Uncompressed size - Bundle.writeInt (ret, 2, this.name.length); // Filename length - Bundle.writeInt (ret, 2, 0); // Extra field length - if (central) { - Bundle.writeInt (ret, 2, 0); // File comment length - Bundle.writeInt (ret, 2, 0); // Disk number start - Bundle.writeInt (ret, 2, 0); // Internal attributes - Bundle.writeInt (ret, 4, 0); // External attributes - Bundle.writeInt (ret, 4, offset); // Relative offset - } - Bundle.writeString (ret, this.name); // Filename - if (!central) - Bundle.writeBytes(ret, this.data); // File data - return Uint8Array.from(ret); - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// -// Boot Program // -/////////////////////////////////////////////////////////////////////////////// - -// Produce the application bundle -let bundle = new Bundle(name, image.src, image.getAttribute("settings") || "", - location.protocol != "file:" && location.hash == "#debug"); -for (let x=0,offset=buffer.indexOf(0)-manifest[0][1]; xthis.init(m.data); - } - - async init(main) { - - // Configure message ports - this.core = this.port; - this.core.onmessage = m=>this.onCore(m.data); - this.main = main; - this.main.onmessage = m=>this.onMain(m.data); - - // Notify main thread - this.port.postMessage(0); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Produce output samples (called by the user agent) - process(inputs, outputs, parameters) { - let output = outputs[0]; - let length = output [0].length; - let empty = null; - - // Process all samples - for (let x = 0; x < length;) { - - // No bufferfed samples are available - if (this.buffers.length == 0) { - for (; x < length; x++) - output[0] = output[1] = 0; - break; - } - - // Transfer samples from the oldest buffer - let y, buffer = this.buffers[0]; - for (y = this.offset; x < length && y < buffer.length; x++, y+=2) { - output[0][x] = buffer[y ]; - output[1][x] = buffer[y + 1]; - } - - // Advance to the next buffer - if ((this.offset = y) == buffer.length) { - if (empty == null) - empty = []; - empty.push(this.buffers.shift()); - this.offset = 0; - } - - } - - // Return emptied sample buffers to the core thread - if (empty != null) - this.core.postMessage(empty, empty.map(e=>e.buffer)); - - return true; - } - - - - ///////////////////////////// Message Methods ///////////////////////////// - - // Message received from core thread - onCore(msg) { - } - - // Message received from main thread - onMain(msg) { - } - -} -registerProcessor("AudioThread", AudioThread); diff --git a/web/core/Core.js b/web/core/Core.js deleted file mode 100644 index 98350cd..0000000 --- a/web/core/Core.js +++ /dev/null @@ -1,292 +0,0 @@ -// Interface between application and WebAssembly worker thread -class Core { - - //////////////////////////////// Constants //////////////////////////////// - - /* Register types */ - static VB_PROGRAM = 0; - static VB_SYSTEM = 1; - static VB_OTHER = 2; - - /* System registers */ - static VB_ADTRE = 25; - static VB_CHCW = 24; - static VB_ECR = 4; - static VB_EIPC = 0; - static VB_EIPSW = 1; - static VB_FEPC = 2; - static VB_FEPSW = 3; - static VB_PIR = 6; - static VB_PSW = 5; - static VB_TKCW = 7; - - /* Other registers */ - static VB_PC = 0; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor() { - - // Configure instance fields - this.promises = []; - - } - - async init(coreUrl, wasmUrl, audioUrl) { - - // Open audio output stream - this.audio = new AudioContext({ - latencyHint: "interactive", - sampleRate : 41700 - }); - await this.audio.suspend(); - - // Launch the audio thread - await this.audio.audioWorklet.addModule( - Core.url(audioUrl, "AudioThread.js", /***/"./AudioThread.js")); - let node = new AudioWorkletNode(this.audio, "AudioThread", { - numberOfInputs : 0, - numberOfOutputs : 1, - outputChannelCount: [2] - }); - node.connect(this.audio.destination); - - // Attach a second MessagePort to the audio thread - let channel = new MessageChannel(); - this.audio.port = channel.port1; - await new Promise(resolve=>{ - node.port.onmessage = resolve; - node.port.postMessage(channel.port2, [channel.port2]); - }); - this.audio.port.onmessage = m=>this.onAudio(m.data); - - // Launch the core thread - this.core = new Worker( - Core.url(wasmUrl, "CoreThread.js", /***/"./CoreThread.js")); - await new Promise(resolve=>{ - this.core.onmessage = resolve; - this.core.postMessage({ - audio : node.port, - wasmUrl: Core.url(wasmUrl, "core.wasm", /***/"./core.wasm") - }, [node.port]); - }); - this.core.onmessage = m=>this.onCore(m.data); - - return this; - } - - - - ///////////////////////////// Static Methods ////////////////////////////// - - // Select a URL in the same path as the current script - static url(arg, name, bundled) { - - // The input argument was provided - if (arg) - return arg; - - // Running from a bundle distribution - if (bundled.startsWith("blob:") || bundled.startsWith("data:")) - return bundled; - - // Compute the URL for the given filename - let url = new URL(import.meta.url).pathname; - return url.substring(0, url.lastIndexOf("/") + 1) + name; - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Message received from audio thread - onAudio(msg) { - } - - // Message received from core thread - onCore(msg) { - - // Process subscriptions - if (msg.subscriptions && this.onsubscription instanceof Function) { - for (let sub of msg.subscriptions) { - let key = sub.subscription; - delete sub.subscription; - this.onsubscription(key, sub, this); - } - delete msg.subscriptions; - } - - // The main thread is waiting on a reply - if (msg.isReply) { - delete msg.isReply; - - // For "create", produce sim objects - if (msg.isCreate) { - delete msg.isCreate; - msg.sims = msg.sims.map(s=>({ pointer: s })); - } - - // Notify the caller - this.promises.shift()(msg); - } - - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Create and initialize simulations - create(count, options) { - return this.message({ - command: "create", - count : count - }, [], options); - } - - // Delete a simulation - delete(sim, options) { - return this.message({ - command: "delete", - sim : sim.pointer - }, [], options); - } - - // Retrieve the value of all CPU registers - getAllRegisters(sim, options) { - return this.message({ - command: "getAllRegisters", - sim : sim.pointer - }, [], options); - } - - // Retrieve the value of a register - getRegister(sim, type, id, options) { - return this.message({ - command: "getRegister", - id : id, - sim : sim.pointer, - type : type - }, [], options); - } - - // Read multiple bytes from memory - read(sim, address, length, options) { - return this.message({ - command: "read", - address: address, - length : length, - sim : sim.pointer - }, [], options); - } - - // Refresh subscriptions - refresh(subscriptions = null, options) { - return this.message({ - command : "refresh", - subscriptions: subscriptions - }, [], options); - } - - // Simulate a hardware reset - reset(sim, options) { - return this.message({ - command: "reset", - sim : sim.pointer - }, [], options); - } - - // Execute until the next current instruction - runToNext(sims, options) { - return this.message({ - command: "runToNext", - sims : Array.isArray(sims) ? - sims.map(s=>s.pointer) : [ sims.pointer ] - }, [], options); - } - - // Specify a value for a register - setRegister(sim, type, id, value, options) { - return this.message({ - command: "setRegister", - id : id, - sim : sim.pointer, - type : type, - value : value - }, [], options); - } - - // Specify a cartridge ROM buffer - setROM(sim, data, options = {}) { - data = data.slice(); - return this.message({ - command: "setROM", - data : data, - reset : !("reset" in options) || !!options.reset, - sim : sim.pointer - }, [data.buffer], options); - } - - // Execute the current instruction - singleStep(sims, options) { - return this.message({ - command: "singleStep", - sims : Array.isArray(sims) ? - sims.map(s=>s.pointer) : [ sims.pointer ] - }, [], options); - } - - // Cancel a subscription - unsubscribe(subscription, options) { - return this.message({ - command : "unsubscribe", - subscription: subscription - }, [], options); - } - - // Write multiple bytes to memory - write(sim, address, data, options) { - data = data.slice(); - return this.message({ - address: address, - command: "write", - data : data, - sim : sim.pointer - }, [data.buffer], options); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Send a message to the core thread - message(msg, transfers, options = {}) { - - // Configure options - if (!(options instanceof Object)) - options = { reply: options }; - if (!("reply" in options) || options.reply) - msg.reply = true; - if ("refresh" in options) - msg.refresh = options.refresh; - if ("subscription" in options) - msg.subscription = options.subscription; - if ("tag" in options) - msg.tag = options.tag; - - // Send the command to the core thread - return msg.reply ? - new Promise(resolve=>{ - this.promises.push(resolve); - this.core.postMessage(msg, transfers); - }) : - this.core.postMessage(msg, transfers); - ; - - } - -} - -export { Core }; diff --git a/web/core/CoreThread.js b/web/core/CoreThread.js deleted file mode 100644 index 7b029b1..0000000 --- a/web/core/CoreThread.js +++ /dev/null @@ -1,338 +0,0 @@ -"use strict"; - -/* Register types */ -const VB_PROGRAM = 0; -const VB_SYSTEM = 1; -const VB_OTHER = 2; - -/* System registers */ -const VB_ADTRE = 25; -const VB_CHCW = 24; -const VB_ECR = 4; -const VB_EIPC = 0; -const VB_EIPSW = 1; -const VB_FEPC = 2; -const VB_FEPSW = 3; -const VB_PIR = 6; -const VB_PSW = 5; -const VB_TKCW = 7; - -/* Other registers */ -const VB_PC = 0; - -// Dedicated emulation thread -class CoreThread { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor() { - - // Configure instance fields - this.subscriptions = new Map(); - - // Wait for initializer message from parent thread - onmessage = m=>this.init(m.data.audio, m.data.wasmUrl); - } - - async init(audio, wasmUrl) { - - // Configure message ports - this.audio = audio; - this.audio.onmessage = m=>this.onAudio (m.data); - this.main = globalThis; - this.main .onmessage = m=>this.onMessage(m.data); - - // Load and instantiate the WebAssembly module - this.wasm = (await WebAssembly.instantiateStreaming( - fetch(wasmUrl), { - env: { emscripten_notify_memory_growth: ()=>this.onGrowth() } - })).instance; - this.onGrowth(); - this.pointerSize = this.PointerSize(); - this.pointerType = this.pointerSize == 8 ? Uint64Array : Uint32Array; - - // Notify main thread - this.main.postMessage(0); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Message received from audio thread - onAudio(frames) { - - // Audio processing was suspended - if (frames == 0) { - return; - } - - // Wait for more frames - this.audio.postMessage(0); - } - - // Emscripten has grown the linear memory - onGrowth() { - Object.assign(this, this.wasm.exports); - } - - // Message received from main thread - onMessage(msg) { - - // Subscribe to the command - if (msg.subscription && msg.command != "refresh") - this.subscriptions.set(CoreThread.key(msg.subscription), msg); - - // Process the command - let rep = this[msg.command](msg); - - // Do not send a reply - if (!msg.reply) - return; - - // Configure the reply - if (!rep) - rep = {}; - if (msg.reply) - rep.isReply = true; - if ("tag" in msg) - rep.tag = msg.tag; - - // Send the reply to the main thread - let transfers = rep.transfers; - if (transfers) - delete rep.transfers; - this.main.postMessage(rep, transfers || []); - - // Refresh subscriptions - if (msg.refresh && msg.command != "refresh") { - let subs = {}; - if (Array.isArray(msg.refresh)) - subs.subscriptions = msg.refresh; - this.refresh(subs); - } - - } - - - - //////////////////////////////// Commands ///////////////////////////////// - - // Create and initialize a new simulation - create(msg) { - let sims = new Array(msg.count); - for (let x = 0; x < msg.count; x++) - sims[x] = this.Create(); - return { - isCreate: true, - sims : sims - }; - } - - // Delete all memory used by a simulation - delete(msg) { - this.Delete(msg.sim); - } - - // Retrieve the values of all CPU registers - getAllRegisters(msg) { - let program = new Int32Array (32); - let system = new Uint32Array(32); - for (let x = 0; x < 32; x++) { - program[x] = this.vbGetRegister(msg.sim, 0, x); - system [x] = this.vbGetRegister(msg.sim, 1, x); - } - return { - pc : this.vbGetRegister(msg.sim, 2, 0) >>> 0, - program : program, - system : system, - transfers: [ program.buffer, system.buffer ] - }; - } - - // Retrieve the value of a register - getRegister(msg) { - let value = this.vbGetRegister(msg.sim, msg.type, msg.id); - if (msg.type != VB_PROGRAM) - value >>>= 0; - return { value: value }; - } - - // Read multiple bytes from memory - read(msg) { - let buffer = this.malloc(msg.length); - this.vbReadEx(msg.sim, msg.address, buffer.pointer, msg.length); - let data = buffer.slice(); - this.free(buffer); - return { - address : msg.address, - data : data, - transfers: [data.buffer] - }; - } - - // Process subscriptions - refresh(msg) { - let subscriptions = []; - let transfers = []; - - // Select the key set to refresh - let keys = Array.isArray(msg.subscriptions) ? - msg.subscriptions.map(s=>CoreThread.key(s)) : - this.subscriptions.keys() - ; - - // Process all subscriptions - for (let key of keys) { - - // Process the subscription - let sub = this.subscriptions.get(key); - let rep = this[sub.command](sub); - - // There is no result - if (!rep) - continue; - - // Add the result to the response - rep.subscription = sub.subscription; - if ("tag" in sub) - rep.tag = sub.tag; - subscriptions.push(rep); - - // Add the transfers to the response - if (!rep.transfers) - continue; - transfers = transfers.concat(rep.transfers); - delete rep.transfers; - } - - // Send the response to the main thread - if (subscriptions.length == 0) - return; - this.main.postMessage({ - subscriptions: subscriptions.sort(CoreThread.REFRESH_ORDER) - }, transfers); - } - - // Simulate a hardware reset - reset(msg) { - this.vbReset(msg.sim); - } - - // Execute until the next current instruction - runToNext(msg) { - let sims = this.malloc(msg.sims.length, true); - for (let x = 0; x < msg.sims.length; x++) - sims[x] = msg.sims[x]; - this.RunToNext(sims.pointer, msg.sims.length); - this.free(sims); - - let pcs = new Array(msg.sims.length); - for (let x = 0; x < msg.sims.length; x++) - pcs[x] = this.vbGetRegister(msg.sims[x], 2, 0) >>> 0; - - return { pcs: pcs }; - } - - // Specify a value for a register - setRegister(msg) { - let value = this.vbSetRegister(msg.sim, msg.type, msg.id, msg.value); - if (msg.type != VB_PROGRAM) - value >>>= 0; - return { value: value }; - } - - // Specify a cartridge ROM buffer - setROM(msg) { - let prev = this.vbGetROM(msg.sim, 0); - let success = true; - - // Specify a new ROM - if (msg.data != null) { - let data = this.malloc(msg.data.length); - for (let x = 0; x < data.length; x++) - data[x] = msg.data[x]; - success = !this.vbSetROM(msg.sim, data.pointer, data.length); - } - - // Operation was successful - if (success) { - - // Delete the previous ROM - this.Free(prev); - - // Reset the simulation - if (msg.reset) - this.vbReset(msg.sim); - } - - return { success: success }; - } - - // Execute the current instruction - singleStep(msg) { - let sims = this.malloc(msg.sims.length, true); - for (let x = 0; x < msg.sims.length; x++) - sims[x] = msg.sims[x]; - this.SingleStep(sims.pointer, msg.sims.length); - this.free(sims); - - let pcs = new Array(msg.sims.length); - for (let x = 0; x < msg.sims.length; x++) - pcs[x] = this.vbGetRegister(msg.sims[x], 2, 0) >>> 0; - - return { pcs: pcs }; - } - - // Delete a subscription - unsubscribe(msg) { - this.subscriptions.delete(CoreThread.key(msg.subscription)); - } - - // Write multiple bytes to memory - write(msg) { - let data = this.malloc(msg.data.length); - for (let x = 0; x < data.length; x++) - data[x] = msg.data[x]; - this.vbWriteEx(msg.sim, msg.address, data.pointer, data.length); - this.free(data); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Delete a byte array in WebAssembly memory - free(buffer) { - this.Free(buffer.pointer); - } - - // Format a subscription key as a string - static key(subscription) { - return subscription.map(k=>k.toString()).join("\n"); - } - - // Allocate a byte array in WebAssembly memory - malloc(length, pointers = false) { - let size = pointers ? length * this.pointerSize : length; - return this.map(this.Malloc(size), length, pointers); - } - - // Map a typed array into WebAssembly memory - map(address, length, pointers = false) { - let ret = new (pointers ? this.pointerType : Uint8Array) - (this.memory.buffer, address, length); - ret.pointer = address; - return ret; - } - - // Comparator for subscriptions within the refresh command - static REFRESH_ORDER(a, b) { - a = a.subscription[0]; - b = b.subscription[0]; - return a < b ? -1 : a > b ? 1 : 0; - } - -} - -new CoreThread(); diff --git a/web/core/Disassembler.js b/web/core/Disassembler.js deleted file mode 100644 index 8149762..0000000 --- a/web/core/Disassembler.js +++ /dev/null @@ -1,546 +0,0 @@ -// Machine code to human readable text converter -class Disassembler { - - //////////////////////////////// Constants //////////////////////////////// - - // Default settings - static DEFAULTS = { - condCL : "L", // Use C/NC or L/NL for conditions - condEZ : "E", // Use E/NE or Z/NZ for conditions - condNames : true, // Use condition names - condUppercase: false, // Condition names uppercase - hexPrefix : "0x", // Hexadecimal prefix - hexSuffix : "", // Hexadecimal suffix - hexUppercase : true, // Hexadecimal uppercase - instUppercase: true, // Mnemonics uppercase - jumpAddress : true, // Jump/branch shows target address - memInside : false, // Use [reg1 + disp] notation - opDestFirst : false, // Destination operand first - proNames : true, // Use program register names - proUppercase : false, // Program register names uppercase - splitBcond : false, // BCOND condition as an operand - splitSetf : true, // SETF condition as an operand - sysNames : true, // Use system register names - sysUppercase : false // System register names uppercase - }; - - - - /////////////////////////// Disassembly Lookup //////////////////////////// - - // Opcode descriptors - static OPDEFS = [ - [ "MOV" , [ "opReg1" , "opReg2" ] ], // 000000 - [ "ADD" , [ "opReg1" , "opReg2" ] ], - [ "SUB" , [ "opReg1" , "opReg2" ] ], - [ "CMP" , [ "opReg1" , "opReg2" ] ], - [ "SHL" , [ "opReg1" , "opReg2" ] ], - [ "SHR" , [ "opReg1" , "opReg2" ] ], - [ "JMP" , [ "opReg1Ind" ] ], - [ "SAR" , [ "opReg1" , "opReg2" ] ], - [ "MUL" , [ "opReg1" , "opReg2" ] ], - [ "DIV" , [ "opReg1" , "opReg2" ] ], - [ "MULU" , [ "opReg1" , "opReg2" ] ], - [ "DIVU" , [ "opReg1" , "opReg2" ] ], - [ "OR" , [ "opReg1" , "opReg2" ] ], - [ "AND" , [ "opReg1" , "opReg2" ] ], - [ "XOR" , [ "opReg1" , "opReg2" ] ], - [ "NOT" , [ "opReg1" , "opReg2" ] ], - [ "MOV" , [ "opImm5S", "opReg2" ] ], // 010000 - [ "ADD" , [ "opImm5S", "opReg2" ] ], - null, // SETF: special - [ "CMP" , [ "opImm5S", "opReg2" ] ], - [ "SHL" , [ "opImm5U", "opReg2" ] ], - [ "SHR" , [ "opImm5U", "opReg2" ] ], - [ "CLI" , [ ] ], - [ "SAR" , [ "opImm5U", "opReg2" ] ], - [ "TRAP" , [ "opImm5U" ] ], - [ "RETI" , [ ] ], - [ "HALT" , [ ] ], - null, // Invalid - [ "LDSR" , [ "opReg2" , "opSys" ] ], - [ "STSR" , [ "opSys" , "opReg2" ] ], - [ "SEI" , [ ] ], - null, // Bit string: special - null, // BCOND: special // 100000 - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - null, // BCOND: special - [ "MOVEA", [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "ADDI" , [ "opImm16S" , "opReg1", "opReg2" ] ], - [ "JR" , [ "opDisp26" ] ], - [ "JAL" , [ "opDisp26" ] ], - [ "ORI" , [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "ANDI" , [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "XORI" , [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "MOVHI", [ "opImm16U" , "opReg1", "opReg2" ] ], - [ "LD.B" , [ "opReg1Disp", "opReg2" ] ], // 110000 - [ "LD.H" , [ "opReg1Disp", "opReg2" ] ], - null, // Invalid - [ "LD.W" , [ "opReg1Disp", "opReg2" ] ], - [ "ST.B" , [ "opReg2" , "opReg1Disp" ] ], - [ "ST.H" , [ "opReg2" , "opReg1Disp" ] ], - null, // Invalid - [ "ST.W" , [ "opReg2" , "opReg1Disp" ] ], - [ "IN.B" , [ "opReg1Disp", "opReg2" ] ], - [ "IN.H" , [ "opReg1Disp", "opReg2" ] ], - [ "CAXI" , [ "opReg1Disp", "opReg2" ] ], - [ "IN.W" , [ "opReg1Disp", "opReg2" ] ], - [ "OUT.B", [ "opReg2" , "opReg1Disp" ] ], - [ "OUT.H", [ "opReg2" , "opReg1Disp" ] ], - null, // Floating-point/Nintendo: special - [ "OUT.W", [ "opReg2" , "opReg1Disp" ] ] - ]; - - // Bit string sub-opcode descriptors - static BITSTRING = [ - "SCH0BSU", "SCH0BSD", "SCH1BSU", "SCH1BSD", - null , null , null , null , - "ORBSU" , "ANDBSU" , "XORBSU" , "MOVBSU" , - "ORNBSU" , "ANDNBSU", "XORNBSU", "NOTBSU" , - null , null , null , null , - null , null , null , null , - null , null , null , null , - null , null , null , null - ]; - - // Floating-point/Nintendo sub-opcode descriptors - static FLOATENDO = [ - [ "CMPF.S" , [ "opReg1", "opReg2" ] ], - null, // Invalid - [ "CVT.WS" , [ "opReg1", "opReg2" ] ], - [ "CVT.SW" , [ "opReg1", "opReg2" ] ], - [ "ADDF.S" , [ "opReg1", "opReg2" ] ], - [ "SUBF.S" , [ "opReg1", "opReg2" ] ], - [ "MULF.S" , [ "opReg1", "opReg2" ] ], - [ "DIVF.S" , [ "opReg1", "opReg2" ] ], - [ "XB" , [ "opReg2" ] ], - [ "XH" , [ "opReg2" ] ], - [ "REV" , [ "opReg1", "opReg2" ] ], - [ "TRNC.SW", [ "opReg1", "opReg2" ] ], - [ "MPYHW" , [ "opReg1", "opReg2" ] ], - null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null - ]; - - // Condition mnemonics - static CONDITIONS = [ - "V" , "C" , "E" , "NH", "N", "T", "LT", "LE", - "NV", "NC", "NE", "H" , "P", "F", "GE", "GT" - ]; - - // Program register names - static REG_PROGRAM = [ - "r0" , "r1" , "hp" , "sp" , "gp" , "tp" , "r6" , "r7" , - "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15", - "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", - "r24", "r25", "r26", "r27", "r28", "r29", "r30", "lp" - ]; - - // System register names - static REG_SYSTEM = [ - "EIPC", "EIPSW", "FEPC", "FEPSW", "ECR", "PSW", "PIR", "TKCW", - "8" , "9" , "10" , "11" , "12" , "13" , "14" , "15" , - "16" , "17" , "18" , "19" , "20" , "21" , "22" , "23" , - "CHCW", "ADTRE", "26" , "27" , "28" , "29" , "30" , "31" - ]; - - // Other register names - static REG_OTHER = [ "PC", "PSW" ]; - - - - ///////////////////////////// Static Methods ////////////////////////////// - - // Determine the bounds of a data buffer to represent all lines of output - static dataBounds(address, line, length) { - let before = 10; // Number of lines before the first line of output - let max = 4; // Maximum number of bytes that can appear on a line - - // The reference line is before the preferred earliest line - if (line < -before) { - length = (length - line) * max; - } - - // The reference line is before the first line - else if (line < 0) { - address -= (line + before) * max; - length = (length + before) * max; - } - - // The reference line is at or after the first line - else { - address -= (line + before) * max; - length = (Math.max(length, line) + before) * max; - } - - return { - address: (address & ~1) >>> 0, - length : length - }; - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor() { - Object.assign(this, Disassembler.DEFAULTS); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Disassemble a region of memory - disassemble(data, dataAddress, refAddress, refLine, length, pc = null) { - let pcOffset = pc === null ? -1 : pc - dataAddress >>> 0; - - // Locate the offset of the first line of output in the buffer - let offset = 0; - for (let - addr = dataAddress, - circle = refLine > 0 ? new Array(refLine) : null, - index = 0, - more = [], - remain = null - ;;) { - - // Determine the size of the current line - if (more.length == 0) - this.more(more, data, offset); - let size = more.shift(); - - // The current line contains the reference address - if (refAddress - addr >>> 0 < size) { - - // The next item in the buffer is the first line of output - if (refLine > 0) { - offset = circle[index]; - break; - } - - // This line is the first line of output - if (refLine == 0) - break; - - // Count more lines for the first line of output - remain = refLine; - } - - // Record the offset of the current instruction - if (refLine > 0) { - circle[index] = offset; - index = (index + 1) % circle.length; - } - - // Advance to the next line - let sizeToPC = pcOffset - offset >>> 0; - if (offset != pcOffset && sizeToPC < size) { - size = sizeToPC; - more.splice(); - } - addr = addr + size >>> 0; - offset += size; - if (remain !== null && ++remain == 0) - break; // The next line is the first line of output - } - - // Process all lines of output - let lines = new Array(length); - for (let - addr = dataAddress + offset, - more = [], - x = 0; - x < length; x++ - ) { - - // Determine the size of the current line - if (more.length == 0) - this.more(more, data, offset, pcOffset); - let size = more.shift(); - - // Add the line to the response - lines[x] = this.format({ - rawAddress: addr, - rawBytes : data.slice(offset, offset + size) - }); - - // Advance to the next line - let sizeToPC = pcOffset - offset >>> 0; - if (offset != pcOffset && sizeToPC < size) { - size = sizeToPC; - more.splice(); - } - addr = addr + size >>> 0; - offset += size; - } - - return lines; - } - - - - /////////////////////////// Formatting Methods //////////////////////////// - - // Format a line as human-readable text - format(line) { - let canReverse = true; - let opcode = line.rawBytes[1] >>> 2; - let opdef; - let code = [ - line.rawBytes[1] << 8 | line.rawBytes[0], - line.rawBytes.length == 2 ? null : - line.rawBytes[3] << 8 | line.rawBytes[2] - ]; - - // BCOND - if ((opcode & 0b111000) == 0b100000) { - let cond = code[0] >>> 9 & 15; - opdef = - cond == 13 ? [ "NOP", [ ] ] : - this.splitBcond ? [ "BCOND", [ "opBCond", "opDisp9" ] ] : - [ - cond == 5 ? "BR" : "B" + this.condition(cond, true), - [ "opDisp9" ] - ] - ; - canReverse = false; - } - - // Processing by opcode - else switch (opcode) { - - // SETF - case 0b010010: - opdef = !this.splitSetf ? - [ - "SETF" + Disassembler.CONDITIONS[code[0] & 15], - [ "opReg2" ] - ] : - [ "SETF", [ "opCond", "opReg2" ] ] - ; - break; - - // Bit string - case 0b011111: - opdef = Disassembler.BITSTRING[code[0] & 31]; - if (opdef != null) - opdef = [ opdef, [] ]; - break; - - // Floating-point/Nintendo - case 0b111110: - opdef = Disassembler.FLOATENDO[code[1] >>> 10]; - break; - - // All others - default: opdef = Disassembler.OPDEFS[opcode]; - } - - // The opcode is undefined - if (opdef == null) - opdef = [ "---", [] ]; - - // Format the line's display text - line.address = this.hex(line.rawAddress, 8, false); - line.bytes = new Array(line.rawBytes.length); - line.mnemonic = this.instUppercase ? opdef[0] : opdef[0].toLowerCase(); - line.operands = new Array(opdef[1].length); - for (let x = 0; x < line.bytes.length; x++) - line.bytes[x] = this.hex(line.rawBytes[x], 2, false); - for (let x = 0; x < line.operands.length; x++) - line.operands[x] = this[opdef[1][x]](line, code); - if (this.opDestFirst && canReverse) - line.operands.reverse(); - - return line; - } - - // Format a condition operand in a BCOND instruction - opBCond(line, code) { - return this.condition(code[0] >>> 9 & 15); - } - - // Format a condition operand in a SETF instruction - opCond(line, code) { - return this.condition(code[0] & 15); - } - - // Format a 9-bit displacement operand - opDisp9(line, code) { - let disp = code[0] << 23 >> 23; - return this.jump(line.rawAddress, disp); - } - - // Format a 26-bit displacement operand - opDisp26(line, code) { - let disp = (code[0] << 16 | code[1]) << 6 >> 6; - return this.jump(line.rawAddress, disp); - } - - // Format a 5-bit signed immediate operand - opImm5S(line, code) { - return (code[0] & 31) << 27 >> 27; - } - - // Format a 5-bit unsigned immediate operand - opImm5U(line, code) { - return code[0] & 31; - } - - // Format a 16-bit signed immediate operand - opImm16S(line, code) { - let ret = code[1] << 16 >> 16; - return ( - ret < -256 ? "-" + this.hex(-ret) : - ret > 256 ? this.hex( ret) : - ret - ); - } - - // Format a 16-bit unsigned immediate operand - opImm16U(line, code) { - return this.hex(code[1], 4); - } - - // Format a Reg1 operand - opReg1(line, code) { - return this.programRegister(code[0] & 31); - } - - // Format a disp[reg1] operand - opReg1Disp(line, code) { - let disp = code[1] << 16 >> 16; - let reg1 = this.programRegister(code[0] & 31); - - // Do not print the displacement - if (disp == 0) - return "[" + reg1 + "]"; - - // Format the displacement amount - disp = - disp < -256 ? "-" + this.hex(-disp) : - disp > 256 ? this.hex( disp) : - disp.toString() - ; - - // [reg1 + disp] notation - if (this.memInside) { - return "[" + reg1 + (disp.startsWith("-") ? - " - " + disp.substring(1) : - " + " + disp - ) + "]"; - } - - // disp[reg1] notation - return disp + "[" + reg1 + "]"; - } - - // Format a [Reg1] operand - opReg1Ind(line, code) { - return "[" + this.programRegister(code[0] & 31) + "]"; - } - - // Format a Reg2 operand - opReg2(line, code) { - return this.programRegister(code[0] >> 5 & 31); - } - - // Format a system register operand - opSys(line, code) { - return this.systemRegister(code[0] & 31); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Select the mnemonic for a condition - condition(index, forceUppercase = false) { - if (!this.condNames) - return index.toString(); - let ret = - index == 1 ? this.condCL : - index == 2 ? this.condEZ : - index == 9 ? "N" + this.condCL : - index == 10 ? "N" + this.condEZ : - Disassembler.CONDITIONS[index] - ; - if (!forceUppercase && !this.condUppercase) - ret = ret.toLowerCase(); - return ret; - } - - // Format a number as a hexadecimal string - hex(value, digits = null, decorated = true) { - value = value.toString(16); - if (this.hexUppercase) - value = value.toUpperCase(); - if (digits != null) - value = value.padStart(digits, "0"); - if (decorated) { - value = this.hexPrefix + value + this.hexSuffix; - if (this.hexPrefix == "" && "0123456789".indexOf(value[0]) == -1) - value = "0" + value; - } - return value; - } - - // Format a jump or branch destination - jump(address, disp) { - return ( - this.jumpAddress ? - this.hex(address + disp >>> 0, 8, false) : - disp < -256 ? "-" + this.hex(-disp) : - disp > 256 ? "+" + this.hex( disp) : - disp.toString() - ); - } - - // Determine the number of bytes in the next line(s) of disassembly - more(more, data, offset) { - - // Error checking - if (offset + 1 >= data.length) - throw new Error("Disassembly error: Unexpected EoF"); - - // Determine the instruction's size from its opcode - let opcode = data[offset + 1] >>> 2; - more.push( - opcode < 0b101000 || // 16-bit instruction - opcode == 0b110010 || // Illegal opcode - opcode == 0b110110 // Illegal opcode - ? 2 : 4); - } - - // Format a program register - programRegister(index) { - let ret = this.proNames ? - Disassembler.REG_PROGRAM[index] : "r" + index; - if (this.proUppercase) - ret = ret.toUpperCase(); - return ret; - } - - // Format a system register - systemRegister(index) { - let ret = this.sysNames ? - Disassembler.REG_SYSTEM[index] : index.toString(); - if (!this.sysUppercase && this.sysNames) - ret = ret.toLowerCase(); - return ret; - } - -} - -export { Disassembler }; diff --git a/web/core/wasm.c b/web/core/wasm.c deleted file mode 100644 index 01dc80c..0000000 --- a/web/core/wasm.c +++ /dev/null @@ -1,79 +0,0 @@ -#undef VBAPI -#include -#include -#include - - - -/////////////////////////////// Module Commands /////////////////////////////// - -// Create and initialize a new simulation -EMSCRIPTEN_KEEPALIVE VB* Create() { - VB *sim = malloc(sizeof (VB)); - vbInit(sim); - return sim; -} - -// Delete all memory used by a simulation -EMSCRIPTEN_KEEPALIVE void Delete(VB *sim) { - free(sim->cart.ram); - free(sim->cart.rom); - free(sim); -} - -// Proxy for free() -EMSCRIPTEN_KEEPALIVE void Free(void *ptr) { - free(ptr); -} - -// Proxy for malloc() -EMSCRIPTEN_KEEPALIVE void* Malloc(int size) { - return malloc(size); -} - -// Size in bytes of a pointer -EMSCRIPTEN_KEEPALIVE int PointerSize() { - return sizeof (void *); -} - - - -////////////////////////////// Debugger Commands ////////////////////////////// - -// Execute until the following instruction -static uint32_t RunToNextAddress; -static int RunToNextFetch(VB *sim, int fetch, VBAccess *access) { - return access->address == RunToNextAddress; -} -static int RunToNextExecute(VB *sim, VBInstruction *inst) { - RunToNextAddress = inst->address + inst->size; - vbSetCallback(sim, VB_ONEXECUTE, NULL); - vbSetCallback(sim, VB_ONFETCH, &RunToNextFetch); - return 0; -} -EMSCRIPTEN_KEEPALIVE void RunToNext(VB **sims, int count) { - uint32_t clocks = 20000000; // 1s - vbSetCallback(sims[0], VB_ONEXECUTE, &RunToNextExecute); - vbEmulateEx (sims, count, &clocks); - vbSetCallback(sims[0], VB_ONEXECUTE, NULL); - vbSetCallback(sims[0], VB_ONFETCH , NULL); -} - -// Execute the current instruction -static int SingleStepBreak; -static int SingleStepFetch(VB *sim, int fetch, VBAccess *access) { - if (fetch != 0) - return 0; - if (SingleStepBreak == 1) - return 1; - SingleStepBreak = 1; - return 0; -} -EMSCRIPTEN_KEEPALIVE void SingleStep(VB **sims, int count) { - uint32_t clocks = 20000000; // 1s - SingleStepBreak = sims[0]->cpu.stage == 0 ? 0 : 1; - vbSetCallback(sims[0], VB_ONFETCH, &SingleStepFetch); - vbEmulateEx (sims, count, &clocks); - vbSetCallback(sims[0], VB_ONEXECUTE, NULL); - vbSetCallback(sims[0], VB_ONFETCH , NULL); -} diff --git a/web/debugger/CPU.js b/web/debugger/CPU.js deleted file mode 100644 index a51ea0e..0000000 --- a/web/debugger/CPU.js +++ /dev/null @@ -1,1439 +0,0 @@ -import { Core } from /**/"../core/Core.js"; -import { Disassembler } from /**/"../core/Disassembler.js"; -import { Toolkit } from /**/"../toolkit/Toolkit.js"; -let register = Debugger => { - -// CPU disassembler and register state window -class CPU extends Toolkit.Window { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(debug, index) { - super(debug.app, { - class: "tk window cpu" - }); - - // Configure instance fields - this.debug = debug; - this.height = 300; - this.index = index; - this.shown = false; - this.width = 400; - - // Window - this.setTitle("{debug.cpu._}", true); - this.substitute("#", " " + (index + 1)); - if (index == 1) - this.element.classList.add("two"); - this.addEventListener("close" , e=>this.visible = false); - this.addEventListener("visibility", e=>this.onVisibility(e)); - - // Client area - Object.assign(this.client.style, { - display : "grid", - gridAutoRows : "100%", - gridTemplateColumns: "auto" - }); - - // Disassembler - this.disassembler = new DisassemblerPane(this); - this.lastFocus = this.disassembler.view; - - // Register lists - this.registers = new RegisterPane(this); - - // Main content area - this.add(new Toolkit.SplitPane(debug.app, { - orientation: "right", - primary : this.registers, - secondary : this.disassembler - })); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Window key press - onKeyDown(e) { - super.onKeyDown(e); - - // Error checking - if (e.altKey || e.shiftKey) - return; - - // Processing by key: CTRL down - if (e.ctrlKey) switch (e.key) { - case "b": case "B": - this.disassembler.bytesColumn = !this.disassembler.bytesColumn; - break; - case "f": case "F": - this.disassembler.fitColumns(); - break; - case "g": case "G": - this.disassembler.goto(); - break; - default: return; - } - - // Processing by key: CTRL up - else switch (e.key) { - case "F10": - this.debug.app.runToNext(this.index, { refresh: true }); - break; - case "F11": - this.debug.app.singleStep(this.index, { refresh: true }); - break; - default: return; - } - - Toolkit.handle(e); - } - - // Window visibility - onVisibility(e) { - - // Configure instance fields - this.shown = this.shown || e.visible; - - // Configure subscriptions - if (!e.visible) { - if (this.registers) - this.registers .unsubscribe(); - if (this.disassembler) - this.disassembler.unsubscribe(); - } else { - this.registers .fetch(); - this.disassembler.fetch(); - } - - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - - // Disassembler - this.disassembler.fetch(); - - // Registers - for (let reg of this.registers.list) { - reg.chkExpand.element.style.removeProperty("min-width"); - reg.dasmConfigured(); - } - this.registers.regResize(); - } - - // Window is being shown for the first time - firstShow() { - this.disassembler.firstShow(); - this.registers .firstShow(); - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// - - - -class DisassemblerPane extends Toolkit.ScrollPane { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(cpu) { - super(cpu.debug.app, { - class : "tk scroll-pane scr-dasm", - overflowX: "auto", - overflowY: "hidden" - }); - - // Configure instance fields - this._bytesColumn = true; - this.columnWidths = new Array(6); - this.cpu = cpu; - this.delta = 0; - this.dasm = null; - this.lines = []; - this.pending = false; - this.subscription = [ 1, cpu.index, "cpu", "disassembler", "refresh" ], - this.viewAddress = 0xFFFFFFF0; - this.viewLine = 10; - - // Initialize column widths - for (let x = 0; x < this.columnWidths.length; x++) - this.columnWidths[x] = 0; - - // Client area - let view = this.view = new Toolkit.Component(cpu.debug.app, { - class : "tk mono disassembler", - role : "application", - tabIndex: "0", - style : { - display : "grid", - height : "100%", - minWidth: "100%", - overflow: "hidden", - position: "relative", - width : "max-content" - } - }); - view.setLabel("{debug.cpu.disassembler}", true); - view.setRoleDescription("{debug.cpu.disassembler}", true); - view.addEventListener("keydown", e=>this.viewKeyDown(e)); - view.addEventListener("resize" , e=>this.viewResize ( )); - view.addEventListener("wheel" , e=>this.viewWheel (e)); - - // Label for measuring text dimensions in the disassembler - this.pc = new Toolkit.Label(cpu.debug.app, { - class : "tk label mono pc", - visible : false, - visibility: true, - style : { - position: "absolute" - } - }); - this.pc.setText("\u00a0", false); //   - view.append(this.pc); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Column resized - colResize() { - if (this.lines.length == 0) - return; - for (let x = 0; x < this.columnWidths.length; x++) { - let elm = this.lines[0].all[x]; - let width = elm.getBoundingClientRect().width; - if (width <= this.columnWidths[x]) - continue; - this.columnWidths[x] = width; - elm.style.minWidth = width + "px"; - } - } - - // Key press - viewKeyDown(e) { - - // Error checking - if (e.altKey || e.ctrlKey || e.shiftKey) - return; - - // Processing by key - switch (e.key) { - case "ArrowDown": - this.fetch(-1); - break; - case "ArrowLeft": - this.scrollLeft -= this.hscroll.unitIncrement; - break; - case "ArrowRight": - this.scrollLeft += this.hscroll.unitIncrement; - break; - case "ArrowUp": - this.fetch(+1); - break; - case "PageDown": - this.fetch(-this.tall(true)); - break; - case "PageUp": - this.fetch(+this.tall(true)); - break; - default: return; - } - - Toolkit.handle(e); - } - - // Resize - viewResize() { - - // Error checking - if (!this.pc) - return; - - // Working variables - let tall = this.tall(false); - let grew = this.lines.length < tall; - - // Process all new lines - for (let y = this.lines.length; y < tall; y++) { - let resizer = y != 0 ? null : - new ResizeObserver(()=>this.colResize()); - - let line = { - lblAddress : document.createElement("div"), - lblBytes : new Array(4), - lblMnemonic: document.createElement("div"), - lblOperands: document.createElement("div") - }; - - // Address label - line.lblAddress.className = "address"; - if (y == 0) - resizer.observe(line.lblAddress); - this.view.append(line.lblAddress); - - // Byte labels - for (let x = 0; x < line.lblBytes.length; x++) { - let lbl = line.lblBytes[x] = document.createElement("div"); - lbl.className = "byte" + (x == 0 ? " b0" : ""); - if (y == 0) { - lbl.style.minWidth = "0px"; - resizer.observe(lbl); - } - this.view.append(lbl); - } - - // Mnemonic label - line.lblMnemonic.className = "mnemonic"; - if (y == 0) - resizer.observe(line.lblMnemonic); - this.view.append(line.lblMnemonic); - - // Operand label - line.lblOperands.className = "operands"; - this.view.append(line.lblOperands); - - // All elements - line.all = line.lblBytes.concat([ - line.lblAddress, - line.lblMnemonic, - line.lblOperands - ]); - - this.lines.push(line); - } - - // Remove lines that are no longer visible - while (tall < this.lines.length) { - let line = this.lines[tall]; - line.lblAddress .remove(); - line.lblMnemonic.remove(); - line.lblOperands.remove(); - for (let lbl of line.lblBytes) - lbl.remove(); - this.lines.splice(tall, 1); - } - - // Configure elements - let lineHeight = this.pc.element.getBoundingClientRect().height; - this.hscroll.unitIncrement = lineHeight; - this.view.element.style.gridAutoRows = lineHeight + "px"; - - // Update components - if (grew) - this.fetch(); - else this.refresh(); - } - - // Mouse wheel - viewWheel(e) { - - // Error checking - if (e.altKey || e.ctrlKey || e.shiftKey) - return; - - // Always handle the event - Toolkit.handle(e); - - // Determine how many full lines were scrolled - let scr = Debugger.linesScrolled(e, - this.pc.element.getBoundingClientRect().height, - this.tall(true), - this.delta - ); - this.delta = scr.delta; - scr.lines = Math.max(-3, Math.min(3, scr.lines)); - - // No lines were scrolled - if (scr.lines == 0) - return; - - // Scroll the view - this.fetch(-scr.lines); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Display the bytes column in the disassembler - get bytesColumn() { return this._bytesColumn; } - set bytesColumn(show) { - show = !!show; - if (show == this._bytesColumn) - return; - this._bytesColumn = show; - this.refresh(); - } - - // Retrieve a disassembly from the simulation state - async fetch(viewScroll = 0) { - - // Select the parameters for the simulation fetch - let params = { - viewAddress: this.viewAddress, - viewLine : this.viewLine + viewScroll, - viewLength : this.tall(false) + 20, - viewScroll : viewScroll - }; - if (this.pending instanceof Object) { - params.viewLine += this.pending.viewScroll; - params.viewScroll += this.pending.viewScroll; - } - let bounds = Disassembler.dataBounds( - params.viewAddress, params.viewLine, params.viewLength); - params.dataAddress = bounds.address; - params.dataLength = bounds.length; - - // A communication with the core thread is already underway - if (this.pending) { - this.pending = params; - this.refresh(); - return; - } - - // Retrieve data from the simulation state - this.pending = params; - for (let data=null, promise=null; this.pending instanceof Object;) { - - // Wait for a transaction to complete - if (promise != null) { - this.pending = true; - data = await promise; - promise = null; - } - - // Initiate a new transaction - if (this.pending instanceof Object) { - params = this.pending; - let options = { tag: params }; - if (this.cpu.isVisible()) - options.subscription = this.subscription; - promise = this.cpu.debug.core.read(this.cpu.debug.sim, - params.dataAddress, params.dataLength, options); - } - - // Process the result of a transaction - if (data != null) { - this.refresh(data); - data = null; - } - - }; - this.pending = false; - } - - // Component is being displayed for the first time - firstShow() { - this.viewLine = Math.floor(this.tall(true) / 3) + 10; - this.viewResize(); - } - - // Shrink all columns to fit the current view - fitColumns() { - if (this.lines.length == 0) - return; - for (let elm of this.lines[0].all) - elm.style.removeProperty("min-width"); - for (let x = 0; x < this.columnWidths.length; x++) - this.columnWidths[x] = 0; - this.refresh(); - this.colResize(); - } - - // Ensure PC is visible in the view - followPC(pc) { - let tall = this.tall(true); - let count = !this.dasm ? 0 : Math.min(this.dasm.length - 10, tall); - - // Determine whether PC already is visible - for (let x = 0; x < count; x++) { - let line = this.dasm[x + 10]; - if (pc - line.rawAddress >>> 0 < line.bytes.length) - return; // PC is already visible - } - - // Request a new view containing PC - this.viewAddress = pc; - this.viewLine = Math.floor(tall / 3) + 10; - if (this.cpu.isVisible()) - this.fetch(); - } - - // Prompt the user to navigate to a new editing address - goto() { - - // Retrieve the value from the user - let addr = prompt(this.app.localize("{debug.cpu.goto}")); - if (addr === null) - return; - addr = parseInt(addr.trim(), 16); - if ( - !Number.isInteger(addr) || - addr < 0 || - addr > 4294967295 - ) return; - - // Navigate to the given address - this.viewAddress = addr; - this.viewLine = Math.floor(this.tall(true) / 3) + 10; - this.fetch(); - } - - // Update localization strings - localize() { - this.localizeRoleDescription(); - this.localizeLabel(); - } - - // Update disassembler - refresh(msg = null) { - let tall = this.tall(false); - - // Receiving data from the simulation state - if (msg != null) { - - // Disassemble the retrieved data - this.dasm = this.cpu.debug.dasm.disassemble( - msg.data, msg.address, - msg.tag.viewAddress, msg.tag.viewLine, msg.tag.viewLength, - this.cpu.registers.pc - ); - - // Configure the view - this.viewAddress = this.dasm[10].rawAddress; - this.viewLine = 10; - if (this.pending instanceof Object) - this.viewLine += this.pending.viewScroll; - } - - // Determine an initial number of visible byte columns - let showBytes = 0; - if (this.bytesColumn) { - for (let x = 0; x < 4 && - this.columnWidths[x] != 0; x++, showBytes++); - } - - // Working variables - let foundPC = false; - let lineHeight = this.pc.element.getBoundingClientRect().height; - - // Process all lines - let index = 20 - this.viewLine; - for (let y = 0; y < this.lines.length; y++, index++) { - let line = this.lines[y]; - let isPC = false; - - // There is no data for this line - if (index < 0 || this.dasm == null || index >= this.dasm.length) { - line.lblAddress .innerText = "--------"; - line.lblBytes[0].innerText = "--"; - line.lblMnemonic.innerText = "---"; - line.lblOperands.innerText = ""; - for (let x = 1; x < line.lblBytes.length; x++) - line.lblBytes[x].innerText = ""; - if (this.bytesColumn) - showBytes = Math.max(showBytes, 1); - } - - // Present the disassembled line - else { - let dasm = this.dasm[index]; - line.lblAddress .innerText = dasm.address; - line.lblMnemonic.innerText = dasm.mnemonic; - line.lblOperands.innerText = dasm.operands.join(", "); - for (let x = 0; x < line.lblBytes.length; x++) - line.lblBytes[x].innerText = dasm.bytes[x] || ""; - isPC = this.cpu.registers.pc == dasm.rawAddress; - if (this.bytesColumn) - showBytes = Math.max(showBytes, dasm.bytes.length); - } - - // Configure whether PC is on this line - if (isPC) { - foundPC = true; - this.pc.element.style.top = lineHeight * y + "px"; - for (let elm of line.all) - elm.classList.add("is-pc"); - } else { - for (let elm of line.all) - elm.classList.remove("is-pc"); - } - - } - - // Show or hide the PC background - this.pc.visible = foundPC; - - // Configure which byte columns are visible - for (let line of this.lines) { - for (let x = 0; x < line.lblBytes.length; x++) { - line.lblBytes[x].style - [x < showBytes ? "removeProperty" : "setProperty"] - ("display", "none") - ; - } - } - - // Configure layout - this.view.element.style.gridTemplateColumns = - "repeat(" + (showBytes + 3) + ", max-content)"; - } - - // Stop receiving updates from the simulation - unsubscribe() { - this.cpu.debug.core.unsubscribe(this.subscription, false); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Measure the number of lines visible in the view - tall(fully) { - return Math.max(1, Math[fully ? "floor" : "ceil"]( - (fully ? this.view : this).element - .getBoundingClientRect().height / - this.pc.element.getBoundingClientRect().height - )); - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// - - - -// Entry in the register lists -class Register { - - //////////////////////////////// Constants //////////////////////////////// - - // Register types - static PLAIN = 0; - static PROGRAM = 1; - static CHCW = 2; - static ECR = 3; - static PSW = 4; - static PIR = 5; - static TKCW = 6; - - // Program register formats - static HEX = 0; - static SIGNED = 1; - static UNSIGNED = 2; - static FLOAT = 3; - - // Expansion controls by register type - static FIELDS = { - [this.CHCW]: [ - [ "check", "ICE", 1 ] - ], - [this.ECR]: [ - [ "texth", "FECC", 16, 16 ], - [ "texth", "EICC", 0, 16 ] - ], - [this.PIR]: [ - [ "texth", "PT", 0, 16 ] - ], - [this.PSW]: [ - [ "check", "CY" , 3 ], [ "check", "FRO", 9 ], - [ "check", "OV" , 2 ], [ "check", "FIV", 8 ], - [ "check", "S" , 1 ], [ "check", "FZD", 7 ], - [ "check", "Z" , 0 ], [ "check", "FOV", 6 ], - [ "check", "NP" , 15 ], [ "check", "FUD", 5 ], - [ "check", "EP" , 14 ], [ "check", "FPR", 4 ], - [ "check", "ID" , 12 ], [ "textd", "I" , 16, 4 ], - [ "check", "AE" , 13 ] - ], - [this.TKCW]: [ - [ "check", "FIT", 7 ], [ "check", "FUT", 4 ], - [ "check", "FZT", 6 ], [ "check", "FPT", 3 ], - [ "check", "FVT", 5 ], [ "check", "OTM", 8 ], - [ "check", "RDI", 2 ], [ "textd", "RD" , 0, 2 ] - ] - }; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(registers, key, type, apiType, apiId) { - let app = registers.cpu.debug.app; - - // Configure instance fields - this.apiId = apiId; - this.apiType = apiType; - this.controls = []; - this.dasm = registers.cpu.debug.app.dasm; - this.debug = registers.cpu.debug; - this.expansion = null; - this.format = Register.HEX; - this.key = key; - this.registers = registers; - this.type = type; - - // Resolve the target object - switch (apiType) { - case Core.VB_PROGRAM: this.target = registers.program; break; - case Core.VB_SYSTEM : this.target = registers.system ; break; - case Core.VB_OTHER : this.target = registers ; break; - } - - // Main controls - this.main = new Toolkit.Component(app, { - class : "main", - visibility: true, - style : { - alignItems : "center", - display : "grid", - gridTemplateColumns: "max-content auto" - } - }); - - // Expand/collapse check box - this.chkExpand = new Toolkit.Checkbox(app, { - class : "tk expand", - disabled: true, - instant : true, - role : "" - }); - this.main.add(this.chkExpand); - - // Value text box - this.txtValue = new Toolkit.TextBox(app, { - class : "tk text-box mono", - spellcheck: "false", - size : "1", - value : "00000000" - }); - this.txtValue.setLabel(this.label); - this.txtValue.addEventListener("action" , e=>this.valAction (e)); - this.txtValue.addEventListener("keydown", e=>this.valKeyDown(e)); - this.main.add(this.txtValue); - - // Expansion area - if (type == Register.PROGRAM) - this.initProgram(app); - else this.initSystem(app, Register.FIELDS[type]); - if (this.expansion != null) { - - // Expand/collapse check box - this.chkExpand.addEventListener("input", e=>this.chkInput(e)); - this.chkExpand.disabled = false; - this.chkExpand.element.setAttribute("role", "checkbox"); - this.chkExpand.element.setAttribute("tabindex", "0"); - this.chkExpand.element - .setAttribute("aria-controls", this.expansion.element.id); - - // Expansion area - this.expansion.visible = false; - } - - // Update controls - this.dasmConfigured(); - this.refresh(); - - // PSW is initially expanded - if (apiType == Core.VB_SYSTEM && apiId == Core.VB_PSW) - this.expanded = true; - - // System registers after PSW are initially hidden - else if (apiType == Core.VB_SYSTEM) - this.visible = false; - } - - // Expansion controls for program registers - initProgram(app) { - - // Expansion area - let exp = this.expansion = new Toolkit.Component(app, { - class: "expansion", - id : Toolkit.id(), - style: { - display : "inline-grid", - gridTemplateColumns: "max-content", - } - }); - exp.localize = ()=>exp.localizeLabel(); - exp.setLabel("{debug.cpu.format}", true); - - // Radio group - let group = new Toolkit.RadioGroup(app); - - // Hex radio button - let opt = new Toolkit.Radio(app, { - checked: true, - group : group - }); - opt.setText("{debug.cpu.hex}", true); - opt.addEventListener("input", e=>this.onProgram(Register.HEX)); - exp.add(opt); - - // Signed radio button - opt = new Toolkit.Radio(app, { group: group }); - opt.setText("{debug.cpu.signed}", true); - opt.addEventListener("input", e=>this.onProgram(Register.SIGNED)); - exp.add(opt); - - // Unsigned radio button - opt = new Toolkit.Radio(app, { group: group }); - opt.setText("{debug.cpu.unsigned}", true); - opt.addEventListener("input", e=>this.onProgram(Register.UNSIGNED)); - exp.add(opt); - - // Float radio button - opt = new Toolkit.Radio(app, { group: group }); - opt.setText("{debug.cpu.float}", true); - opt.addEventListener("input", e=>this.onProgram(Register.FLOAT)); - exp.add(opt); - } - - // Expansion controls for system registers - initSystem(app, fields) { - - // No expansion area - if (!fields) - return; - - // Expansion area - let exp = this.expansion = new Toolkit.Component(app, { - class: "expansion", - id : Toolkit.id(), - style: { - display : "inline-grid", - gridTemplateColumns: "max-content max-content", - } - }); - - // Process all controls - for (let field of fields) { - - // Bit check box - if (field[0] == "check") { - let box = new Toolkit.Checkbox(app); - box.setText(field[1], false); - box.bit = field[2]; - box.addEventListener("input", e=>this.onBit(e)); - exp.append(box.element); - if (this.type == Register.PIR || this.type == Register.TKCW) - box.disabled = true; - this.controls.push(box); - } - - // Decimal text box - else if (field[0] == "textd") { - - // Containing element for outer layout purposes - let div = document.createElement("div"); - div.className = "text-dec"; - Object.assign(div.style, { - alignItems : "center", - display : "inline-grid", - gridTemplateColumns: "max-content auto" - }); - - // Text box - let txt = new Toolkit.TextBox(app, { - id : Toolkit.id(), - spellcheck: false, - value : "0", - style : { - maxWidth: "2em" - } - }); - txt.bit = field[2]; - txt.bits = field[3]; - txt.isHex = false; - txt.addEventListener("action", e=>this.onText(e)); - - // Label - let lbl = new Toolkit.Label(app, { - htmlFor: txt.element.id, - id : Toolkit.id(), - tag : "label" - }); - lbl.setText(field[1], false); - - txt.setLabel(lbl); - - // Disable all fields - if (this.type == Register.PIR || this.type == Register.TKCW) { - lbl.disabled = true; - txt.disabled = true; - } - - // Output control - this.controls.push(txt); - div.append(lbl.element); - div.append(txt.element); - exp.append(div); - } - - // Hexadecimal text box - else if (field[0] == "texth") { - - // Text box - let txt = new Toolkit.TextBox(app, { - class : "tk text-box mono", - id : Toolkit.id(), - spellcheck: false, - value : "0", - style : { - maxWidth: "3em" - } - }); - txt.bit = field[2]; - txt.bits = field[3]; - txt.isHex = true; - txt.addEventListener("action", e=>this.onText(e)); - - // Label - let lbl = new Toolkit.Label(app, { - htmlFor: txt.element.id, - id : Toolkit.id(), - tag : "label" - }); - lbl.setText(field[1], false); - - txt.setLabel(lbl); - - // Disable all fields - if (this.type == Register.PIR || this.type == Register.TKCW) { - lbl.disabled = true; - txt.disabled = true; - } - - // Output control - this.controls.push(txt); - exp.append(lbl.element); - exp.append(txt.element); - } - - } - - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Expand/collapse check box input - chkInput(e) { - this.expanded = this.chkExpand.checked; - } - - // Program register format changed - onProgram(format) { - this.format = format; - this.txtValue.element.classList - [format == Register.HEX ? "add" : "remove"]("mono"); - this.refresh(); - } - - // Bit check box input - onBit(e) { - let oldValue = this.target[this.key]; - let target = e.target.component; - let mask = 1 << target.bit; - - // Cannot change the value - if (e.disabled) - return; - - // Update the value - this.setValue(target.checked ? oldValue | mask : oldValue & ~mask); - } - - // Text box commit - onText(e) { - - // Cannot change the value - if (e.disabled) - return; - - // Working variables - let oldValue = this.target[this.key]; - let target = e.target.component; - let newValue = parseInt(target.value, target.isHex ? 16 : 10); - - // The provided value is invalid - if (!Number.isInteger(newValue)) { - this.refresh(); - return; - } - - // Update the value - let mask = (1 << target.bits) - 1 << target.bit; - this.setValue(oldValue & ~mask | newValue << target.bit & mask); - } - - // Value text box commit - valAction(e) { - Toolkit.handle(e); - let value = this.parseValue(this.txtValue.value); - if (value === null) - this.refresh(); - else this.setValue(value); - } - - // Value text box key press - valKeyDown(e) { - if (e.altKey || e.ctrlKey || e.shiftKey || e.key != "Escape") - return; - Toolkit.handle(e); - this.refresh(); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // The expansion area is visible - get expanded() { return this.chkExpand.checked; } - set expanded(expanded) { - this.chkExpand.checked = expanded; - this.setVisible(this.main.visible); - } - - // Specify whether the element is visible - get visible() { return this.main.visible; } - set visible(visible) { - visible = !!visible; - if (visible == this.main.visible) - return; - this.main.element.style[visible ? "removeProperty" : "setProperty"] - ("position", "absolute"); - this.main.visible = visible; - this.setVisible(visible); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - let names; - switch (this.apiType) { - case Core.VB_SYSTEM: names = Disassembler.REG_SYSTEM; break; - case Core.VB_OTHER : names = Disassembler.REG_OTHER ; break; - } - this.chkExpand.uiLabel.setText(this.apiType != Core.VB_PROGRAM ? - names[this.apiId] : this.dasm.programRegister(this.key)); - } - - // Update controls from simulation state - refresh() { - - // Value text box - let value = this.target[this.key]; - this.txtValue.value = this.formatValue(value); - - // Expansion controls - for (let ctrl of this.controls) { - - // Bit check box - if (ctrl instanceof Toolkit.Checkbox) - ctrl.checked = !!(value >> ctrl.bit & 1); - - // Decimal text box - else if (ctrl instanceof Toolkit.TextBox && !ctrl.isHex) - ctrl.value = value >> ctrl.bit & (1 << ctrl.bits) - 1; - - // Hexadecimal text box - else if (ctrl instanceof Toolkit.TextBox && ctrl.isHex) { - ctrl.value = this.dasm.hex( - value >> ctrl.bit & (1 << ctrl.bits) - 1, - Math.ceil(ctrl.bits / 4), - false); - } - - } - - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Format a register value as text - formatValue(value) { - switch (this.format) { - case Register.HEX: - return this.debug.hex(value >>> 0, 8, false); - case Register.SIGNED: - return (value >> 0).toString(); - case Register.UNSIGNED: - return (value >>> 0).toString(); - case Register.FLOAT: - value = Debugger.ixf(value); - if (Number.isFinite(value)) { - let text = value.toFixed(100); - if (/[^0-9\-\.]/.test(text)) - text = value.toFixed(6); - if (text.indexOf(".") != -1) { - text = text.replace(/0+$/, "") - .replace(/\.$/, ".0"); - } else text += ".0"; - return text; - } - if (!Number.isNaN(value)) { - return ( - (value == Number.NEGATIVE_INFINITY ? "-" : "") + - this.debug.app.localize("{debug.cpu.infinity}") - ); - } - return "NaN"; - } - return null; - } - - // Parse text as a register value - parseValue(value) { - switch (this.format) { - case Register.HEX: - value = parseInt(value, 16); - return ( - !Number.isInteger(value) || - value < 0 || - value > 0xFFFFFFFF ? - null : value - ); - case Register.SIGNED: - value = parseInt(value); - return ( - !Number.isInteger(value) || - value < -0x80000000 || - value > 0x7FFFFFFF ? - null : value - ); - case Register.UNSIGNED: - value = parseInt(value); - return ( - !Number.isInteger(value) || - value < 0 || - value > 0xFFFFFFFF ? - null : value - ); - case Register.FLOAT: - value = parseFloat(value); - return ( - !Number.isFinite(value) || - value < Debugger.ixf(0xFF7FFFFF) || - value > Debugger.ixf(0x7F7FFFFF) - ? null : Debugger.fxi(value) >>> 0); - } - return null; - } - - // Specify a new register value - async setValue(value) { - - // Update the value in the simulation state - let options = {}; - if (this.key == "pc") - options.refresh = [ this.registers.cpu.disassembler.subscription ]; - let result = await this.debug.core.setRegister( - this.debug.sim, this.apiType, this.apiId, value, options); - - // Update the value in the debugger window - this.target[this.key] = result.value; - - // Update the register controls - this.refresh(); - } - - // Update visibility for expansion controls - setVisible(visible) { - if (this.expansion) - this.expansion.visible = visible && !!this.expanded; - } - -} - - - -/////////////////////////////////////////////////////////////////////////////// - - - -// Register list manager -class RegisterPane extends Toolkit.SplitPane { - - //////////////////////////////// Constants //////////////////////////////// - - // System register templates - static SYSTEMS = [ - [ "pc" , Register.PLAIN, Core.VB_OTHER , Core.VB_PC ], - [ Core.VB_PSW , Register.PSW , Core.VB_SYSTEM, Core.VB_PSW ], - [ Core.VB_ADTRE, Register.PLAIN, Core.VB_SYSTEM, Core.VB_ADTRE ], - [ Core.VB_CHCW , Register.CHCW , Core.VB_SYSTEM, Core.VB_CHCW ], - [ Core.VB_ECR , Register.ECR , Core.VB_SYSTEM, Core.VB_ECR ], - [ Core.VB_EIPC , Register.PLAIN, Core.VB_SYSTEM, Core.VB_EIPC ], - [ Core.VB_EIPSW, Register.PSW , Core.VB_SYSTEM, Core.VB_EIPSW ], - [ Core.VB_FEPC , Register.PLAIN, Core.VB_SYSTEM, Core.VB_FEPC ], - [ Core.VB_FEPSW, Register.PSW , Core.VB_SYSTEM, Core.VB_FEPSW ], - [ Core.VB_PIR , Register.PIR , Core.VB_SYSTEM, Core.VB_PIR ], - [ Core.VB_TKCW , Register.TKCW , Core.VB_SYSTEM, Core.VB_TKCW ], - [ 29 , Register.PLAIN, Core.VB_SYSTEM, 29 ], - [ 30 , Register.PLAIN, Core.VB_SYSTEM, 30 ], - [ 31 , Register.PLAIN, Core.VB_SYSTEM, 31 ] - ]; - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(cpu) { - super(cpu.debug.app, { - orientation: "top", - style: { - overflow: "visible" - } - }); - - // Configure instance fields - this.cpu = cpu; - this.list = []; - this.pending = false; - this.subscription = [ 0, cpu.index, "cpu", "registers", "refresh" ], - - // Initialize regsiters - this.pc = 0xFFFFFFF0; - this.program = new Array(32); - this.system = new Array(32); - for (let x = 0; x < 32; x++) - this.program[x] = this.system[x] = 0; - - // System registers list - this.lstSystem = new Toolkit.Component(cpu.debug.app, { - class: "tk registers", - style: { - minHeight: "100%", - minWidth : "100%", - width : "max-content" - } - }); - - // System registers scroll pane - this.scrSystem = new Toolkit.ScrollPane(cpu.debug.app, { - class : "tk scroll-pane scr-system", - overflowX: "hidden", - overflowY: "scroll", - view : this.lstSystem, - style : { - position: "relative" - } - }); - this.primary = this.scrSystem; - - // Program registers list - this.lstProgram = new Toolkit.Component(cpu.debug.app, { - class: "tk registers", - style: { - minHeight: "100%", - minWidth : "100%", - width : "max-content" - } - }); - - // Program registers scroll pane - this.scrProgram = new Toolkit.ScrollPane(cpu.debug.app, { - class : "tk scroll-pane scr-program", - overflowX: "hidden", - overflowY: "scroll", - view : this.lstProgram - }); - this.secondary = this.scrProgram; - - // Configure register lists - for (let sys of RegisterPane.SYSTEMS) - this.addRegister(new Register(this, ... sys)); - for (let x = 0; x < 32; x++) { - this.addRegister(new Register(this, - x, Register.PROGRAM, Core.VB_PROGRAM, x)); - } - - // Value text box measurer - let text = []; - for (let c of "0123456789abcdefABCDEF") - text.push(c.repeat(8)); - this.sizer = new Toolkit.Label(cpu.app, { - class: "tk text-box mono", - style: { - position : "absolute", - visibility: "hidden" - } - }); - this.sizer.setText(text.join("\n"), false); - this.list[0].main.element.after(this.sizer.element); - - // Monitor the bounds of the register names column - let resizer = new ResizeObserver(()=>this.regResize()); - for (let reg of this.list) - resizer.observe(reg.chkExpand.element); - - // Monitor the bounds of the value text boxes - this.list[0].txtValue.addEventListener("resize", e=>this.valResize(e)); - this.sizer .addEventListener("resize", e=>this.sizResize(e)); - } - - // Add a Register object to a list - addRegister(reg) { - this.list.push(reg); - - // Add the main element to the appropriate list container - let list = this[reg.type == Register.PROGRAM ? - "lstProgram" : "lstSystem"]; - list.add(reg.main); - - // Add the expansion element - if (reg.expansion != null) - list.add(reg.expansion); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Register label resized - regResize() { - let max = 0; - let widths = new Array(this.list.length); - - // Measure the widths of all labels - for (let x = 0; x < this.list.length; x++) { - widths[x] = Math.ceil(this.list[x].chkExpand.element - .getBoundingClientRect().width); - max = Math.max(max, widths[x]); - } - - // Ensure all labels share the same maximum width - for (let x = 0; x < this.list.length; x++) { - if (widths[x] < max) - this.list[x].chkExpand.element.style.minWidth = max + "px"; - } - - } - - // Sizer resized - sizResize(e) { - let width = Math.ceil(e.target.getBoundingClientRect().width) + "px"; - for (let reg of this.list) - reg.txtValue.style.minWidth = width; - } - - // Value text box resized - valResize(e) { - let height = Math.ceil(e.target.getBoundingClientRect().height); - this.scrSystem .vscroll.unitIncrement = height; - this.scrProgram.vscroll.unitIncrement = height; - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Retrieve registers from the simulation state - async fetch() { - - // Select the parameters for the simulation fetch - let params = {}; - - // A communication with the core thread is already underway - if (this.pending) { - this.pending = params; - this.refresh(); - return; - } - - // Retrieve data from the simulation state - this.pending = params; - for (let data = null, promise = null; this.pending instanceof Object;){ - - // Wait for a transaction to complete - if (promise != null) { - this.pending = true; - data = await promise; - promise = null; - } - - // Initiate a new transaction - if (this.pending instanceof Object) { - params = this.pending; - let options = {}; - if (this.isVisible()) - options.subscription = this.subscription; - promise = this.cpu.debug.core.getAllRegisters( - this.cpu.debug.sim, options); - } - - // Process the result of a transaction - if (data != null) { - this.refresh(data); - data = null; - } - - }; - this.pending = false; - } - - // Component is being displayed for the first time - firstShow() { - - // Retrieve the desired dimensions of the system registers list - let bounds = this.scrSystem.element.getBoundingClientRect(); - - // Show all hidden system registers - for (let reg of this.list) - reg.visible = true; - - // Prepare the initial dimensions of the register lists - this .element.style.width = Math.ceil(bounds.width ) + "px"; - this.scrSystem .element.style.height = Math.ceil(bounds.height) + "px"; - this.scrSystem .overflowX = "auto"; - this.scrSystem .overflowY = "auto"; - this.scrProgram.overflowX = "auto"; - this.scrProgram.overflowY = "auto"; - - this.fetch(); - } - - // Update register lists - refresh(msg = null) { - - // Receiving data from the simulation state - if (msg != null) { - this.pc = msg.pc; - this.program.splice(0, this.program.length, ... msg.program); - this.system .splice(0, this.system .length, ... msg.system ); - } - - // Update register controls - for (let reg of this.list) - reg.refresh(); - } - - // Stop receiving updates from the simulation - unsubscribe() { - this.cpu.debug.core.unsubscribe(this.subscription, false); - } - -} - -Debugger.CPU = CPU; -} - -export { register }; diff --git a/web/debugger/Debugger.js b/web/debugger/Debugger.js deleted file mode 100644 index be029a8..0000000 --- a/web/debugger/Debugger.js +++ /dev/null @@ -1,104 +0,0 @@ -import { ISX } from /**/"./ISX.js"; - -// Debug mode UI manager -class Debugger { - - ///////////////////////////// Static Methods ////////////////////////////// - - // Data type conversions - static F32 = new Float32Array(1); - static U32 = new Uint32Array(this.F32.buffer); - - // Reinterpret a float32 as a u32 - static fxi(x) { - this.F32[0] = x; - return this.U32[0]; - } - - // Process file data as ISM - static isx(data) { - return new ISX(data); - } - - // Reinterpret a u32 as a float32 - static ixf(x) { - this.U32[0] = x; - return this.F32[0]; - } - - // Compute the number of lines scrolled by a WheelEvent - static linesScrolled(e, lineHeight, pageLines, delta) { - let ret = { - delta: delta, - lines: 0 - }; - - // No scrolling occurred - if (e.deltaY == 0); - - // Scrolling by pixel - else if (e.deltaMode == WheelEvent.DOM_DELTA_PIXEL) { - ret.delta += e.deltaY; - ret.lines = Math.sign(ret.delta) * - Math.floor(Math.abs(ret.delta) / lineHeight); - ret.delta -= ret.lines * lineHeight; - } - - // Scrolling by line - else if (e.deltaMode == WheelEvent.DOM_DELTA_LINE) - ret.lines = Math.trunc(e.deltaY); - - // Scrolling by page - else if (e.deltaMode == WheelEvent.DOM_DELTA_PAGE) - ret.lines = Math.trunc(e.deltaY) * pageLines; - - // Unknown scrolling mode - else ret.lines = 3 * Math.sign(e.deltaY); - - return ret; - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, sim, index) { - - // Configure instance fields - this.app = app; - this.core = app.core; - this.dasm = app.dasm; - this.sim = sim; - - // Configure debugger windows - this.cpu = new Debugger.CPU (this, index); - this.memory = new Debugger.Memory(this, index); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - this.cpu .dasmConfigured(); - this.memory.dasmConfigured(); - } - - // Ensure PC is visible in the disassembler - followPC(pc = null) { - this.cpu.disassembler.followPC(pc); - } - - // Format a number as hexadecimal - hex(value, digits = null, decorated = true) { - return this.dasm.hex(value, digits, decorated); - } - -} - -// Register component classes -(await import(/**/"./CPU.js" )).register(Debugger); -(await import(/**/"./Memory.js")).register(Debugger); - -export { Debugger }; diff --git a/web/debugger/ISX.js b/web/debugger/ISX.js deleted file mode 100644 index 44ee8da..0000000 --- a/web/debugger/ISX.js +++ /dev/null @@ -1,177 +0,0 @@ -// Debug manager for Intelligent Systems binaries -class ISX { - - ///////////////////////// Initialization Methods ////////////////////////// - - // Throws on decoding error - constructor(data) { - - // Configure instance fields - this.data = data; - this.offset = 0; - this.ranges = []; - this.symbols = []; - this.codes = []; - - // Skip any header that may be present - if (data.length >= 32 && this.readInt(3) == 0x585349) - this.offset = 32; - else this.offset = 0; - - // Process all records - while (this.offset < this.data.length) { - switch (this.readInt(1)) { - - // Virtual Boy records - case 0x11: this.code (); break; - case 0x13: this.range (); break; - case 0x14: this.symbol(); break; - - // System records - case 0x20: - case 0x21: - case 0x22: - let length = this.readInt(4); - this.offset += length; - break; - - // Other records - default: throw "ISX decode error"; - } - } - - // Cleanup instance fields - delete this.data; - delete this.decoder; - delete this.offset; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Produce a .vb format ROM file from the ISX code segments - toROM() { - let head = 0x00000000; - let tail = 0x01000000; - - // Inspect all code segments - for (let code of this.codes) { - let start = code.address & 0x00FFFFFF; - let end = start + code.data.length; - - // Segment begins in the first half of ROM - if (start < 0x00800000) { - - // Segment ends in the second half of ROM - if (end > 0x00800000) { - head = tail = 0; - break; - } - - // Segment ends in the first half of ROM - else if (end > head) - head = end; - } - - // Segment begins in the second half of ROM - else if (start < tail) - tail = start; - } - - // Prepare the output buffer - let min = head + 0x01000000 - tail; - let size = 1; - for (; size < min; size <<= 1); - let rom = new Uint8Array(size); - - // Output all code segments - for (let code of this.codes) { - let dest = code.address & rom.length - 1; - for (let src = 0; src < code.data.length; src++, dest++) - rom[dest] = code.data[src]; - } - - return rom; - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Process a code record - code() { - let address = this.readInt(4); - let length = this.readInt(4); - let data = this.readBytes(length); - if ( - length == 0 || - length > 0x01000000 || - (address & 0x07000000) != 0x07000000 || - (address & 0x07000000) + length > 0x08000000 - ) throw "ISX decode error"; - this.codes.push({ - address: address, - data : data - }); - } - - // Process a range record - range() { - let count = this.readInt(2); - while (count--) { - let start = this.readInt(4); - let end = this.readInt(4); - let type = this.readInt(1); - this.ranges.push({ - end : end, - start: start, - type : type - }); - } - } - - // Process a symbol record - symbol() { - let count = this.readInt(2); - while (count--) { - let length = this.readInt(1); - let name = this.readString(length); - let flags = this.readInt(2); - let address = this.readInt(4); - this.symbols.push({ - address: address, - flags : flags, - name : name - }); - } - } - - // Read a byte buffer - readBytes(size) { - if (this.offset + size > this.data.length) - throw "ISX decode error"; - let ret = this.data.slice(this.offset, this.offset + size); - this.offset += size; - return ret; - } - - // Read an integer - readInt(size) { - if (this.offset + size > this.data.length) - throw "ISX decode error"; - let ret = new Uint32Array(1); - for (let shift = 0; size > 0; size--, shift += 8) - ret[0] |= this.data[this.offset++] << shift; - return ret[0]; - } - - // Read a text string - readString(size) { - return (this.decoder = this.decoder || new TextDecoder() - ).decode(this.readBytes(size)); - } - -} - -export { ISX }; diff --git a/web/debugger/Memory.js b/web/debugger/Memory.js deleted file mode 100644 index 5012a72..0000000 --- a/web/debugger/Memory.js +++ /dev/null @@ -1,574 +0,0 @@ -import { Toolkit } from /**/"../toolkit/Toolkit.js"; -let register = Debugger => Debugger.Memory = - -// Debugger memory window -class Memory extends Toolkit.Window { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(debug, index) { - super(debug.app, { - class: "tk window memory" - }); - - // Configure instance fields - this.data = null, - this.dataAddress = null, - this.debug = debug; - this.delta = 0; - this.editDigit = null; - this.height = 300; - this.index = index; - this.lines = []; - this.pending = false; - this.shown = false; - this.subscription = [ 0, index, "memory", "refresh" ]; - this.width = 400; - - // Available buses - this.buses = [ - { - editAddress: 0x05000000, - viewAddress: 0x05000000 - } - ]; - this.bus = this.buses[0]; - - // Window - this.setTitle("{debug.memory._}", true); - this.substitute("#", " " + (index + 1)); - if (index == 1) - this.element.classList.add("two"); - this.addEventListener("close" , e=>this.visible = false); - this.addEventListener("visibility", e=>this.onVisibility(e)); - - // Client area - Object.assign(this.client.style, { - display : "grid", - gridTemplateRows: "max-content auto" - }); - - // Bus drop-down - this.drpBus = new Toolkit.DropDown(debug.app); - this.drpBus.setLabel("{debug.memory.bus}", true); - this.drpBus.setTitle("{debug.memory.bus}", true); - this.drpBus.add("{debug.memory.busMemory}", true, this.buses[0]); - this.drpBus.addEventListener("input", e=>this.busInput()); - this.add(this.drpBus); - - // Hex editor - this.hexEditor = new Toolkit.Component(debug.app, { - class : "tk mono hex-editor", - role : "application", - tabIndex: "0", - style : { - display : "grid", - gridTemplateColumns: "repeat(17, max-content)", - height : "100%", - minWidth : "100%", - overflow : "hidden", - position : "relative", - width : "max-content" - } - }); - this.hexEditor.localize = ()=>{ - this.hexEditor.localizeRoleDescription(); - this.hexEditor.localizeLabel(); - }; - this.hexEditor.setLabel("{debug.memory.hexEditor}", true); - this.hexEditor.setRoleDescription("{debug.memory.hexEditor}", true); - this.hexEditor.addEventListener("focusout", e=>this.commit ( )); - this.hexEditor.addEventListener("keydown" , e=>this.hexKeyDown(e)); - this.hexEditor.addEventListener("resize" , e=>this.hexResize ( )); - this.hexEditor.addEventListener("wheel" , e=>this.hexWheel (e)); - this.hexEditor.addEventListener( - "pointerdown", e=>this.hexPointerDown(e)); - this.lastFocus = this.hexEditor; - - // Label for measuring text dimensions - this.sizer = new Toolkit.Label(debug.app, { - class : "tk label mono", - visible : false, - visibility: true, - style: { - position: "absolute" - } - }); - this.sizer.setText("\u00a0", false); //   - this.hexEditor.append(this.sizer); - - // Hex editor scroll pane - this.scrHex = new Toolkit.ScrollPane(debug.app, { - overflowX: "auto", - overflowY: "hidden", - view : this.hexEditor - }); - this.add(this.scrHex); - - // Hide the bus drop-down: Virtual Boy only has one bus - this.drpBus.visible = false; - this.client.style.gridTemplateRows = "auto"; - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Bus drop-down selection - busInput() { - - // An edit is in progress - if (this.editDigit !== null) - this.commit(false); - - // Switch to the new bus - this.bus = this.drpBus.value; - this.fetch(); - } - - // Hex editor key press - hexKeyDown(e) { - - // Error checking - if (e.altKey || e.ctrlKey) - - // Processing by key, scroll lock off - if (!e.getModifierState("ScrollLock")) switch (e.key) { - case "ArrowDown": - this.commit(); - this.setEditAddress(this.bus.editAddress + 16); - Toolkit.handle(e); - return; - case "ArrowLeft": - this.commit(); - this.setEditAddress(this.bus.editAddress - 1); - Toolkit.handle(e); - return; - case "ArrowRight": - this.commit(); - this.setEditAddress(this.bus.editAddress + 1); - Toolkit.handle(e); - return; - case "ArrowUp": - this.commit(); - this.setEditAddress(this.bus.editAddress - 16); - Toolkit.handle(e); - return; - case "PageDown": - this.commit(); - this.setEditAddress(this.bus.editAddress + this.tall(true)*16); - Toolkit.handle(e); - return; - case "PageUp": - this.commit(); - this.setEditAddress(this.bus.editAddress - this.tall(true)*16); - Toolkit.handle(e); - return; - } - - // Processing by key, scroll lock on - else switch (e.key) { - case "ArrowDown": - this.setViewAddress(this.bus.viewAddress + 16); - this.fetch(); - Toolkit.handle(e); - return; - case "ArrowLeft": - this.scrHex.scrollLeft -= this.scrHex.hscroll.unitIncrement; - Toolkit.handle(e); - return; - case "ArrowRight": - this.scrHex.scrollLeft += this.scrHex.hscroll.unitIncrement; - Toolkit.handle(e); - return; - case "ArrowUp": - this.setViewAddress(this.bus.viewAddress - 16); - this.fetch(); - Toolkit.handle(e); - return; - case "PageDown": - this.setViewAddress(this.bus.viewAddress + this.tall(true)*16); - this.fetch(); - Toolkit.handle(e); - return; - case "PageUp": - this.setViewAddress(this.bus.viewAddress - this.tall(true)*16); - this.fetch(); - Toolkit.handle(e); - return; - } - - // Processing by key, editing - switch (e.key) { - - case "0": case "1": case "2": case "3": case "4": - case "5": case "6": case "7": case "8": case "9": - case "a": case "A": case "b": case "B": case "c": - case "C": case "d": case "D": case "e": case "E": - case "f": case "F": - let digit = parseInt(e.key, 16); - if (this.editDigit === null) { - this.editDigit = digit; - this.setEditAddress(this.bus.editAddress); - } else { - this.editDigit = this.editDigit << 4 | digit; - this.commit(); - this.setEditAddress(this.bus.editAddress + 1); - } - break; - - // Commit the current edit - case "Enter": - if (this.editDigit === null) - break; - this.commit(); - this.setEditAddress(this.bus.editAddress + 1); - break; - - // Cancel the current edit - case "Escape": - if (this.editDigit === null) - return; - this.editDigit = null; - this.setEditAddress(this.bus.editAddress); - break; - - default: return; - } - - Toolkit.handle(e); - } - - // Hex editor pointer down - hexPointerDown(e) { - - // Error checking - if (e.button != 0) - return; - - // Working variables - let cols = this.lines[0].lblBytes.map(l=>l.getBoundingClientRect()); - let y = Math.max(0, Math.floor((e.clientY-cols[0].y)/cols[0].height)); - let x = 15; - - // Determine which column is closest to the touch point - if (e.clientX < cols[15].right) { - for (let l = 0; l < 15; l++) { - if (e.clientX > (cols[l].right + cols[l + 1].x) / 2) - continue; - x = l; - break; - } - } - - // Update the selection address - let address = this.toAddress(this.bus.viewAddress + y * 16 + x); - if (this.editDigit !== null && address != this.bus.editAddress) - this.commit(); - this.setEditAddress(address); - } - - // Hex editor resized - hexResize() { - let tall = this.tall(false); - let grew = this.lines.length < tall; - - // Process all visible lines - for (let y = this.lines.length; y < tall; y++) { - let line = { - lblAddress: document.createElement("div"), - lblBytes : [] - }; - - // Address label - line.lblAddress.className = "addr" + (y == 0 ? " first" : ""); - this.hexEditor.append(line.lblAddress); - - // Byte labels - for (let x = 0; x < 16; x++) { - let lbl = line.lblBytes[x] = document.createElement("div"); - lbl.className = "byte b" + x + (y == 0 ? " first" : ""); - this.hexEditor.append(lbl); - } - - this.lines.push(line); - } - - // Remove lines that are no longer visible - while (tall < this.lines.length) { - let line = this.lines[tall]; - line.lblAddress.remove(); - for (let lbl of line.lblBytes) - lbl.remove(); - this.lines.splice(tall, 1); - } - - // Configure components - let lineHeight = this.sizer.element.getBoundingClientRect().height; - this.scrHex.hscroll.unitIncrement = lineHeight; - this.hexEditor.element.style.gridAutoRows = lineHeight + "px"; - - // Update components - if (grew) - this.fetch(); - else this.refresh(); - } - - // Hex editor mouse wheel - hexWheel(e) { - - // Error checking - if (e.altKey || e.ctrlKey || e.shiftKey) - return; - - // Always handle the event - Toolkit.handle(e); - - // Determine how many full lines were scrolled - let scr = Debugger.linesScrolled(e, - this.sizer.element.getBoundingClientRect().height, - this.tall(true), - this.delta - ); - this.delta = scr.delta; - scr.lines = Math.max(-3, Math.min(3, scr.lines)); - - // No lines were scrolled - if (scr.lines == 0) - return; - - // Scroll the view - this.setViewAddress(this.bus.viewAddress + scr.lines * 16); - this.fetch(); - } - - // Window key press - onKeyDown(e) { - super.onKeyDown(e); - - // Error checking - if (e.altKey || !e.ctrlKey || e.shiftKey) - return; - - // Processing by key - switch (e.key) { - case "g": case "G": this.goto(); break; - default: return; - } - - Toolkit.handle(e); - } - - // Window visibility - onVisibility(e) { - this.shown = this.shown || e.visible; - if (!e.visible) - this.debug.core.unsubscribe(this.subscription, false); - else this.fetch(); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Disassembler configuration has changed - dasmConfigured() { - this.refresh(); - } - - // Prompt the user to navigate to a new editing address - goto() { - - // Retrieve the value from the user - let addr = prompt(this.app.localize("{debug.memory.goto}")); - if (addr === null) - return; - addr = parseInt(addr.trim(), 16); - if ( - !Number.isInteger(addr) || - addr < 0 || - addr > 4294967295 - ) return; - - // Commit an outstanding edit - if (this.editDigit !== null && this.bus.editAddress != addr) - this.commit(); - - // Navigate to the given address - this.hexEditor.focus(); - this.setEditAddress(addr, 1/3); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Write the edited value to the simulation state - commit(refresh = true) { - - // Error checking - if (this.editDigit === null) - return; - - // The edited value is in the bus's data buffer - if (this.data != null) { - let offset = this.toAddress(this.bus.editAddress-this.dataAddress); - if (offset < this.data.length) - this.data[offset] = this.editDigit; - } - - // Write one byte to the simulation state - let data = new Uint8Array(1); - data[0] = this.editDigit; - this.editDigit = null; - this.debug.core.write(this.debug.sim, this.bus.editAddress, - data, { refresh: refresh }); - } - - // Retrieve data from the simulation state - async fetch() { - - // Select the parameters for the simulation fetch - let params = { - address: this.toAddress(this.bus.viewAddress - 10 * 16), - length : (this.tall(false) + 20) * 16 - }; - - // A communication with the core thread is already underway - if (this.pending) { - this.pending = params; - this.refresh(); - return; - } - - // Retrieve data from the simulation state - this.pending = params; - for (let data=null, promise=null; this.pending instanceof Object;) { - - // Wait for a transaction to complete - if (promise != null) { - this.pending = true; - data = await promise; - promise = null; - } - - // Initiate a new transaction - if (this.pending instanceof Object) { - params = this.pending; - let options = {}; - if (this.isVisible()) - options.subscription = this.subscription; - promise = this.debug.core.read(this.debug.sim, - params.address, params.length, options); - } - - // Process the result of a transaction - if (data != null) { - this.refresh(data); - data = null; - } - - }; - this.pending = false; - } - - // Update hex editor - refresh(msg = null) { - - // Receiving data from the simulation state - if (msg != null) { - this.data = msg.data; - this.dataAddress = msg.address; - } - - // Process all lines - for (let y = 0; y < this.lines.length; y++) { - let address = this.toAddress(this.bus.viewAddress + y * 16); - let line = this.lines[y]; - - // Address label - line.lblAddress.innerText = this.debug.hex(address, 8, false); - - // Process all bytes - for (let x = 0; x < 16; x++) { - let label = line.lblBytes[x]; - let text = "--"; - - // Currently editing this byte - if (address+x==this.bus.editAddress && this.editDigit!==null) { - text = this.debug.hex(this.editDigit, 1, false); - } - - // Bus data exists - else if (this.data != null) { - let offset = this.toAddress(address-this.dataAddress+x); - - // The byte is contained in the bus data buffer - if (offset >= 0 && offset < this.data.length) - text = this.debug.hex(this.data[offset], 2, false); - } - - label.innerText = text; - label.classList[address + x == this.bus.editAddress ? - "add" : "remove"]("edit"); - } - - } - } - - // Specify the address of the hex editor's selection - setEditAddress(address, auto = false) { - let col = this.lines[0].lblBytes[address&15].getBoundingClientRect(); - let port = this.scrHex.viewport.element.getBoundingClientRect(); - let row = this.toAddress(address & ~15); - let scr = this.scrHex.scrollLeft; - let tall = this.tall(true, 0); - - // Ensure the data row is fully visible - if (this.toAddress(row - this.bus.viewAddress) >= tall * 16) { - if (!auto) { - this.setViewAddress( - this.toAddress(this.bus.viewAddress - row) <= - this.toAddress(row - (this.bus.viewAddress + tall * 16)) - ? row : this.toAddress(row - (tall - 1) * 16)); - } else this.setViewAddress(row - Math.floor(tall * auto) * 16); - this.fetch(); - } - - // Ensure the column is fully visible - this.scrHex.scrollLeft = - Math.min( - Math.max( - scr, - scr + col.right - port.right - ), - scr - port.x + col.x - ) - ; - - // Refresh the display; - this.bus.editAddress = this.toAddress(address); - this.refresh(); - } - - // Specify the address of the hex editor's view - setViewAddress(address) { - this.bus.viewAddress = this.toAddress(address); - } - - // Measure the number of lines visible in the view - tall(fully = null, plus = 1) { - return Math.max(1, Math[fully===null ? "abs" : fully?"floor":"ceil"]( - this.scrHex.viewport.element.getBoundingClientRect().height / - this.sizer .element.getBoundingClientRect().height - )) + plus; - } - - // Ensure an address is in the proper range - toAddress(address) { - return address >>> 0; - } - -} - -export { register }; diff --git a/web/locale/en-US.json b/web/locale/en-US.json deleted file mode 100644 index 9a855f0..0000000 --- a/web/locale/en-US.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "id" : "en-US", - "name": "English (US)", - - "app": { - "title": "Virtual Boy Emulator" - }, - - "menu._": "Main menu", - - "menu.file": { - "_" : "File", - "loadROM" : "Load ROM{#}...", - "loadROMError" : "Error loading ROM file", - "loadROMInvalid": "The selected file is not a Virtual Boy ROM.", - "dualMode" : "Dual mode", - "debugMode" : "Debug mode" - }, - - "menu.emulation": { - "_" : "Emulation", - "run" : "Run", - "pause" : "Pause", - "reset" : "Reset{#}", - "linkSims": "Link sims" - }, - - "menu.debug": { - "_" : "Debug{#}", - "backgrounds" : "Backgrounds", - "bgMaps" : "BG maps", - "breakpoints" : "Breakpoints", - "characters" : "Characters", - "console" : "Console", - "cpu" : "CPU", - "frameBuffers": "Frame buffers", - "memory" : "Memory", - "objects" : "Objects", - "palettes" : "Palettes" - }, - - "menu.theme": { - "_" : "Theme", - "auto" : "Auto", - "dark" : "Dark", - "light" : "Light", - "virtual": "Virtual" - }, - - "window": { - "close": "Close" - }, - - "debug.cpu": { - "_" : "CPU{#}", - "disassembler" : "Disassembler", - "float" : "Float", - "format" : "Format", - "goto" : "Enter the address to seek to:", - "hex" : "Hex", - "infinity" : "Infinity", - "programRegisters": "Program registers", - "signed" : "Signed", - "systemRegisters" : "System registers", - "unsigned" : "Unsigned", - "value" : "Value" - }, - - "debug.memory": { - "_" : "Memory{#}", - "bus" : "Bus", - "busMemory": "Memory", - "goto" : "Enter the address to seek to:", - "hexEditor": "Hex editor" - } - -} diff --git a/web/template.html b/web/template.html deleted file mode 100644 index 69f9ab8..0000000 --- a/web/template.html +++ /dev/null @@ -1 +0,0 @@ -Virtual Boy Emulator \ No newline at end of file diff --git a/web/theme/check.svg b/web/theme/check.svg deleted file mode 100644 index ac12455..0000000 --- a/web/theme/check.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/check2.svg b/web/theme/check2.svg deleted file mode 100644 index f0c4196..0000000 --- a/web/theme/check2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/theme/close.svg b/web/theme/close.svg deleted file mode 100644 index d70dcf1..0000000 --- a/web/theme/close.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/collapse.svg b/web/theme/collapse.svg deleted file mode 100644 index 7d7ab60..0000000 --- a/web/theme/collapse.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/dark.css b/web/theme/dark.css deleted file mode 100644 index b1785fb..0000000 --- a/web/theme/dark.css +++ /dev/null @@ -1,28 +0,0 @@ -:root { - --tk-control : #333333; - --tk-control-active : #555555; - --tk-control-border : #cccccc; - --tk-control-highlight : #444444; - --tk-control-shadow : #9b9b9b; - --tk-control-text : #cccccc; - --tk-desktop : #111111; - --tk-selected : #008542; - --tk-selected-blur : #325342; - --tk-selected-blur-text : #ffffff; - --tk-selected-text : #ffffff; - --tk-splitter-focus : #008542c0; - --tk-window : #222222; - --tk-window-blur-close : #d9aeae; - --tk-window-blur-close-text : #eeeeee; - --tk-window-blur-title : #9fafb9; - --tk-window-blur-title2 : #c0b0a0; - --tk-window-blur-title-text : #444444; - --tk-window-close : #ee9999; - --tk-window-close-focus : #99ee99; - --tk-window-close-focus-text: #333333; - --tk-window-close-text : #ffffff; - --tk-window-text : #cccccc; - --tk-window-title : #80ccff; - --tk-window-title2 : #ffb894; - --tk-window-title-text : #000000; -} diff --git a/web/theme/expand.svg b/web/theme/expand.svg deleted file mode 100644 index f8cdfe5..0000000 --- a/web/theme/expand.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/web/theme/expand2.svg b/web/theme/expand2.svg deleted file mode 100644 index 9673e56..0000000 --- a/web/theme/expand2.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/inconsolata.woff2 b/web/theme/inconsolata.woff2 deleted file mode 100644 index db38b3a1289bcd133838d8ceffb5c0c3d4bd81bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40712 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

ypWDvfQ%uDKZNogj(kl+y1U@d3I5Vp^@r}Pe7 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

Q-2&`WXJk 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

b;*%R_8rEQ3#iE2LX~N-%q1fw zTdl1fU)aieP^b#D7E-dw-I9G-Qq&ka78PO^cY#78L}Nx|f=gosXeO?mBt2dAD?$t9 ztN!UD>p2x2A2@QyJ&8fnp{xMHtbE9^-u~_uHhG@U))joUWi&bX)X)i_XMJ^AMlom* zWd!Do2<(gGAg!oeD-pomA)-M-yI@e25g>#l;b`E7RkYT?vi_9~T_w8{h>^YW3beQ| zgQP4=5~-0}NW3s-Z&sHTJ8+J&Kg3FbB?gnCLccm!%2@!uGUCFXFge_StH`bdf8=nsK>jHZ+*|wm-=(**TmR}w|M<^t{qlb*a@2X+qyYi& z0s;Q<$vZ0k4_C&>;J0ZxbLiKHxP7rO#$p1?(ZSFua{w~2h&?gV5OSLAPCCsYSxJ>j z$IdA?>7txc1Jg!Pj5kQk#w0H%0+BqLsPvH21(L&-!hlPUi9{MOo9U{?sv^z?Pdamy z$(W0HQ2w zfSEKP&ZeW7t635Oc9cUouMPxr6}PnGKh8nsi+#^qhiG(D#T9a#C5LgN2uGJ}1Se=V zNsykD(*}cKu4%Y4d06Qe%7TOlteEt(83M^BF{&hBZqdrtVN6MpE3N^13QAP?OzHe} z{!}&D@vz;RhIiOrT~0?CJ{=KL7)4YbSnRk{uDfBJ+1?vdUC1+2-ED0hk3p;dIspK& z!Fj5mdSm?Lt|8Xix-6$zoe`~F<1t%uNyc$Ivq&{#yybJ;;L-WUR4aL}^NY6jqS(D* zE|8x^;`(!R7c{&4akFNZKAM;5E$+-yo89iZdW)D|Y=7AOuPZxj%QDA%WWc#HA!b4t z5fpCWFG}buN{A3JG+{N|W`~GfaDNxA}qUYRru<+r_ z4W`_$hZ;uhF@+`$GZ`X-kuiu@30^cPX2jvNj~A6HA&u0??Gc*ehU4eWD%=TC zT^%^dVejXJW0dKhIYn;R?gyJ=ge*5%H&?g%Moa0kr`NtoBft$Nd)sfWbcR!1=i}CS zclEv0?y>U@X_d1x>@mqTY9=7uCUUH=0C-j8(x4B!HSMr{FLQ*pz<0IZ(W z=gNDsk%$oHG{a2=9RGYNhp-~#AzP6Gf_hsC?pURacvMS?*u05bBD_1(qGl#62Am-Y zkLThmNth>z*`OXzHN?mWNi2NY#0lp~yo^fF3s|*!ORHIslIk@S&)_hoTMcWr!*Jn6 zEj+^n5fx{{8+9ceE zE^#~aK=fLhX@nC^{7ylEh7=BRb9p9ALaD6_EQA3@pw7uDw7BjKK*upqRat)_0<%Zf ziU_TR4AQx15hy(4^<|OvG_p$bIlX|sx}`M`XbNmBR6BETKtC5A!%T#1!$~?VNFb7) zQY3h71~6g67@K4rAu<6j)SYGy>9I+&gr*WtsE z%B5nl^!;Ds-S~Z%>OO@({LUUQJ%__V5&ezgKwUuKBs26L2nM@deawx(t*dmR{=u&h znB_fD$N3;R{(#Mf9R3FW8+Gl+mD#2iNZc z@*!cV5tY$xm{~iE*P;lIGaz)514u-4=tr9fq<>cp3HBF^x>^*&EF546I>@iwVQq$v zLl=p)^otmXi2>ccqMkL)Cmqv;TDJ|SSS%EVSL$G0I?r70>cR~!I~|L$5gg!{&u5d% z)oGoXrA&h+oqJRZA)el+YLgq1>myUD#91h!FCcUWpTMC2+RX2DI@)!*ZC1_?5Oc@k z%Iym6*G$XAP}#yA1ITCuYFXn~c|e>_%Nn8?gR!@N$@Q?Lb-Cr@XArN{KMMYexY`VH z1xg9@tix&b!0#r@x|q;uTDKn`2Ze#F5Bf08+IH;ub(&|?S-=S|&eF!OFDQ0I=Rc*r z9|!~zf*8QS-}nG|DL60~8m@L)lSLggAHJQejBx>#J~Ng!uFwtCgaFFTyBBw$*Y6Q+ zRCt@ieP?VDEk>PKuj^~Uc~@L?$z{*fc;9cX+UiAr@|2HUb6r2bcx93#iISy|O4a>t zsa&N!pBFDvmWPJOl|w;AX-tniYWWJV4GSOWRrAdDIzHF=TV;brXS9Yki|sFhL@+`S zNGO9|6I1fUt1}ddB~qDOp;W0gT3s_zHVsCT*&=WdJ8_zL=Xhre;BXKyfl_z{<^7H6 zqSI>}o3!ZPb8B7+N!{zY>LXlV^mB|%Km0Hs?&fu*X_-Cg*-^~`Q&F*lz z+@4P390N0{x?)|mqfHi@gEx_3|J0O7Wpag5rPf$lS=;11xwo^| zIus>9p5(>J*+u8mmv29Q{rQJLVQ>TzK%p^M9G*ZVkttLfoxxnh%&xfZ??Ou>zms6x>O_BPfOwB%o-9<#<7qWJT3j8FA$2v5~)nCP^yCGgGQ^< z8;mBi#cH!VoIH;?=I9ROCuGo0g&}wX(VhTsO^CTllf~v#c{BzQqOoz2t-4UE)EX`W z%SpF(_F4xFL!8QRK^01sTBC&#o!(%0hPSH%U{Bu8$j>cOgaoedBZ|TtmW^>MnICkO z!~L*NkmeuQgR}rY85LU-G@YcXMQ|XjL|Fi z4(HI_({B@nBSs8_1@blPKQcW`^dpCiR`@7PVvl1kvE?i)&)d^-nLw=%LCShn=);y0 z!-C;kx4Y%pUBRAfM&(`~*=*0aWrSZ9Vy)~FZfgaZW|jG5OY>ou^$&Uyw)KH#ua6FN zUN^YUfSU&7ENUk*!lyt*%v() zN^lAuO2v2k3N59(eSUKsRR(f~7r(O4@WJW#bu>5|*dBz7!Bx9op^JlV*zUyVpYXo} z-FB^g`=(NQye%r_JR;lpZQLB#rqWtLImxaoBn`Li${Ox&{gUkUA$}PiPyXjH6m=e)vN~wQ6E?H^x$z~lT%59J{;b>fav@VcOG>2|gG59R^@MCjeCnqf z3N28^<0K;qom=w0big6B?ZhN5LYa=81kfTB zutg~2LfcMEnhSYTH&bUm2y1^aJ*8IbzriUY_@Z^52iHGam8I+EC5U4dC$p%etfH!> zuA!+#($>)>M=z_Wq^zQMbg&MB}Xs2sHCi-s-~`?sYTM((IrPOcTq`MMO95* zLsN^St)okhUY??ovWlvjx`w6}Nn1yk9KD?4ZRuuqkk7Y%C+J!>#+YhqVs$&tg^apI zc%Y>A9zm+74{}5F?Lt`W+SV%u%p#O=p<>bjB5V=L_)$DxAso#bTD)e^-R>F0h53(C zZyZcT7r%@@s9~q=tqMVxS!|w#hzO~xeTvATN$W!Impu1L5W23tChK^^a>@z@x?hVB(RX+;rNXTuIPf?yAWthI*dZ~E3 zoDvV~&{q?eJKrZCx5>R_be<%XMe%;0$uiqvf{hQieslC;H{YPh^6;K2uo#3_R@h~R zn89o2I)+PH*_bJGIc2G_s)yGni|V@)6?4eSti)m*caSxqQer)8M!#rj7D zpx-9gAv%d2sNr#5)o$@|vS#EW&Ev7?R+24Lu5<^@RmZc5joqUARE&gZ8*oF1BO|s4 z3Tv=j0^RbHZmIcGGfg-KE1q&gSD~>Ew224XuYiKlklO4Zs?G?7+R;O7;jC5}CKcTh z=8yQ#Q=7wV1S_60pU#|*^pwWF`8hqNH_TRKM3pFbd!eP4$Ib*lCy+%g&8?2q@K6ld z0rsLgI~&KitZd;oaY@2&A_CEKkUKRaRlzzXM3&HG89alAl<~f9qzl1L+?~l%Qh^O1 zQ5fmxMVJ*ADIv(&-Ig-tt&Z#Tvn~Pj zRUt@fv$BxwZn-Gr1eJP1W>ai1M7L@Xb(KvH3Amnxc*+f1M~fP;zi_4g+{NK#cGf5a zmjznC9FB5Fu}Kuh(OUZfxzJ+q1Bl5@tkc~#G5IVG`BJ%^Y(>yMb8buVs^`AaB3`Py zUfl~k>}kR?Y2|nA=9z)Tfeu_Ms)*c;wL7Z(XP_8wqNO2Ct2>~J7DYwMcNu@Sh$M}6mnna&MO3iohnXc={ zXl^7<@*9@nTE8Fq{P+LEu#~=De+)vs*Zk3ywekd-U#Y*wjf%d;Ayvt{Yg%u7OXA%X z-OPTZok~nGUw=x)a*2>2^m_W*6*MKO{MHnC8C=%r+G33|yAgYN9RXiscjfj~Y>Ae7 zNt=wZ{+4vLcs=?GC#3i;k|Kso!cNQq=E--oOh(@t0_6QLAOyF_k;Z{p*o=F>kH!ve zj!_@_(ea#Q`_!jh>c45jd1@uBj2|)ctBPv*u`&p_?FJ8bprM*O!0?sZ-|&OmW?0K@ z)wd8@^tm+aJlrN-8g&}9o_bB{)cUE%tu}EawbtOGliaA45o?kYc#+~?^87G_~n$6$_XXUWajfTMc%LyW<1OGUir^H z;5&Zs|GDBQRs7GRqRXk^1mv38=NwOHre>M&a}Ly3b*COu07fm~cuKMio9_zU2*u3A z?4SOHxc3!5GZ1&;fm6c{3##uN#=rR4RNW{$nLY*Y=pOKmk(I-tCk * { - align-items: center; - border : 1px solid transparent; - column-gap : 4px; - display : flex; - margin : 0 1px 1px 0; - padding : 2px; - user-select: none; -} - -.tk.menu-item .icon { - box-sizing: border-box; - height : 1em; - width : 1em; -} - -.tk.menu-item .icon:before { - content: ""; - display: block; - height : 100%; - width : 100%; -} - -.tk.menu-bar > .menu-item .icon, -.tk.menu:not(.icons) > .menu-item .icon { - display: none; -} - -.tk.menu-item.checkbox .icon { - border: 1px solid currentcolor; -} - -.tk.menu-item.checkbox[aria-checked="true"] .icon:before { - background : currentcolor; - mask : /**/url("./check.svg") center no-repeat; - -webkit-mask: /**/url("./check.svg") center no-repeat; -} - -.tk.menu-item .label { - flex-grow: 1; -} - -.tk.menu-item:not([aria-expanded="true"], - [aria-disabled="true"], .pushed):hover > *, -.tk.menu-item:not([aria-expanded="true"], .pushed):focus > * { - border : 1px solid var(--tk-control-shadow); - box-shadow: 1px 1px 0 var(--tk-control-shadow); -} - -.tk.menu-item:focus > * { - background: var(--tk-control-active); -} - -.tk.menu-item.pushed > *, -.tk.menu-item[aria-expanded="true"] > * { - background: var(--tk-control-active); - border : 1px solid var(--tk-control-shadow); - box-shadow: none; - margin : 1px 0 0 1px; -} - -.tk.menu > [role="separator"] { - border : solid var(--tk-control-shadow); - border-width: 1px 0 0 0; - margin : 4px 2px; -} - - - -/*********************************** Radio ***********************************/ - -.tk.radio { - column-gap: 2px; -} - -.tk.radio .box { - border : 1px solid var(--tk-control-shadow); - border-radius: 50%; - color : var(--tk-control-text); - margin : 1px; -} - -.tk.radio:focus .box { - background: var(--tk-control-active); -} - -.tk.radio .box:before { - background : transparent; - border-radius: 50%; - content : ""; - display : block; - height : 4px; - margin : 2px; - width : 4px; -} - -.tk.radio[aria-checked="true"] .box:before { - background: currentcolor; -} - -.tk.radio.pushed .box:before { - background: var(--tk-control-shadow); -} - -.tk.radio[aria-disabled="true"] .box { - background: var(--tk-control); - color : var(--tk-control-shadow); -} -.tk.radio[aria-disabled="true"] .label { - color: var(--tk-control-shadow); -} - - - -/********************************* ScrollBar *********************************/ - -.tk.scroll-bar { - border : 1px solid var(--tk-control-shadow); - box-sizing: border-box; -} - -.tk.scroll-bar .unit-less, -.tk.scroll-bar .unit-more { - background: var(--tk-control); - border : 0 solid var(--tk-control-shadow); - color : var(--tk-control-text); - height : 11px; - width : 11px; -} -.tk.scroll-bar[aria-orientation="horizontal"] .unit-less { - border-right-width: 1px; -} -.tk.scroll-bar[aria-orientation="horizontal"] .unit-more { - border-left-width: 1px; -} -.tk.scroll-bar[aria-orientation="vertical"] .unit-less { - border-bottom-width: 1px; -} -.tk.scroll-bar[aria-orientation="vertical"] .unit-more { - border-top-width: 1px; -} - -.tk.scroll-bar .unit-less:before, -.tk.scroll-bar .unit-more:before { - background : currentColor; - content : ""; - display : block; - height : 100%; - mask : /**/url("./scroll.svg") center no-repeat; - -webkit-mask: /**/url("./scroll.svg") center no-repeat; - width : 100%; -} - -.tk.scroll-bar .unit-less.pushed:before, -.tk.scroll-bar .unit-more.pushed:before { - mask-size : 9px; - -webkit-mask-size: 9px; -} - -.tk.scroll-bar[aria-orientation="horizontal"] .unit-less:before { - transform: rotate(-90deg); -} -.tk.scroll-bar[aria-orientation="horizontal"] .unit-more:before { - transform: rotate(90deg); -} -.tk.scroll-bar[aria-orientation="vertical"] .unit-more:before { - transform: rotate(180deg); -} - -.tk.scroll-bar .track { - background: var(--tk-control-highlight); -} - -.tk.scroll-bar .thumb { - background: var(--tk-control); - box-shadow: 0 0 0 1px var(--tk-control-shadow); -} - -.tk.scroll-bar .block-less.pushed, -.tk.scroll-bar .block-more.pushed { - background: var(--tk-control-shadow); - opacity : 0.5; -} - -.tk.scroll-bar:focus .unit-less, -.tk.scroll-bar:focus .unit-more, -.tk.scroll-bar:focus .thumb { - background: var(--tk-control-active); -} - -.tk.scroll-bar[aria-disabled="true"] .unit-less, -.tk.scroll-bar[aria-disabled="true"] .unit-more, -.tk.scroll-bar.unneeded .unit-less, -.tk.scroll-bar.unneeded .unit-more, -.tk.scroll-bar[aria-disabled="true"] .thumb { - color: var(--tk-control-shadow); -} - -.tk.scroll-bar.unneeded .thumb { - visibility: hidden; -} - - - -/******************************** ScrollPane *********************************/ - -.tk.scroll-pane { - border: 1px solid var(--tk-control-shadow); -} - -.tk.scroll-pane > .scroll-bar[aria-orientation="horizontal"] { - border-width: 1px 1px 0 0; -} -.tk.scroll-pane:not(.vertical) > .scroll-bar[aria-orientation="horizontal"] { - border-width: 1px 0 0 0; -} -.tk.scroll-pane > .scroll-bar[aria-orientation="vertical"] { - border-width: 0 0 1px 1px; -} -.tk.scroll-pane:not(.horizontal) > .scroll-bar[aria-orientation="vertical"] { - border-width: 0 0 0 1px; -} - -.tk.scroll-pane > .viewport, -.tk.scroll-pane > .corner { - background: var(--tk-control); -} - - - -/********************************* SplitPane *********************************/ - -.tk.split-pane > [role="separator"]:focus { - background: var(--tk-splitter-focus); -} - -.tk.split-pane > .horizontal[role="separator"] { - width: 3px; -} -.tk.split-pane > .vertical[role="separator"] { - height: 3px; -} - - - -/********************************** TextBox **********************************/ - -.tk.text-box { - background : var(--tk-window); - border : 1px solid var(--tk-control-border); - color : var(--tk-window-text); - line-height: 1em; - height : calc(1em + 2px); - padding : 0; - margin : 0; - min-width : 0; -} - -.tk.text-box.[aria-disabled="true"] { - background: var(--tk-control-shadow); - color : var(--tk-window-text); -} - - - -/********************************** Windows **********************************/ - -.tk.window { - background: var(--tk-control); - border : 1px solid var(--tk-control-shadow); - box-shadow: 1px 1px 0 var(--tk-control-shadow); -} - -.tk.window:focus-within { - border : 1px solid var(--tk-control-border); - box-shadow: 1px 1px 0 var(--tk-control-border); -} - -.tk.window > .nw1 { left : -2px; top : -2px; width : 8px; height: 3px; } -.tk.window > .nw2 { left : -2px; top : 1px; width : 3px; height: 5px; } -.tk.window > .n { left : 6px; top : -2px; right : 6px; height: 3px; } -.tk.window > .ne1 { right: -2px; top : -2px; width : 8px; height: 3px; } -.tk.window > .ne2 { right: -2px; top : 1px; width : 3px; height: 5px; } -.tk.window > .w { left : -2px; top : 6px; bottom: 6px; width : 3px; } -.tk.window > .e { right: -2px; top : 6px; bottom: 6px; width : 3px; } -.tk.window > .sw1 { left : -2px; bottom: -2px; width : 8px; height: 3px; } -.tk.window > .sw2 { left : -2px; bottom: 1px; width : 3px; height: 5px; } -.tk.window > .s { left : 6px; bottom: -2px; right : 6px; height: 3px; } -.tk.window > .se1 { right: -2px; bottom: -2px; width : 8px; height: 3px; } -.tk.window > .se2 { right: -2px; bottom: 1px; width : 3px; height: 5px; } - -.tk.window > .title { - align-items : center; - background : var(--tk-window-blur-title); - border-bottom: 1px solid var(--tk-control-shadow); - color : var(--tk-window-blur-title-text); - padding : 1px; - user-select : none; -} - -.tk.window:focus-within > .title { - background: var(--tk-window-title); - color : var(--tk-window-title-text); -} - -.tk.window.two > .title { - background: var(--tk-window-blur-title2); -} - -.tk.window.two:focus-within > .title { - background: var(--tk-window-title2); -} - -.tk.window > .title .text { - cursor : default; - font-weight : bold; - overflow : hidden; - text-align : center; - text-overflow: ellipsis; - white-space : nowrap; -} - -.tk.window > .title .close-button { - background: var(--tk-window-blur-close); - border : 1px solid var(--tk-control-shadow); - box-sizing: border-box; - color : var(--tk-window-close-text); - height : 13px; - width : 13px; -} - -.tk.window:focus-within > .title .close-button { - background: var(--tk-window-close); -} - -.tk.window > .title .close-button:focus { - background: var(--tk-window-close-focus); - color : var(--tk-window-close-focus-text); - outline : 1px solid var(--tk-control); -} - -.tk.window > .title .close-button:before { - background : currentcolor; - content : ""; - display : block; - height : 11px; - mask : /**/url("./close.svg") center no-repeat; - -webkit-mask: /**/url("./close.svg") center no-repeat; - width : 11px; -} - -.tk.window > .title .close-button.pushed:before { - mask-size : 9px; - -webkit-mask-size: 9px; -} - -.tk.window > .client { - overflow: hidden; -} diff --git a/web/theme/light.css b/web/theme/light.css deleted file mode 100644 index 9239e4f..0000000 --- a/web/theme/light.css +++ /dev/null @@ -1,28 +0,0 @@ -:root { - --tk-control : #eeeeee; - --tk-control-active : #cccccc; - --tk-control-border : #000000; - --tk-control-highlight : #f8f8f8; - --tk-control-shadow : #6c6c6c; - --tk-control-text : #000000; - --tk-desktop : #cccccc; - --tk-selected : #008542; - --tk-selected-blur : #5e7d70; - --tk-selected-blur-text : #ffffff; - --tk-selected-text : #ffffff; - --tk-splitter-focus : #008542c0; - --tk-window : #ffffff; - --tk-window-blur-close : #d9aeae; - --tk-window-blur-close-text : #eeeeee; - --tk-window-blur-title : #aac4d5; - --tk-window-blur-title2 : #dbc4b8; - --tk-window-blur-title-text : #444444; - --tk-window-close : #ee9999; - --tk-window-close-focus : #99ee99; - --tk-window-close-focus-text: #333333; - --tk-window-close-text : #ffffff; - --tk-window-text : #000000; - --tk-window-title : #80ccff; - --tk-window-title2 : #ffb894; - --tk-window-title-text : #000000; -} diff --git a/web/theme/radio.svg b/web/theme/radio.svg deleted file mode 100644 index 3d6392a..0000000 --- a/web/theme/radio.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/roboto.woff2 b/web/theme/roboto.woff2 deleted file mode 100644 index 0167f193966bed1bbc30964561641ee0e674ad1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66064 zcma(1Q?M{Rur&y8+qV5|+qP}nwr$(CZQHhO+k4(~zNsH`HJz%Yl8f$2(v__4)oya4 zi~xWD004kM2mna`jX;C_0Dyoc|GW0z^#3V*qN2(&I9R^e0o~F#)zV=401>nh%8)_a zh%iuL+a#f1PSDAC0D!DOB*5kDAOxV{4&cWgFo6TlXUW#Mc&hv_N$uNNMS1r~L7qu_+XW$q$-B@BjW+=%jtQt*&P7J)Y=j z5xD`PrmI&PTXhh?;t*&+u^d&@WNfp7mshe-K8=4|>ltD#*uq&|9klM?8SrnyX*7%! z&q{S4B}yGt9VIDwOcc7jTwOPd$XBEf4q!u3pYiDM0tg|7rgzz%A($o7Bcz9oxt~<7 zrDq{a<1g(4EUvI&t`rI?idwTz2{r-MhJ+2o?|emL5!ED!r5DQ)H!7V%!Z*sJ#oy=G zL6r?*MIh011YYCnY3k`Kke0(G z{=B;~%F}jUIuUigso1a~_y=UsogY{Ti6F z2*_DeEPNT`14S*^Gb~PJSS;_Xu7-jPtsU8YqVNxT&;{J@{8mpA&Z5CJi@FIVmGB0y zN~tZoI3js??WyiG+{LKZ!hY5C{Ge-&-khQ(GrO}Y#&t|Ic89Bu{2F(ml6Et6sFZ5K zeH@>pUbLep?XY6Wen-(C@=&{H2VEST?BR1<{~g(`TMoP{o-4^wxo~C=bH~aCenoAn zY52y4liI%QRA!tcc7p~RwdXJP1uJ(hx!PhC1eXfG9XbB8~Kxec;#|;WK4WP4??E|>ZeRCjq%i(z4h`T$Ld1}s?sS} zh=Nx0SNXv%HZ3;$IS)Y8WADR(w~ljPoc<9qh~K~2U`D=&(;f9S%a2fOM zgAmC=FobV3*#U{9HG0@I%w=*l^!6xm;3?{pN_xxSM*P!BVU0KLV;Ah{6vJ$ux?882 zmnbC9=+j*%OMkRNKr)!iV3NJg&u%{*qXY?U`S9cF(+lfznl>;npi#&$H0Up^^V8QE zaV>G?X;=-%^7ZoomXmfE9?K6>)N+P&`jRy3y=xNGfdYW(fBVsOd;MkQWn>%;p~8V0 z6K2p;kW4X;NVa<$+pGR{o4b}8VMk;b&BnCj60E>*haiN9Jr-6lXsXv{59XOGOA1MJ z&RH*|77Y@naC&=ON)7>qQcphw;WQapaeC&gs#652Tpq{L8@~vN=hDB&$Oh0ld)aC* zi&y4L`~iQxo4TMoCMbY(p$_*-}8s2XFqv*H_p{QB(7ezx3zgzk8+y0OVWG%M*7I*3$&CgY1oY;14iik7g>v&(m`rNy%YVQ}K6$4~l!c0f+txFB`;Opy*PfvR9iqV_KoA5d}seNZ=}F zzJoQJg}uXbE8ch!5Wa|RP>_FDSy@q8fn9~cC=zQjXBL;bWxq!>joLzRD zoU4@TRBAf?NH7)-q~$M@PTBI;JiYO4ow~?Yupu7JC229KL*{qOz@|mPa}m3!ZNbIW zrG`}lrBf*f1XU0Z784HYF!j1*7JRLa1klZfXq{?O1eW}JaQ47Wa($7-py7&G>`^YmI|Jii zR^{g7``;F1|0G3ace1TgDW#cNoFDw^?#N=mW_CAv1WkTtpuBU7^xyDd>E7165H%Yu zp+Etee*bXBp>K7FHo2+ziPQF*z!v;EKtwuja%et-CLXUHKf`q&%9U>;(6&zS5Z}M^ zy_{a$P`!JAa#B_3K>x#K*oTOC05SEws5_JJ=|AMYKfiwuoOeto$|A$6sECM&Du}2m zDk?_PKPr2Fz#rfbs}kpqD0&E}XB9rkHE^=#q@WqsSh>t%sK13@fF@78kDr^bHhv$# zx_^K_Zy!IvAfX`lIRNi~`bg#G`G$%>ii$-ABjxD(Q4>6XSYLHG#yIVk`wqv>{72`c zKyk=)0{jpF7yy;I8KCt2`Xj;M{qUX<`Bx%#*q)NLIOn1k#LU*YzFr{xvweFzT7>&~ zT7zieKWSywVa}`o`x#S4B)zuRH*-8(23KA9U2VYB2?+A@q=ZO@Xg&l0y6@Dzzirxr z`66ubbpE1(GC>RCRS~hd0!*k*4aqqFEQB28;HT|AF+;CVj|v+jP^ist0{9$>Io zEvJJi(*rov{Yl@Ut%O~{6^0Xpu{coV!HU=;1dZz1xdX(Bl**YogC-1HxGtnw!@?|@ zIeBd9Gawf?MpkBahL)zb#?-n&A`(fY5~yT4Va#PRTg+EVal=c5%4m1iQ&m@4TU}q_ zVr6G(Yi$7tAYcLp_aR82Vg?THL5LvH!YaXFr(Tj~0*;eeaR1_Fh|y#72foQxL;Qk8 zm50eti+~w>F(CkEMw?TrjaACHrt-M+n59`@-SGcwOn^bQ6-fDX~)c z$-T5ssWOwfsXi-zvbSmE<1@!K7G*P$TPK_&Jy5xYlzb~!Pfx=SUo&~k67~D6<{aNo z_WD0k-$DosKZAd@B(Nolw_$kXX(45_C z<1?9rk*m}T)x>O8UoJT)HF1=-Y{jrYp@c(7OYs&F7mXFQ<(NA**YS#4t@cCSGR#ir zd7HvvFS76(sS~ZwE?cr8k!PwzyaE%ObhQ*4;JN&FnK?!^8>{#etN09U3qS2&79Dw) zE`Os&DF)|1LX&TIu(q(gc%M4?N(f|)`CSCw+O`THQw!=uoUJw&>>_RF3R1mF6*@}1 z{GXg7NHfZvbP)K26w%l=w=)y*CM6=a1POpZCN)2@(5lM-p9vZSKJ|bP$yy9oGQhkh zh^&Un7}TS$$l(yI+&A^O?dN_*!bhx{M%Vow`;%xh2ft*xikc@`T+E{YfkO#T;JY!9 zC3unnV}-zZORo%X@3TOX#eKEQ>A=)PH?pQ<8lI{i(+MlTe_Lro%khm3rTjpF6ckMI zA%`Tc+{fe$!x<>Zkjo?m5(D;b(cncTr=>!P&BxV5C9&PYBPdPWar0B%x;aAA9DqQz zx?-($SAPX^#QyWPPFM}Lne+@}7+?|7XPFh$d?oduDu^mss46gz77nxHQ$Zh8sp9{j zs|I#jC|56=*L6D;rNEE!f8v`}F_4d!_UXt)wF?oPb}@C6GKdo5Dh0|EO01p)p5aRBQLVhe+b?GY3quF38F`vri)Afl1m z`mp8oFAk)nQS9N&B&uIh8r;@l+Bv z#kxuvTW&zk8eNiaN#vom!DAF?A;g4DX$f0$>BnA@ zCB(=Y75oKJindicsob#L?9t~IsyUuy8$Sp}ycJix6?VLpmb}%rzUfw;#o2rqXXG|c z+u{!(vk0RB#@NXiVl<|Y=vFZ>Oc<5YM`2El0MQH)`$<9*`6UXB16GQ5!BqmX9J>>@ z5V$0MidzOJQyLXi?pSW>;3#vqfs}*E%3Ny_LcnA(?^M_8jxh_viY(`VlSYOR{f^!EuF%bq_l6vhC2NQm9`dPSm!HH$`gG43SIj=!bCC zI7bBas^+v9#UJr19ntQ2Nh`N);l1|tGA`?hFAov&Y+BrjEX>Xwfx6f6@no0e4oW^> z;Z3fAlXg}(w|m)EuVAx$b&%s#lZ%9}Sq&MF&&Rnbt2TnE^_y^@n) zzkC@u?!1dFMGdZ!Q2)U)RIHz)k5VU`FyS#w7$sg9N6AYGr)D-$cc6OzTR|m?gn@oa zq0*?jp?P|%ntVuxQ{}87owj$BxieRW>c=q*azk7$mPCCzSW^3yV#Di}zi#1+yA0pp z(ftC~KrBH#q1Y9WIr_@v5xQ1KGF`c@tyE~M)AcwVqw|S_)CLYW0n2q>7= z(){7-a3G4O00<(csIV}c) z^B0VwWICmu*9;U@GXsSVtEb=OAk*{xZa`}E1i_O-`{12f8h8-Eg$JQe#sm{1PyhfE zlUH1tUtD0KXPl^RMh>rv|9_(xU62QGgY~+nW$>bBPPACzn4nhF?qsX%^#D};A{aH@ z*fRzn`9FBU9~d`G4$@jV!R7~msspOk0}N&ZLN=`q!k2rF^RM18?7ETu!Z0MMN8^~V zuK#Lfp?+4(zkhxD95Q?t4HXU@0tAR12QF;r0EpdtR6$T&dgvG_6O)L!xWU-*%=eYNYuNHxAB-+{Z?}|0UlGIU$?#9;Amx5lC1{dk3}Rn`?up z*YfouC!{;-ClKJ~{I%zXUj67Ullg?FD5FktToHg;f@{vMd)a^JY3Ls$5}9N+G2Iq! zvMYsP?Dp7~wi^bBYnSN0N z_V!5bCPNls&_EC&k+ESp`au&$7*rV#JRf;q4iGjFBdBQ=@d|NCkxXYyMr2vs&eXCt z#V-n-hDvrx!A2&X(&k)D6eq{Wjn=qai@(^d>#NHAt-{lW^z(MfQtT>}Ntc&pk)8U* zs=a(S-uL4uLgG=z22Su4Cm;~-cXy*em=>7=jdq#@B9;F!;Br2>lt?Pu$V7V=Nxwy7 zau6+lGvbmFCSlroZGZAmGxQ8DT&rQOL{+cgsfc6~en zLQ->Kl}x8K#G^KCUB4hE44%W594hj-n4@00XGiJfM}p+Aar zOWu&X-A9oRsyKKxNY!GhOfR`#saoA(ZqYB;%lP#vo?3MV1?#Q*9uf$O zfkyX{{y%SlhAH+0wp#&-GR7teODYfN{jwvGzWBZYJQZh zlWM&^#E>c{*K6-nP~JBq&IAWBA$;hb(c{S&z7=H0J;?KlxLoRlhXlDtC5@}_zxdl< zuZswsQG>`VA{7*d2c#oXN~N8+|8r&Gtf26%gMpD>%7nf)$mUw(&{djBI_3EPN)KE> z91&&2aok;6A2%A_a`3;8(_FW2V2~aM<#Cee_D&EA2`R9WN=nHfB{1phyEw3 z=--Esw8YZn^aK+n%SdqrazM4MGl^;^x^`C5=elOUre}^BAGWZ%>lA z_-k`;8DU1+&&stc9mVk_q4|(b8W>ppxe47dwmTrnn@M7$X&X{Ead42mbk%JNz;nUYSteP$%Lji@^pZ0z&UzGO>#Uy!TY~IKyY;7 zvD#|!kzLEap!Ia%QG+i*C z*ZuK`7g)a>Tyoc~i(j*XU`PZixo%+3>jod(TXx+0n%|a1{4vb&@wAFA4pnoL_jP=Z zs9AbhCY?<8t=bFD%Zhy4RZRND6;w<|V!zg!1@Z5j;okK>?KWXg;v_mhrLA&&ZB(@D zV5P^@Ew#Im1RcLYdYBnGd&o;^YEozAf&c2O(pa!r({hZ!22jvqUre}h60jv$ z<`ZU26xkWPAv#~-4}ITUDM-hq!;HrMu^&{Pf060m$dX6uJmIT4C_LI%Lc66co4C+S z={zP=aMhua1tSYc1Mx1q%bT3wuJk#4E$35}_^Ke+sY-?m&LszJ>?+Vfn{ki>K!$T; zMvlSse!m;h5U%N#JX$S#V-|G7t*k-z(W0&RVMwi%iqwE;(8j6(6@i^(g0y^63u7`i z#4tTW?VuW4^pWXn(`yD$xdKrrZuXm1p$cQxRvWpKDPWk>zEL9XXFL#yW^)Vgz2Y(j zxvmafj%Qtz31Ai{PNVB5v2O{W_SFHQSuR zC>E~Y`3J#a;v++pOeR;!N95hdz}dN1xh+e1)NI8g8chw0TCs+^!uwOQm&08AOd5-P zn1R(CN*YQu8FL0f>y%z;Y*LL;&SIT7OjhbBrPS0l;jPtywHK_&3L!6inEX&pR}m*S z-I1M%6FVMD-rgVE$Q4W}j2$jV8=@2$YE&#vppfexa$~P>)EQq)Vz3oTa?Bm7#VS*N zr;tdzc>ouYdBVpfFK-sXRu#?8muS`b2JL?AC((sQV=ytWCbXjeoWfu-nam=M(ym&E z0v}}qK)c^$oR{TNI-w<@zXZFpBc5LAUwpz|i}8hCC{CyTRqv+D&$0kk=>MN1-}$z8 zEyj3asAZH3zb0S0V%4AT3hEp`e&q5fa4-~>h}6LCRj;e{<&5fjuTCX}N?ge?16D=m zDPGm)DQ4%n_pUP$8cdbq5)s}ORX@FTU@&oP9R zcSu!sbQS$y*pP5qzgRXV!jVAVdRGv6-o(}eCstHNpJgA{c_!q@f|$+sMuDy^Hi$vd z6elixIwO!^?71C;{@p1~m9s5d+J(|L`bRo9JIb9_I5m}Gt4v9y*2HN+Bwo2uykf=L z&vG#XPaN^mUnS(yTQT+jhayZ4S)ob3n5C`39iUH!F%{$)aMRWko0=}SN!?VR zkOdTd-r_awaL;niIrz^&^a1fd5#VNrO^4X=U*YBnkJdNlk657t@!H%{M2+x*IY2Ps zu5oeO{zrqe?bHv}7;McL%+J9+0_*EA7=4-oEiynLLpkGx0I7r~NAYpgX5F$pf%_ak< z4GvTN(t7)9Ibn|vdeIqui9YOOdRKDMvO?(gJ8szz)$aMKZoqGNABU|POlSEtsj?Ui;I>=DI)4*87`cN$%4|Cf+BbU!OD+? z60O^o*Avt&Oc_BqS-ZnlOyx;~87lXdfygA)o4EQO+L>7Ho6-5cfUfiIYrTTME(eO}nXP%W@vC|un6#q?leLX*locGincY?aZj0~wmwjGlFJUKYRAj}Nld;3OTq zpwq%ttXytKz-<>5^8^6+-*uI>0lF|^B&=#jnv~d5UY5y$cRlZl=9WWau=vb#&Hm%m z-whDb0JvlCz(qtAP(ls<`s$C7Kb|28pd?c#jBwU~1W+oW& zdZvI+%DMF3yhOnoqYYRYz)H6n>8^7Sy6hwKlg^^goNv!1QzEY0N2NIbCytaFuW6#3 zPPYQwO!cSjJkkKA-WFWv2Gjr~`lHlWS+*Dcr{ElzotqYCmAo7sf>2P3poFy)rii)9 zl9*;O>s2*mCp&b$=ONkwuW-itEv8L)xVzXw88hCyYY`j%pD@7&a5~8w`i|$(k;DvS zTXoE{RpzBqE_sxa2u8mX?VS~iXH;_Ow0DMj`>?lrFfTVs2tJViQ31%?YrBVUOLm7{ zh}8zU;M(qaGT;TB(+5z|I-_{Zc{eQWGe>c~_ILf#+xbi$_&aVA_H-~+GTrp(@_Eg+ zrMB$xszJSb_Zn~yxqDNlMTLc#t>YlVAEQj==Yhis`;rr|_;0Uz$B3xJPf!(4Ei7CJ zI=K-9)ekR~SyDoXgM9Yj{Zcsx*JewbV{qI%A|@P9sD%echQ=-g-Y@Ux6+teUPG>sx zcMHLB9hN7_a@<5WRjYO{pQn(`Xf|6~>6iENR$eLa;jR3X>&jtj?seKn2NEJl@~{L| zD|_o=2O%N}Lxhh*$uFQ3{!id0z!@$5( z=rHd#uY((XScX)zF;yPotBiAl+bFY@Oemtxz^j`_4#dV zX>v1vfB!=S;77m!3i%&b!wMQXfD}f`5G+~1lt#-MI(YyUOi(BK&ootxY8hMp0TBQ! zE--S``se8B>g(+7?(e|C!^FkT&(YJ=)|uMI#p@>7T;PSWYrTyvQ*Ly=NlJO0LY`=yVC zx5lpS&;hfvEk4t);C=OnQ^xzrNsUD@30j{qG_up`_1f|#I<~sS0Iv{a4?bJlRN&m+ z+I@pWsWp1s2*PXl=b>R<43{4kSCl+zIp6cc{BHDLYhImIovnEgzg_uHXBul0ZxS8~ z@~53yiA|RnTHxL}D0SD`r-sabKuCDnZ@tbw}zDiyZ%G}`l2Gk!i&5+Wb zU)0H+BJx)yLe%Hb>we_l_bRHF|Lci~FHPHBJ+5;L)4dCm7FD`$&sl3VZ3Ne4lzn0j z3;1fmPvwCn)9HYdDx}TV@-)c%_cp5MWHF30)5=y@a>2{UjjHY0l9jtfQw7)6PhSvQ zltpF5EgeSM>dc-#{OglIp9B_ZoDdC(*U5c%p7Qy}`~vot?!(?`F>YrLpc}xBhJaxg zPgBh95g3Tx0QR4V)zHr)ryrB%QT*HG#|Zc#*HRZ^wU52^FRwYI%lqStZl6xv@m}PB z*@GPY-`xlLMO!V#EkKt%vV}}}md2OGs7DrkE6jBTyI?k?d(u7WiQGRFq*%l#6geV= zCS%W=1;vSCrAd>rCfH8Lm82dHwk-P}+ox=(!ai6qM~38}1pP@4b!X@1IK=f~#LIBF z)>-eMY^L`Quf_J5+tc<9pVJMG=krSjv5hX@W``iu;i-ilq?M|L9<<9Bid~2oQxv-p zPp(Keq!-)7U8&_SiBAFre6ouo;gzK49fUpjhaq~t$kQOKUys!@u=02FG zD)V2YjWPyryd^b>k5ej^egqVOG;Drhd_yAde-QkT0p#2%;=@R=j1A#+DCVN3+Xpzh zECfo}i{lR~KoXono*Y@e%Ubv`(S8AjJemZq7EYi`*kPX;sAO(}{D8)P~`~6M+`jgzrR90#z zNi-JhhwQoTBJVD2FyNSsSh0cydl)RS14UB(x9`NuZcl%s^P_&qXNyEwvPYF0S(I5`3mq$Kf)V-!g$mY7soJz&z(XcprpC-#nQ zZe89!et8ONB-AJ=l9EM6jSg>Y0Sri%0odjD0u3%LL{Z)DIOR?q6$%lu0U3yD-sY*!jg-!Li;?dWe=JSu zgf4=X0g~p0}&qIdc(V07!$w?!t5}{+qmpB%8%lIlb3prv0a!I1;7L zw9g=}&9fz&fCH~n%y!`bh9hoVZc-1nlO1WAimXMzP;6pv?4 z!yy(Kv4b8oYEEN4TIe*c`*6A^P<8#2kF$xSZz5C+PennE_%6(J~=dSU=r4D?40_aS41{lJHh?j-a?1PyxRZA7+u9HYF( zbywM$hZXqtACEbS$NvUlBvu|H^nYV_yJ5^Z!3Z*pTpavZN7=_j zZcp^wOWoBoUOJz!8O<03n+8vFrfkQhOJ8q=H@m2}o^HfmL|&#-y`pac*(WnmZ=h?7 zz474aKmuutHlOD!FENxoDZ3JPCGZPo=iu*Mz4s^2?w^DEef$w+Z0H?~xaVFza{LJ< zsbeO2QBDcC8UYd0{9>ZTVw%OHw$dZU(!=`7;KY)J?B@z7Ap+2r@`VJCK{&+(V1)Mp zQQhOP*2!ZboOjlGTu_RY%omKtf)jzQ_@VCjaiZ~2;Uw_eKfN!4A{6E3uLb```-cZd z2S${L!1Da#!`H!yY3gE%iwn`NGX2icyidmUa*)g7pKcZB&t|%5b)(I zi~Gm^@8S^ytvlY}(2y?-#0w?m<~fGm;ZH2|JFUPwUlKCXuZ%-AOHI$vz2En&j1#@q zKaLJjk5RJH57Sf75Rnnm5)-tSlmBl)iltdvB>og%Mh%FnN>D!n1&nSG|7zjoYkMmX ze$ddZ$G`}i{Mc9Qij8w4^v?kg)%;?#bJ3o1C9_k2R{Tyu^+@(6<)r+1V}F(PFD|ih z2DOl`$yb3oyF19OqVb+uzPy6X#~6ARlwuF`yAYkGvHIRr8ye=rFVgG97WTwwBW zVxw{it&D|~nlc=bPx?XT8j^DdV zrD}|&iki~TgeWtWwh`7^Fd55;*wysQXJYD&iytb@aQp*J0Tf6{fpc;IqXKU-J}Q3* z)uwVMZH$Gi11W1=@?!CNWs35!o3N^f%nVKK2hF9q-rG%sE;k0SJsop^IiZmKzPA1! zu_TAgUSxV3&~FaQ z(QM}h9g)BIiD~;nDO0ugKYjEb&8AFA3oACw<`M_IIS%IPh}>jKj9rS;85n$3vgjeb z?Cfi!QIC0@T-lCgnWbi_IGD-i>d6l6DgY&NVZ|kbjrJsXZNWSKs5=_-h)n;eB>?Se z&wWFGgeNEusNj297KZeA>s~VhIG6 zvZaXxl00=$Kum>IkzS3Z{7gqjm?rd4q*JI*>U@$j!Yc9~!{ECLP1-ky;iugK%NnBB zY4iADES}h>X8UX23)6p}ea*3a(p<*w2c;jm2KiTdD8J=BQZ?1{kGf(N=Y=2{4T{h9 zWx9B9s&DQ}7u19S>}BVbcgFVCd_&Q3e3J~Z`H_x1Sqil|f&j77A%PkPuV1Ge?SndO z_i;3X7UrFClo{O#i)|fVizc{z9M4`l58{EI5DE4OPT%&Fx2|U-PXN_70=yY)x zf08)l{{QfRcnydUC5~M@wP?zkwq6D{O?X4Sv`Zd$;2hb55!3|B#RSvO1luz-Uol9e zj_H%q!>$_P16yb(;kQy?sYHj)F^*PPk#q~S(oTagzv=NiMPbaV`Iz~Ik#|VP3i`JE zf_I?yV?Ul9*%4YBWjdD@lQBIUk3e0?T!IZ+3>9{S_zo$M`74sAz$yY_Rdc5ipos`~s&sT#Nw z*k`7&AJI44*3ZbdrxM9#uqqgsott5v#o6hy;b-B|{h@($fHkGqH7#N8j}~ViO^gT3 zTCC2pbM_LgIqDrNIDo_OX(7I9{d5W+mrg3o z?s57E{Uvb0Uu~*ZRG8$aUGBBG)teL!KqNpjV)XN&- zs5zq+4jY}d3TYs<$ACEhTK*E&GS_Vf0sH|S^*gw#)rvnori3EFDy$giBW|lHA}$*I z#rqJV-7ws+r}WSPyBerk6M87%paZS9yM}dlzY)Kdac6hR+yx;DYW{FS0fD-ud``HCr72xcM08=$)-LwAhSPpQE-0rvA{-WsMYHov*qToc+Nj6g3?+jfH(} zJ^fsm`rfvpey!P(OC`1&x$1khTGLwVnkGg|CF`%p2X<43H3-bzDay$C2J^I(fd?LW z8|1?xPLe($SbNxWQ7PZXX~&3M(OR^p21JCw z%HR5S%G3!QnM!B5^k+Y+7WstXR?=xC^G4@5dtSp-c?leRqmNuF9Z(W(!laZ^h^H21 zyQ6Bt)ZxES$%*6}hLH0rkj>^P-!!Y+#{#NdNGAC{CMPV=LW%W|0XtD3xn0m!JtCNT z7Uk|Bq%rY56=}~J)E!dM>8z0D?G~_DuH_3=1#YINEV%&q?uB5Y{?Yac2P#Q(B+Cma z@kGxnQH-l_lA+#4g$XT0Q|di;U3!eUr~}9Unq7);UqjWmV-_<5S{`D^1K}+T(wNT& z<)ie%u>Da=0|4M{`*gGGRTDXPGspD)?Cfcy9z~&hxF4HtY;RJ>rMkOM&lIUPg^lf4 zlgBybKu4`2aSg~mSEuW9ZLmnG-GjWb>U9rKMkL0a6v%5|@iq6EpE8mDl-;J3C=L!G zJK6-|DJht&MKrISIP}%=bocMPogTD$I|wA~<*YnqSWDQkHgWu(NJjW+@C<8HdAJ{> zJ~caxY1X_H7m!$s$dN42P_P9x$!Dg&uQz6X>|E0R1h+ifho{l8$N2M6EFC(~mJRKE z>{IOQsMVY`EA<5=?rKkZx;J#y$xoZ~SrnzGwR?!&CW+IW$z@FIv=lkG7Rq~*0TbA2 zf?WKJnDqzznK%`m)r1hu#mXu16Szvb)1_$m9WBzPbCncJ<2o&}e7y6dC&_Yk!l(TK{c*IoV-I`=U1gvV_%kZ1qTt+UZfI zO~^itgAxuB$iWHCuTi>I5r9(4mEMhD~+#4vdbR$XMZPUJ`kxzx9;5Joa_0FZrz#Htf8VrsvGXwPNkljm zEL-Keeyxk%b7}HiTNRD7hIgyaQ$l)7(KB>D5Q#tFDI9DhIDoMd#FbU5!(mjf4!Bw4 zkKObq#U?y9{?^mvm;Bn**CeoPuCcJT-PrZPL&IqwaAXuzBV`J-~dK)8B2)f}h6eD2bH@;dHSgN}LE7?@5oq^_HMY zZiCN-w*dtf<%l4l z%FmP4D``_pB)N!iEZ1lVi6v#I6r5aI_?VKs&-&UxVt0|4stp~UXGdB`@wy&kOp7$8 zs~f&B`e^gPu(WBl20EE?vWCdX zB6k@5QeauLikKa&>KdU?QhpAG8ceE>M#4&UV1t~{5CpsG=lnTpd80xX;%oy!?f{|8 z!p*VZ4!P$YQu)X4>Bx-xQ57kz>dZ{un`LBw!Y%rmt0CAKKB1w=M!~g>!gZ#fZMm!W z!Hm(E=NaiJ&#FAX>TM;PF7^RV?zY;Ox#Mx%SPy4wQ!h{M#zKF*4+g5dw`-@>NVS_% zWv+VWd%N}c`fYa2Fos>x-FL($!sT4zx+PNP^aIknTYXa<@N(N+v{gAIgV}AzMFB9~ z6In1gs9i;=L4de#LZ~yU?|$b@e^H~uICf3^;O*Lw-n76w^!(5k`--ubar^E%cq@5@ zMDNRMeX2_l>D4cdGY11~8B96JC$@c?R**`(Q-L-nLAX1fP4zH|yIIx*lj{4C>Shmuj2m{p7pTH1(d-=i6+?(?IU52c-Vy0HcLjKA>r@282V z_mvW-2WmTRydKX-I|jF~r8&03GU1jIU}z&t!`YDki{pYrJ~ii4~AA? zC^lDz_~2@JWJkVxqQ$n(l-JH6R6TTOYidJEZA~;;Cn|hhN0YiQ*-l`knrwqQFeAKc zoBWy8mh;BmtmV^uPV%;h68K(~PkDS6NwQnaw#bOUx7a*D9*@PU@w2fB57?$*9(_j= z+Hh{rTgm0-&#kqd8;9U_T+g&Yj+AbC*>?4NhI-FgEkaDXxpQxc4URg-SzstBUReVn zl$wy@z<@&k4LxN6_(*wXMkt?fh$+#8}$?RZN%<`Vz!jLvtbiEfA$vX=?zCzBb@ftmw6 z!ZVehf?wi?Ca~~Y?=(2nD|)z0PaL;I=aC<2{sN6UF)@;A^Zuj6i`%{AE3x;h#%2Gf zbhfByP6kJ4UJl~n7HKu>a0I(GcQFfWA}olpme8OIs~oueenggZr|mpd0G3%DDg-`= z_u4mgs_JokOjAS2HPD~Bcxj?W2raS|*N8EHertDpBp*TX@m`QRR7d#%s7r^u*rn-l ztL=EAakBGG9y}9t$BnW}CA2;xqnv*o;_#n9~BO8l$Pw+=qT_{OG{Bn7M%i|dtD7j4f$x@{{jXhDNzOX9V!v)?>%UMP$^ z2p!vuuZ_8alwpMmC%tIdv(q-yHg%tNy<;u|jSUXoao?r^U~0fOX}=Ut`n>D9`#HTs z?!N+DzAt;8Vd$$HK0_uBWi;$_QM2-%#CPGfoJU;EB+43r)D#9dWu(IJUQn_KtkpUo z-$Uy5Y+6i8d$NqfCz1y`0s~%Kp^RMjC#NmLqZ<9hNltSME;S+DgW$TD(rD)fI>?JL zn-fbnL!Dy85`&v-gM6e>mvRY}?G#FDivL|}rYV#^q~Gke-&I_w94S9zqbavTl2E9~ z9a|~4kZ>Ixgs0LrrTOELm2mFkTtaeb*huasJARxuCXH<8&`wWO#_V!6smVd63goYexfoCdV zVFsKIB=GVy&NgMk!wQ47Cw2JMB)GK!T4%s((kF$>EPI7}sBGUF?pq17*RD7HVq!U; ziscNHg0_Ckq~$8~APq8ZIS3hL#BLEy{X#nf9IW|&0Xaa%zo?>UW`fMz0m{drb*qkS zZvV*j_G?JE(N--Q@i!jxhz#z}yJ}}M*W}-_{gUQfzKKfPsq)>C5%bjPlj3L1ig-kC z3bSvDmx9@0;ih`AD38;UVg?=#;ve7WQ1VmAT9-md0xI7cl3LqcEyK%Uc8)3NOxg?c z_}HL(mvOqhw(Ckvui>ILHtMW&7DEnJje6uGI;uU0__D)`z}&Uq6TN{Ld3z2}SY6S2 zf-Xhvde4OU3$@0R)a_PxO(L;V5^G_zwe3#Z&~)^-hfjsuBd&yVkNlFkDyXp+4NSld zQ_t~NZOy~#Bi3JZuuPRa1)fwepsC=b>6eUIU5IMh{{6jn{k>bGp}81h!#LijL5)QT zd+zFIllrSKPUiAnVzP|4NpK~7?SZzBfB`~P9g z1WX>4xD|ln>*HLKlh!A$ypSng9e!6pxx@j@?+$BlK z%lO`2E`+tL?QbLhkshdou--G@p^t1iD(SV7x2Yiifky}2MK?_gevk~}QR9RH3Tco0 z@G}lM;Eb2uJ{d6Bf!%z0S3{>5Lq8~v_NjM)0Y{)K&xP@5q{(+R-Q=>6E=}*wF8Oi6 zvhp+CYw6385*~P(#a@i*l+Sh^`s{&4ChF=Q`3nl8Ke`?LDKv-{NB4SvCbViCsUHkR zJ={t2@+pEuz>*a=>iu%?Ko-pBo_a%`mnrVWr(Pk;q6ch!F3z(Rf|!xsln_RYSX&*V z+L}ytLvxRLYuMX9(q{@o@Z6Ps^5YRLc)uR8N6I&``s)Bx=tu)c| z((6WS%k=TnP5ay%4+-Od2Amb`*%){m?@`m2F4cjcNqkl|2Hc-Q@)kWv1yZZy51<25(O_Ms$_Spq11l z6~0qXzbbbkeER;2&P;EltP~E3KT7o9G%218PSq1LpKe3G={I&-ZyLxsaUvGOR_+P& zkK)XgaZpKf~xcvsKt#Z?9ter954$#)J&a-u}4b#Bp%D~(gaf*8} z9xbpi^D-%eN4d%uAnQnd!8C>jb018G0C?CHR)xU?7CZN!=Gg>*nog%Z>8@H~^mmt> z@$)!XKU5yjmzRHSwuanP1Lhx=^b)Fppao?l$}~`FdayhIU26SBLs& zM3Wny`t+tT&nS}AoZ@9x^mWB)QE z`Ef}ll-YMu%)t-;&MK(s=IjV%2dN zPzLlC`T8*}Vlq`2FkBxBsg0E+C}4@|rOslnlTP@B1u4iBW|{v~odVc|RKaiP8BD_Pzdk-<7_E!x;ksCm zxQeXJivw;616IJZ=0CVxIV$%b4$rj>J5+4F8^@?&f6$7r+YWjjPBHpUOck)IRL`_n z_$1MlAF@HridSIrgxL*rIGnyfK!XSx@;^gDuvZ*5Cm4PLFqBz0Fas(=F10oxpO41k zl7&v~B5JYiH|MDw}#Dix<>SWx}chz$v;94gJ)CF`me_;iPu9W<@55nzAL9cu{J0gn@Oa*d$NrU!O9pI1db4eq$8#QMd^v317&5fLm=QSL8l_7DVSSg4Z(C< zf7iL$%8-o5APma?v$O<92YF7a6*f@ zd&FFwIujL0e6`e**bc{T#1X2NQ9vjNK3t`_0#PW&Lq1bamn%ybjPViM0!dt!X#-u* zkWD=lvuO0>9vs|T#@1$hgEy%*=Dp@Z&WHs4 z34o6l2MsriF$AvrDl9zwVwg-uI|=8qfO;A0m~6&E@YXj>hRc_Ww+&(FA^7G^5*DGT z?CMs)6bPX0B&!WwvMsq(ps^Z()K^Fj^x1Qu;pCu;K1$npI^dEGC~WjW{X%{0yXsFK zoN%9x!RMEowJM(ZnIDk4xI-U(IBqEHm@>nk!od#>#BJe8g)+oQdaLy0ffZdN8haFq z%tk=-MJ!Yz3kAYTTC-!`c9(1L5`Q~D-WLBZd>^Fm5TfhxgA%@KP$(3&zV>kFUOmBG zh{vufkgEY5)V!r)Fv=JsEgX#w@)xMS8C}qxn5B)^z|u#14xK2pDyiQz+Th5rgI@YEQsDlax-hlzhq) zoc++DFJYx1pNPR;9`qJ>cRDQ>6y>toWNto<(zb5)LOdl0|F~Vv$JJ3`4`HErgN`uw z84MB`*(-eofJEgH=D?e@)`ALj!+J8(nSO4QC}(XIi9UJo;36-J`Ul;^tD*TpfX3`j98l=|q z|85-ow3PpmfHUFeCbP$>!e;{Fh+Vo?{s*$Or$Vl^OGZlQRqO0!dU9WqE8H0`J@U_arMa6q%C~Z1-Y#6cHOy)Et6l+9oBOMnC?2!83E~aj zF&{PS_y9tvauV`hI0bs59byD#jXwGZOv;Kd)EOnXp|Gf=m%YNmo3;pZ3zhY2?(ues z#IWdvq3!FFA9*zBj)ehd+T)J|Zn5ddUPp08dkTiMhAiC869f*8GF(Xh&=jnqr4Y05 z%04;ekkN<7Z)_paJIMXOy-At&ZhP{w#;9z{O9s9I7QzZJG=^{>L!@@??gQbG)yrgU za;Aq&(Y#O~SZ#H>gXwsP$%IC`)GIYgS0@uASj??@)+rPmevw)=YIb?E5++CxTz%EP z5;pFa4yH_=Kg^d33-PqLIihJ1K>y*IUcq}4Myhcn*~wtXAc409@+05tkyG5K=S-$) zz8Sn^a-X9t2^p*8!3&DG1edUEbkwZsT{7S(>9GulT+zTF%_rD#&wU#nty2uVeP=@@ zJ$Y8~gHo)?)t|n5+KJpWGa2W)flpXC_S=sqgkj0_$MNZ)I#VUfe>pElV-I*58_?#z*-Lhz5XJ(z?gqsCuoX(N_cL&(fnCGB6# z;kL*TPn~J_3S6%dB;|i6#f*XexABwX5)RLmii{8V2`B1a46 z^9Pg+KYj%)yv(92hLQb!#5Us-_TMv^D4>>)G6A3O-Y;b|Xy2Gy^LxPB`Fd7M%3@4b z6)0@~`<9`ek&^zkjMw{69c+*qUMPQJevsvF$BnfcI!Bn@S7A-s3j+>#@BdFNo;!X> z`zp1gT+;tRL*yZK{5@2| z&wHfChIf4QX*V)drU7p4{|S*p7YCMum-T6A4f9&TtrZKN(U`wOS8r}%9q1B%`&-i0 zkJ(jBao4uqQ;(TajJJeWnUIE&iJ}Igw=}K0L1Ns*;}Vh9Gv{$Pk*f`2`kTVw6GQ5= zgMW^mEE&G`aSb&T7ew@7C+1N9aF#ke1|WwW765uT(cAf19>7m*Ang|Icu6d(;!a8U zk%^=^>SB5E>V+Dc&~s*TH{liP(-7S1a3-B9hlw8>@fxmY#jtyL$v7g7+iLV4lUVJ2 zb2}-H9zzp&bzX|cFQ6yFx)P8XEJY-iAam!kIef2eK}o~9Cn+}G@UK*{Is3>EY2;6Y z)bzNYjwg5+H5*a(xc?l8LG!U7b6KYR$=fP_ezJ{5ApWqm(ic!F7fGiKx!>@IcPN#n zo!w9rOSrlh%=O3|KdA6Wwy+#q%{*_%ws$PC4{miF`{Qcm7usO3L3#iI#A9_!w>xHJ z*ll<_B=W|}Mj0->r2!jKq*Z%8_kkLOd1MxfFiIhJfHZH$H?_X6#sWFnLo_`oMHSLI ziAy-FY&@~Ci8kr({Lkbc0d#c5N8cxfY6Ef8FV~MN0B~##!1#Wc8x24-h$a|l{@@RL zN{2!0j1W6WAZ4f=m=o_6z*~h%BsG*Oi73L!rox?93VhBMOns|V`VX1Md@y@AzU_40 z8!Rwd?#_7j2jBrYH;8fc+Z#@g(|?HEsRdIiuK@nK#`d@gCH7pB#Inl7 zWJXn*QA1f-d_rXzI-Sd`fQv!pv&d*1nb&K>Bl?^;n^;!CRbGxZ0t~((3c~yWD2vP{ zWAOPLG^&uD_h#2aH$<%zAZxfyp6W$L2(t`nHk#|}s37#!CEqXX^~)9E(hU0g!t($7 zf2V$|IR>GT*KRz10NGr)nHN#Yp64!$G&~u=KviBlhMGf(Y2lG~lhZhbIYfr6S$G~R z?ym>dE{Y_XJqa(|wu&?GtL*Y$9^*L-SRc!jwO$Be_{kegDvo@#;=OR=yAaY>+eBl3 z`C#bsSI8N~nR^~1a!z=EdA;%PHcy6m#Y+}X+lE?B``h0e2HDTw!>#kn>{BiXZ^m4nv$Hax~toV7z>akC08|d_;S?H@P zORVXTvDvE^d|>rHE4VK`wh4nyd^(((3qeQiXZ<`% z9JORw_WK50*%>&xH#9JM@8+jVCi5SvZwI$|B^!s{b8T$yLYFyv^CF$~BGKkpjXt-& zAeY8Wib#*%;2QmJ166qu_P>~J1SJl2zvQ3W+!vgjhUxrglzVsH(q=l_VLWgTIsGL4 zMMO+T8=jEUekYZj<#)>XvKn4Wob+7ddcUTxa=|HF5z){QcH!Upvzb2|olkUKff3B` z(gmNh9~U|7Pm8?4jH)5>F9PvLE+#P{FH6*Qr?e?0q_iPzP2O$N(XQ|(ahGpQSTVKB zQM~Wda{YcDi%NU`Lr*fc(!9yiMwN1O3wH2E^rsyq(1(OMO??#+*~22^v)B(s9Jvz% z*ZVb;qPjJdBG)9Ym7LL=)I+3B=q%xFVBz0yaVMHpaTE$CM!B+2AA{+&h1mJZEwEo+k{rUUy{Y+Q&D z4(`tL%&iN;C&Q16y~zt~T}L#DB^mac-MBc|6t?kz|9iHN|H|}(=97)8K@Z2(J>Nav zXe{HG;TKzNjrx82r^|Y6sm|j7L5xNJ{X`khr`Z1Vw`iTRfzLiy??gKrqpm3rFe{Yj znFEc|+utj!7XSpm^7%_2+dX*1C8*5P*dEvacm3U^3V~}W`5BQLQqn%0rhK8T=}Sm@2QZ^-{PYwyJ7Ay=+L>0vgjR4Rbr^JuekVRsTQC%V+L z0gO+f4eq+=VOM%gg!UdwjmR>B24wW9NtAH=vqSIX=h<3{%A7$V0JwKk2fV?l!-EuDS>zP6bCu81invb>gS>c-9xvaz_%>ZG@XA zvG0#&SL8)E7iANHBOJ2vo8iVJsYYyLh43LbZY+Y6R8Amg(V|O>ERiDKCy~z6{>PAW zZwauVwxOq-q+U`9i5~S3AHqeKIo}5`x(7hevo!Z`%q(*acmvqag_X8tA&||D?qynS z9lfNcmiFRW#y=T&!;I1Zvr4IjIeqEnsBF)%8Zp?9V$r-GN9*PLgjBXT5{H=eV*P3o ze%obGwcnxyg2}c$mS%Vq`ebZ+VRg&xB7fi0#ntiYO>fuxQOnB;E?o+21qGL`74=IL z-BHyX^{oU^+INhh=c#u+0M~dADFWc0j_?I1Gnq%YPBW1JK^F;dPe%@L0LnP$s0^c{ zYqRFW!qejqtB+oeBjSj-!*Yz}LWtVB%CY~vzPjY5qVZ;QCBXW**@mT3DmaZUsO39K zOaHwm=Vn>3l3l#oJ;#@s_l9k~QQu;%uip!1)ue(&f4n-yp777%i9QH$0hr`(%Gfv# zYlnAXC(_%@7v`)ho0Ib<+^-p2dcro?F2Wy`9q4!Q`v!glP#6pT_jxL=_3ILMZ9TU~ zM~Y{A{wQ0egFJG}-tXHeS&?zZ8drPkjW`(mA6l}emW5w|)MX&JZ&O&9~mft2LZQcAOf9~J@vTmWLo zRuniSf7(}4OR1KKQ((=gwo9_t)N$W6uj{0e&WXvN@{5UGsFIp(-4710k)r;jvxdNV z%;=jBT?(}k*e|#v87O#|rgikyom#EihaEIr+wceAXo#n>mfsw?@$qM@flqH+^@b!s zR5VzdCrsWt()Uu0s~y%>Fq>9k)r+v~sZOTM1laq1czCG)HV+TO$}hjx8XSh zDiTC7t^fQb(x=@ebtArADhbtf>Bhu=mvjY0Hlqk4!O@N|O#+gEY8SK$+SYdh@A#^2 zJL9>Ngv9&dPux%3qK`6b`$MbGLc)C~2W2%kT?JQ^Z)_$Y?FHFh+yJSY@0rn?mn{+f z4~=S8J8}j!6AjCyOX`hVokM$b*ty+jkk-15#`9`HxAkOhzAC#vXZw5-DmI~eK5Yvg z;Cc~{6#F%*!^OMuw%O|Bou9{Kp6RJ6m(&|JfQNUt=dg2oqn*v0b?WEM{^6yuo~Ikv zMbGD%_yn1eM8y-3qP7XNX{&a9(NB)4y^4{(P+bAciS-KLO`*7#z|jWRp70I#7ZEX6 zhqI;MgC81|hGbV%=0*-P6<>nuc!{1_1imDv!u%0ywlDXK{A&-s0gCOZw<{tI5C?ni zl~QUE4{T`v^Kkd-Y(tN?wz^cPC&$>sw5d z;fn_QO#z>Rkc1`zC9O1sS=8=v`&!G{9zWWU|8mPcWJG4O>m1+E+B?|h1Q>H5ADvYc z#Ln1wyv0+cxMbZBQplypt~z~={A(a%_ubaG;rBaM;in%$B`4h z{#0R4#@{yNnj#vtbEegAse7iNsJaB7>r$LYEf%*#DJsM*_B0xa_kUtdp{iJ^j?-+R zYE2bYNvw=sgk;cEf)++uwwP;iCf8WDHwT*TVQvo&+?;H=H|V+2V3SSOh3TXCrE_72 zeWIJAGjK8RBL2sR;?tAKSt(bYb`Ypl!3BGlvjT{~eJi+qX~N?$7Edx=Th{h-Ev(9G zR%up5M^;djYN{!UNd6(HXWtTY2R2xd5K~=;sEh>2Nq22Uu(P0|u!9m(|8&F%bPpMe z#wK!2wdY?;6lE7l^Qf{ta0On1?tD*CH{L_ohyQozf=Omvya^_D-%XqZgN_WyyDRDU z87rs76XU7n;^KSyB>k*|`S&EjJA8NO1kYi;6k%)v_%*np%*z2B$ zK_y0GnW@WA0B?{rxTAtvfl6cRNF_F;+)ma$PEmav5wySf(~}s z&2bh-vx`$&jBsTt@<_!>9*)CQka8JPkYlQ&qsXlTfTy~aQFeyS=M0molu~YuoN9#> zZH%?`&kr}%7S{R5k=Snb)wE&`4VjpWV2<4-B}3&vuz+LqCyp;KBA9KYCgg${(b&bA zVh0F3` z1Y;?l-T*F0%jx4|Xaa$|@v;SC11? z0b2*_z1x!?Sk3*7g)wzE4Ky&t=dx{@O~*$YTJM}Z`7mfmIOU|4x13IlMl;Um-fg*4 zWSX?L=ZgxzRPzqlq;B@TI3l%4hy2(MQ{GWSq`s_OUAOU;RtbcyzPV?_vUi$0f)_0# ziOIMlcZGM~0>gWrbJf>&<8k5HziX#b!&BrI<53fr-?l;^XkQXmNQ;4J2KQEpWXLEa zCFhO?JoIQ7xV+&e+l_^+FJ`BtzK9F|E+xr58K-o2H`k^+r+Y$r zp($}*Ab5y8B#_j3S-)UF#1b547Z*Qv)!gJs!j$hZeG(h_$YjC=L#Z+bv}$xnSVdpJ z_!PI5dd>QiCMHZQ)@>HCxyus3g;!L8lxugkkZ&g$pBbIH z@8z{8gn{WiJg880{AP0Sk+G(>Dafb*qpYpqP;bg9y_VkZXurDJMT>@0ER>6tc9E0} z=mae4#e=5);?Y7tRZLBW$X`Nn^a9cl+%pL^%x2X{XP1JvCCg{4&k@cRWb*QC?+1dT z|3vtI%V}BZ!y#8W2;P;TfLrhXehiu9sx0YG=?oL2YmJEqspJ0FIZ5nm0VyloXaBYH zv=uI*(+d|STZ^7&q|)d0o-MfGiw~s>$;24(WbtQ~5<`_-e0#Cy;sSBu#w^W!Z0^Qe zvq7;ZtoOwCX2YV4*_Xs+*1$;jBVS&HuHq|JuS{xwm-+!cM@Og_z~I=ea? zR94qPzhVV*$+b;91nZ3)3o$of{GPyDj=Kg-#?N4NHdfbs&ChqRdt^u|zioHb@epvJ zgh3|{b)C``dPb!oHnqvXsnm4C4caa`g39E1-e6k$_COEb@u^^q&>$K_RUuWPySTJN4;Lmi~ybu{e2%Y!Bt)~l*Wn_XVtVO zi@X|hCX|S$31&?|c|>r3=Og1Qag}6&1L1(^-G^-YDvS93iUz(D<)bR{olahiVB`vv z=7skgX5-MAI4o+f{rtUNZ(qE_J5+bCT1}-@l^u?(JR4bwA%~}?MMmf&*{SJ~QMwsH z+SCY{MUCXheNh^(S9~HH9|fLpOgen{I88mu&by-3&PT`qR?;&V0vhPobGTb1g(oBwzT2fULPhxQ$_g7ek|?Ydxs+U-;d!7iTpii`d&*w}RUG!*RBT5abHHNbeAU_B$KoR${$$nEK%d(=;zy{SN= zcA-j;BT^Xq@>4EN$ZOT>jfBW?=qD+*C{R4JfRkcy>^A7>GGtlk`;@nw1IwPDuLHkQ zj^@Y~f1<8>x|K&w`7dXx{H4*0{uE$jp-l-zVNpWf*VHY0$vOXyS?>B4N)CcHgFcf; z+qs#i#_*b%X1|iz7v|Z#+CS!1B$BCNu!!wVt-WLIwmw&pf_wQ??Lj~1!*bn2R{y=w zst`kO!o-g+6D?uIAF}{N)u-~@q?dbxdvl7nqz?mlS)RzgZr?;MjDOO~@BtG@HWjrR zi33tJ@kp0sGh~UiA=&q<>yqHyLE0#-9-VvvQb<5W=W|iXl4a7Q0KD%gIrQ)4BwGWPeXXdfS zb?^DGD$%`fN=I2g8nZyd4p*_f=>cY9zaO{p9ubrn!wjb%OfPbKmi!=PTdlv6JX%lr##nP5|<-~2QZ<5V!{BsM@ zr~H#r41~ikk8g)Q26Bci_xbT8syMY5x|6@UolGLMKn4p+0?ZU4ooe5I%+3iB{5hjU z5y{Md{phXG=jSVQH3Gzde^<#U{&Z-df#^66Y2U*7`xl|0lawr-7zC28S68W6I!Lc1 z8yEZ8nBCfw=KBliPOeM#^aV!Qv$kjfFUFtp=lk zkG};M)vCJzY|leW+AUZwDph?XzVc@){7tChm4L63+`#oOdxzE`3&d;Qjvpjo=YLnk zamD6y8;a1p`?bwmrPAY`>CpL##<@h?FN7pY~3(0rJwe`bDmx8mlCG zw;JJZBV;azew2hAKFcdU1djZZhHD|;uy!Gw(pPk;2HV}~@hvi3U&aDodilr?m!0l7 z50uCkyhVz~<6K|TW%^R~B#@KmI=@{9!)u7mbUl03jT^wp@~@!)3mDxL_5w`?^u4Z z{PMM`^Q?G}c0!o!d`xc;0uU3I0&W~Urb0pUB&@0x{&6@g<)4M7a6yXWNGr1F^ywORhLnT z;Diw5J&KBgS}*ERIw?+muDIt+EzfVDi0!dK$nGlaG=QK1X~b5g`#y)#vLV{kK$@M4 z2>vtQt{mrL&^;Z8Haayaj#7{g33K(iJQ>Dz_IeBjHlOtX?|NaUDU%fIf_Gke`5zP2 zQHY6D_oLIH$v1hl6Q?_~oRO`>@)U@#;j9ZoX3+UdM?i0&0bHT|CRm$$onGAi&qQ$@^YeM3?vGPO)4!QetrMHqyAMw*lhSpI4vRUNnf3O z31mC<@{K_Cd>T2fCevdjB=*cJ_l$kPOzR!;D;JaE1vgrTXsK7+iMrM;Wd+6bQNW$;! z&LVVeWJ)aQ?$>HHa&=*jX%9%49{Sx^!|f~w)>@mSE~zO`^q|y%8Ii{UHXsD^A(8S2 zY-?};@?jHt0zhc<9~Ag7I)<*lXo!=mkNwDoIbA>dYl2rRXr*Uc-(Lb=$Y0D!pUO5> zQ7bS=OxssH#%&UCxz-Ar$?Ic>t36(MsBX5n`-6GF8!f2Nw=T_Dc(+XRQ zMe2%1(=!Uk`pHG@uA`{3=WhtPw~8tXJXqT`jdp#@)Zw$shUe8vGH&Y2iM(z2xdZrL ztY+}!9-W1%J(WPvHWRlBNs82iCFptZ`rdUAnA6+&ahne!qDN0Ywvey@E+m|p|B!Ij z&S68M=jjT_j+MH!Vb7Ov<^%Tho|vhskjFN-Wt_!)Y-;G9Ehd^2R1$p7Yj@&b*Rnfy zTPf}4Z2>}&-U&yQ;8a%1NGCj9GYJKvz(Wcehd>l4VF3MlJk`8Cd#Vdd)!qQ2S!;AcCN zEEH>gXC6dDK>9A7g>s8?HBG%&APnyrkl|`cJMUsIJgwDcvpxmR8z>i_oz_y_ekR@K zliR=*H%zAU*n46Pul_xCoD(EF%|g{B&kdJ9G&PWsLw(XxPaVEG49} zXo=jOm;>5E{R)*n1DWrj7r*FZjTY;^4JINz6e2oBrsRN}$ZM&Upg_G|6w7N~uUnu% zuKX*=YA$on`c{|DnEWNXvV}fZuY2%w-xPp^+hjCvua`63+cVtD2iy3I2qc^!7SBw! zk=ckZ@^FpL3Ci(p@fwApWSUn*cnP)$qCoIibJ>y%V|B2hd^Izpe5vSaNPIl2RPrBV zSeX4-O*ZD$yV?u_tM2AvW+8BdzwvP;mjiP>q6x}FQ4vhl;3K+rXKU`M1eEuz5Gdpj ziAErU#Ha`B zYx^jQd<%4@8J9QH(F$Fp-`PHk6fbzifap$JgAu~ZF}xdu5_t+!i~o6O0+JDZqY4OVMLp`i!6XAhJW*Xmjn zpUEdLDGzSE=<}_*8&L=^Ja$)cVPP3)ROP5dDO#MB`m|`D>nbtT&7Pu4V~jV8av!zC z92uw^yq{;^Gg;o#3*7n|Tm=$B)g-p8oc{%D>9K1$t%$1z+)L z!XEkj>4+rsE7w47Jd)tH44ECdIs2>?x`65-_82T1AQ9SgVxDphmnEy=fI?jBb*{tN zxoQY%%^Ruzl0T{vIW|wi5u5Tvd6{BLDsp8T*m?&qY^YM64L`%ORn;*DfN&V1AgTi3&mQS zmS1hSwCo0mUmYm7{)`L;PBTvUjXZ5WPdd4VSZsW(D+vnGA4{uA=;ztm(9DGT3iZ0b zn?zSex0Sn8%_PtP%|?LM6Jf7wLnftY^EZ_eh>|ElZylF$BG^G+ODqLlBp)69T!}W} zX(&Dqb?b4vb;IY&&iH0@b!w z5z8}emz+a&8M(TePgSZw{GKSsgHNBy3k3kmOy4b`Kgd6^hCfz?`tQBSwL#*^RCcnu zcFPxwuG_7ZAhGi9R@V+C0^dvP2mW_wkGCrYSpBUE&yTK(JYPB5a@gDIb3*?4krjg! z=RIYgRAYm0xhk-&PApHwttukS;Gt91&h{l>*8D3 z_|3V+uU#}3`AGWJ%)lYBLGFh+HIe#^U{2|9CMwNu%$2|T^dv#A)MlRG7AW5qr|PE_ z%K}i27w-snUh+rcgN38+Q55xJ)eYbD`JUxoB~H&$#WQ={nVZ8V>}mECjCG#Vvs^*4 zq%}u-S5uARruY}&7a(zmnw4APit(y(4Y;!~`1BOM*5lHhcTz#6)#_Ioh;}+{F4=W4 z0)Y(^jD4$%Orf_(OW1hleG2e$ubq!+;DHZU7pD2WL%?2GU1eOVx^B%SpGUIXHXl&K zDEy}hzbkM<);SPrb2Yta95%N<+7G9|qO&JstN}}MvuaB!Ou}Z(n_u#mX(m{h7?}HsTXWtN2tBfi=7LaGP zRAOcUfXD*gRlsu*sj@@a*T(LBCj5uDp64jK63eG`n zRy>d}MZ~KB^r@)LXOs4GvHSNi(Lr2Abz@d}b>fJnc3;sQ(# zl6M1{dF8}qBvLLF%yUDA%{hQ+dq_PT7DrbcYJVvbCWwm{pa&mdPl8{Klqan!+^6zkG2HUBn^Ia*R1(Q5 zPmOVdadi`rq#m{aFKDXJztdT$A6LP%v4;d}I5l;|I^Vjn?kPcw@^4kwS?e?+t-qGM zcT)8hc52?~#=}?6iSX=8Z9#Ie*}Y>xN9XKhOCg@>8^!Ra^u5DAe@Ma>ot>^%CWpYn zu}?kA2O_!}%1HrFS*Tijtlv_0L+9TeJB-Ck+`dPa!Z3Hd2Y>@^+v!{~W>9xPPxieK zA3xuV>vdD3ecWvxH-7>;5`Ce)A&Y%+ia7O>{qi&MGcVy!{PszX_2-Xq>-%vBEY4fv z+X`023*rln*!V*)B0klce)Z%sIEz^)vGxUtl0f)KS5)(Vbe}K-L{xz6I!>Iz zZY=tt1Y1-?7C+k{$(+STc~teqba#{$FEp2;!OI&>)5V8e+mi@qfVl zYzY6<#i{a6W0A>CT}G%|^+c z67r^mK4X@4>40lp?5dJ-f5hfaXXK1!8n>0hsY8*eJL4D0wat*H!qa8YTgajTlf zfj&O(XSd|P!VYVWq~ztfhC|~MhlhLv?4qrx^Xu}qqY0e}uFvFGfyqF?FYd6{hXhXl z;`t%_213lqW{*Hd2B!xBF@~0NS$<-(*^aYT9aZWaj{Sb=O-2ot0cmu7YL-5-5hdxz z%pK2V+cLvl%?^nwp*t!?&*`D~w7ugMqkG)Q2T60s z5dDU=T9zB$nsatRow>0(NxFnQ^?9)jnl})gxA|P~-J8Kafwvne%O0T@64*fnkW54r)NIbUXl-);=_@xNJ1T5yZFn8*qorNy+D;3B+Zxr>N)@uNU_ zyux{|>WwjKsTV#iT1PD+VsVj=?N8G}pWq@buJF~(CY|pup+P#nDkAMTXW>)EcE)oW za?4%g;lW9l0gPAL;@KG+`uAZS=JVi^Lt7C(E`df%2P~bR^X!cb0{AbeSJEngY3>1j zNMIK<5<=H5V;#~Dvp{_zy<2blL@1XWJE(+zL|PK;5y#$OLCQ8y!)aRz5}~$^*;5&$ngu8!E<*S>8W{ZYqdGuY2}vPx10Wk z(+X&i+{pUhT8?AhzXK~hBkw*rE#H8{Ebv4MQUyw$fVIO|I*(Wq_rjNZI; zD3WS(G~W}HWPftmQLoJThr>EU9juv!D!hR(4(ovQT%QK>a=C#&zEkTJoo;Fco8910 zy1~Yjtm@YtZ7cASs?)G4ScA^ZrbFi8OVB6~+534{c*W7iHh{^)^)an^G#RE5k~atjOU7W$4kCTMdh}7rF@lr}xQtrjKB>OuG%zx3OP+hugsPJ6u1uc%Y)$T}D(*2;6IUE9^P2$wx+twmACJT218dLv$Jx`IjK*|4o6Qx z`f2@4NOO!5=E5Q=$wH`rhc0?lpjL5yrCo?oqIet;_n1tKJR)z9o+z3YoSW7jQo_8h z#EX;gK+*Kj?3}g)z$N;cQl^YY7DWrjKC!y;*f$hJ+<`nu_9Lwjm6KAu3UO=}$UNnN zq8Onh5M=nU@2+SQ+p`$n%-iaEX_GiQi=IQyd9wVZ*EHHv#45rNF?<*@#`>3qOyDEL z+LyrZ$x3Ndg@o=`Ae9!=WwJuoTu~@3Ha0pmG%7as;SP8pufD7zkz`tdtLRVsoJn~C z*{nRfy#HrUJZCkcehNjCM3ki&>lu|v5Ru5+di+%rbX&ejW8|AB=9?xJfR4r^(qxei zNZC(5d_4unFw~iJUQP{?i8+#pJd%VF5eh!F$5|}v*U#{a6#GWEtvMitORMD~(Ji@f z)Irr7XgkI)SQ2$m(K<5sp*d9lt<`xBy>T#R4whORGm2<^cU4#0)RjyN37wt!{BdoJ&tyW-fhjLaLyooXV6X-=JiNf&yYaCln% z0kV)!0)U^wWj-)vBU|5e4njt>Kmfr<-2BvoY{}+gagCa6`mxa&nM6_wCJ{1Lh6=v_ z;ob|5>x)!U#WBt_X~%7|-RAM@dzCzCM7ql?Uz2ePbRRn)y^zi_5K#afi z#+c*0h{N)GUMccOx{tSPhB=Dpcv`DVk3%yqquA#LiG%iHy4ja*_>idO|0}UiNw$~x z+Up=h|FlM)ecyG$-J+ty@U5FxKIu?@b%ZPA1Tmu0Ww(5g zoag8$BBIjw&#u%oE^Q0i1P( zja4L4MVrjXqWySr?YgY(xMwkr@Bjo5{~QIde}Vwlfvd+ZH0q|GN+!9R9Y1!Q&XFcv zHn?!{dLvrDUw?PFtV}bmOX)QevmXrRU%3{2TsSZ0gE}W2`?<}0$>R9OmYnp}tfwE! z?UkgkY1mJ#02N$s0bkm4Cl6iU^9BT~r7zNg&UR=Nmz|JwR$Rh55}Hb1fDUr|y3)1e z;B%dt#vXd_i=iqFf*n z7k`tzT5Yn7VkK)JT&kftAI*42w_srI<@^MwxzyI+7L*7eegfJ@!qm`N1yB+RCB@%n zuiYCQIn}u!3M?$9!m4Tj=s-o8#RuLs=NuQ#EvYL9*_APBs|CC}CQi-qJMXN>!sd}Y zc54`f4nGSp%0Oe02*R8ppJS&YvZmC8Npo5iPCz3TN(dx6RS-xH;as_RCGci{prsB7 zY#sH#QW@(ZrxmhRX}?k#66@~2fYQe-#PUf*7DS-wrh2=2HpE3e69Q9v&J71jC}sm) z6f=P^#iqV~yC;Oe(W`b5f1M6o4~$j33;Y=PGcd?gTe|KKdc5=h=>OCI|8w0G%Y=WX z62~2VQ#xOFP)iq0{HaYhb!7y6PGJTl_G7WUlb4@HGiV7EL>M*a#)HwS(VI_h4UQ%?(BYI6T;Qdvv1OPoLqBp!gTVv6LHA#99oxpc*LbD9NuyV~Eud3WbZE zkqm2U>P08PnvK8x=<0i@Zft?sy!DDTxc>L0wzh`$N%(P+N*>ojn7P61JH4bts;5BP z;Uld!S;s*hc#* zmLrqamAG?pDh|C^%FHFmSQq`-yhIySr6koprQnw=a%_AAJdYwj1YK_vWM0BwH#<7K zP{&+b?e2a5YwnhIib>+FqZS6!4`fqX$xKYr^T=ekX}xsxe6fJ4qI0YFoq&EI_Er+ zm?39tDOhXf>!zm8r8wfJCM`$zL5Z$8=NI_EsJ_ZJkUJqlvqV^r_)Tj zM3tbpvcX@xT)IEe*E$d4@34>L7a48W=8v-KZ?eZfFl2P&kL~p1A#H`rr&DXSN_p|rG zeGGa2?0NMQ78M14XmZeHr>5-PhMxW(Lln@t&8t$;c%Oi*D%gi#bR{=G?Afgtni3f; zX1Narw#Din(2G_iIUuHL{tZ~Q_2It4W4=Mzd#^&%eCXUpU+teR21MUE|G(V~9u-E( z>;@khK9f1ebG*#ndoLuF0=WG22j9fOsOJ}XWqS*Qs=%7mC$(oUhFL7Un_jDdb)s6H$R})SHD1{ zRLl$+-#@E1>*Hg^fNHb8zGn?wXE0L1W+^N44yGL|CNA}=EKO>*SWE=lj&V%2823Xk z;PH8`Tug)&1D=e}&dh(c#swrlHa+f(>9|6j?QiH^Ze_6SuFAB#YzaMb%9oZe;>5yq|S9^X?SUNFe-QB)W)M|nLT{>sl zS@YN)&mM>oCEpj=>kq{IOBk<)%>C>9^!qz#AGhThT&;Z6J|D7MDIjY#kiM&8-a zfxt&E`t<_DngKqu?e;5xBf^N7fJNF4kA7@v8yRZ@1Na&yo!gO%R+yBTCKkprF)d%nH$UT=-VktaFZXbs4++t}3K||(PcFUAh`PgqKjx^Qw zP$|{yHl^{*wOEDGUSfSC1ttGQM)~bKS(SssS`7s)#6Eke>3vsnI@u@Ca*i^$Io032 z)wMyy#7Zjaqbqb&y!v_2FAWp{UH&~Wy&-f2WMHFwT_E+^cTwz&tC`N+E-j{R+#e^_yH zBK-a+2)8jfR2s1kg{uJhDv9z19*0W8fEX~upQQdKopa>C>zgLKzphDt)<;nv?reMN zbK5cCez8!@W}9i5diV_pBf-wjd{kgl$`1eX?5Qy5{dpOc4pn1(XCnhKbBfAbxkQO!|%Y}?6W1kmofl!X^zQW9Wk- zVY1Oqf4ZdndHEOoyGh*q`J1fAd}3)BMe(~r{!ngRmGJK?pVJzhT-iC=hYD+9$3PXI zpl`N#hTFQDcz}()HrSNWoy1LAWY;{xa^U%mb#RBo|JNlcF_ApASrT6=MWtOSk+^&# zXeVs%=nPPzHTMoHy_?z^x*++o0l1;|ViMNUJ`PMBI?~c$=cJLYYi@+M#K~W(ahmeF z2ER(;Su}?eaSBrLp~6}%JGp(V5#JvgcHh}|Bd3$SX_^1B?7sthofQQe2l0I!yu-s# z(AVlxT~f~9_!a-zozK-P611>qV8CATIWl|12?Sk})D7gH4@Pm0BLCIB_DcEM1}ez* z#Twowcy7wacX3%igvDVAmdDsVZKh4~=6m;J+uF|Xx;mMgIfxRnEWNhU$5%c2{g?ve zOBy+G?_*lt2>_u1O;YT(ylf-2f7c1PbcP3q#5N8fipa7>HPueR@ckR)OOVif?vn7c zesuZou0`9bQ(X~7V<7y#(M4s%J{B=rJe%T50~Dj%{eG2K-oew9!CPO`!!sL(t?K2j z&fsaR?0C{tPKfA+K5{fi^zwT8WGasNw~3?`M$C#pA5BG!DV!bk8_(|$ND4wwXa268 zxKQ`fd;3esv4V(x-~Nce1z+82!P05vY?T8QNCR!TA4mM$#FqYG$63X@MFGE0hKCvM4Y)v8OHpv|miHi>OZc8x8j2HuZV zl)fD8f~nYoAzu##8N%4y`Uv^S>h%;M1V(tW&CRlceo*-=qLhQuhhiie5&RWb5pHyo@02|Yl(Yz25o zvt_frcH#IeSey=_64W*XyvfV9ZY$bzAWr|wk0jAstOB)|8fESg&#U~@uOl56nb6XZ zbo+<|+CEIr{rcSTKvN7KAqH&+Gpt%8h5VL?Lg#{(u)FDDl-^51q|V4m{cN*uhPzfOFFhuD|oc zXH)aP;d?BAOUpt-(rdoX?fx^|GR?qvPW?(kA3$lh*DoCB=6$tq$6yUiv31y;<88(| zK&fS;z*Er$+9dJf25n}-5T;j)3+@!x7I?B(6SgUt^9!gz#da-+d4}4paTZ zbM&3D&ELI~baVH!_0{t2r6!=EixVYgp69A-721n$0b|eM#l9a@Suqt?vjYDE3Sx=^ zK*QLG)i0?3McHt!!m7viZEr$2{BQKtY6Y;w06c&ir<;3SsHszAQDvWX10_4YmKH)9xB3#=T?B3nQh~?F{zS$GJ&MgaP7!3CrZE?`f{r z^;7z<`TjJUHU`_dxWTFuw9tMjy^tZuSH8_{1-YG#y1+P7yz4!3_aq~m(UR(=j`On2 zP4QJZg795d3r%@WsyK35te`yr6+ql7Fxs$5EofMW^lOf^ zY!&1)S0E!-uBjRO`WY{)BBWlBh#LB-&f|XXhcn`){c%EZMSJzAH!$+`6r`Q2xp+SF zY+$OIGm018wS8{q^gH#=jeLNiI0|)+APMxQ(OO$r~ki1=d4O={1&3dyx&6SX1`30 z`vD=h^D^sJF&5RJn;SZo5py*U$S7}!~r%+hkj~)Yv-!tr8CQHx3S!R4%84Y!skkVfucMEPNZ3;Vn(@4ipJ$)-&JW2 zI4>uKi4W+^_sfEBI($PlTCovZIygOJ=V%GIO~QI&e4|{lvg7m6A{y)iD%qeA8BR>V zMx)YG_oFg~Ognk4XN~(I(DairEhmM*IbLg4q2bV&;8Xv;WxiAS#is_3yS5)E9Ol1u z`>Mr{H^d^-0IPY&2Krzh`pYWccP%u30{Qb!l(ohsdb1&ul58zG>xm@D^-DGLgEKLu zeX$p^$h_J!OK*B4Hoc@zqcOMes>er_X7S2%HPBF^hSOS!30$;-P~mk8j;G>rjuOl% z{SOX+<&TJUAMOJ^qFsH2Q4c(IRb7B`O2^oDN9~HY9#__z%F4!vA+dt4T6*yq^&KTh z@#9Gtxw#St8_4(8d-ENZLGiF}v4eYK&>$rxV5#M`Qv&E>RT;J#SZ;I`h|nWxz~5O) zNu3#tU}U+~FWl9eLZpT-C?bFQV*=JDiH*_QbO4=~e?^E#oc9DnkhsL>r~*TG{U^(y!?0)M&j8ob^sSE(Sgyy3S<$hxK(aJ?v<08VVVa}sOQ9# z4d{P@@C3RSV$Y^VL7xu$6rfH5>Jaz%@7gxQj4D|Uy>?KbgoxvLGN|sU&A@hqpX|;k zpxWM&LL@v%ed8-2M+V2!ppIL`KN0fL#7W*420+QZ=#QcURp7*|AS2Q&QORmtx7Cs{XXs-v7MsYyD6^XCg!G(a zBfbF2P25=GAisomVSC|JAj;(CDGK!SPC0T*t+uZ5{L@I@J& zOCj(I@gWaK)WL?O(%+_Ja)L{9%g`qX1G5_u_1yJUAMOy!Zn4NQlH_!bP#KtfdgrN2j=kX4DU7gN0(o&`1*6Kn0VV$V*Y$BGWdCA!sD9q(#61 z$9;-G91gf%z=)#^2Z|qQ)IxIX^B}tNjpPOr42S1lo^X#FwJig{VjIzmH9Jeqpm`9* zOEuQVn<*DX$ArdoUaio@jpT{?ioyK)m;MgictD|IL2f(%vCiY+rrI})J|g-N;P;-w*%({me{HNrzH=MdvK#=Aml>yz1*aJ}mZ0?yxAeZH~Sy-e3I4K8s+- z(+CFKRZ9OfX>=0TxKATTGXXorLKAY*V~!(3Dib;=KtGi2$Zel~>2)_pyxl5q3Iv08 z?k!$)?FjR_6(5N=?@=6ii{2IA1xKL|Mc5$eLrtz4l$h!#kOXhwixJgcjnDn5=$3UT z4{5o8vOgD+bKB8#cKg+sa%w-rkuy{zaB+0frnOEMs+c^)o0Jkxc= za`St{TYaQy3sr84N_bQrN==r1OvR^zQJ?2WLM&lw@)8igl+^a1++ES|OG#gP?~@GP zK%%6lhTSK)nGd2CT z#yctZeCH77ecq-IMEOs{d&s@|!WrSk->%7K(YVrzj(am9-Rop8#+kl~ENVv}>X?9? z=fPD-v{C^U9jzLGaEqr4uu%K-*???mSINb#$|7&6)Dxgr2DglA4u}Q2IWbX=t#(_G z$#{>;2{@Vyh36s7^D14pF#X*BDDPOHHOpHTUO4Yqq%GN77GLxywe(;YW$)4pzl;3% z8E3$LXnlBX7$offPNWq^(h=gZv-t!4v*f|E&A*J-oKNRcm&t-{$T3G2EGWG4{gDLk zXpgL~C6hW+_6;z!N7QVRS^G$qVE)o^C|ro-oD8oQn#YsxJPm(FDC{X7OHO}5CfHLv zk(@qPN3o}P5;?aXpjbfhL~{B(>7_^v`{XnfdR)Zg%&Xys0Lde0V+$W=qbQ!n8Y(16 zQ2|OYG)f`4Pyuo%9>K8Rogk z{|$5zJMNMGNKT6hux5PAZg>u>3>=N?R&)$1PNVfHc5TJiF?+H!j0 zwNc40Q1bCg#;gcXUF9!Tp!;uEm1hB-Iu#iwI6GWqqCxkr==;R6KIHgyigU)c32Ehae zr+1So7`=qbr)7Wpl|_9fJXD}whtYUx$ui9@;$Mq>z&I5z{L43xU`|I9Iv!uOHKzI3 z_ap`+1hB+9P%yo4-A;*~fvqR1=@~Dk)VL2eJgMgj~l;r2EM&lFf{U6X;MM>Of&+~b;ki0@f4S2QcE!8H?~7m zcU?4!+fdt6z&}Q^^8$h!Nfpk9;+;fq)Pv0^||5`Tr1E8iHF|CyC&{F zD9h~#Q!ohU;Sy9rIg~;P6hjek;DXJ))iF)G;g*LvUjJW_A;u$HVC!bHT4^i7X6J6J zvW47mpv-e_5ecrzpoTu%YoaTR_MCjr8sF&m>aQAUvHqnApp5*qKTjPTB+C9Z@wcV_ zG*SH@qTzMSW=sA5{V!Ulc_4l3@9SlkVBPirwEg}%`2P%2T0HxrONVEy`^6`T|937X zSnqh9Nc4Zr;4>L&+O3d@n)6oANd$?0A;g)mQ{7`OKchQ$_FsUvhHIN0&{(|+v;&E7 zI@h=ZHxvgydY@Gkbm4K2n}9r@wd8?pr`D#4v3!Ezvm!r@)2wQ(*xCM%&Uq?!a91{Q z4Y1R+!zh*LFY73zt~7kf^|0k+eTgC`_3O7x(x@zU22VQG6lvvg$|~7k1Ye&QR@H9= z0sO>d!`C843NVs*y7`h7Mz|dUw-*t7DP{^l?~8N`prj-qpX@Nn5#5%y_4wir*+e!F zeTfss*TRMc7)gAzne;D0bQk3Jgv4@>ItFUt#s7WAtL;aoz>zNYbAY>MFyR)y38baZ znvG-lIlwK`j;MxKpiv9dYW3YTz}=kf7s`w^;8 zrYmEzl7Op#+h?=mBPDBkE9)raT`0$9V{5tfMm^Ng?rG}(obja3+B2I?Sv#KHX3e^3 zo|8<(*&5u`Q5ML~3siFhuY|NFlH@m)xDk za1Pnf)^H>Cw0Wi9%+9T=jRyT_*_M!V0lasR2;Hpyf0mcpD~F=Kk~L?_TL;6nHKL2G z+l0lA6x+50w7+b6pz)MnmY7mZGMsbY%A(FkZH3H+S;x;$7t4vAc9uceMr z*F=anvDQ#Q`asZBl?iUA{+u(6D6M_WOR z^|()oD+{Re@8h$=0uHX-u_~_Ya?50TIcAhkJn`EIiai9WgUyHp7s#t z8r)Muy#knty=0%=_t-mcwfoNI;q@-crYX&2UWZ$<581_?3CTpWxOL5D%R8qI^HNAsb*zw6{`wXmmBUN@ObttuIm66B)RYPBv(|$vUk|tvnf*F`)aoA&rnT$eit`S zIg0<{j7knLtx_gDd)$76N-fmiCV4kV(iShX#&TOv77c4l%EzTo=`9@Ta2j*u;dP*(7Ps%WV1#=$UzPJv$-u|+ zI?;+(kGY}Fv@g0irVJQa*;x1V$6T&n>rLZ+0X_H)$69Y^)1M3#Z`wDH0`+Z?ntbh+ zd%DLCTjq5FsOu812IjlZxURA~8CRYC*j2_Lb$ULqUTpk>`qW;N znM@+1XzW~|0qbe$;RHLO%GBPt<|2>0U*5?AR_K88&$}$Y-8HKy<#P2smj)vp^>q$_ zNZt139uKL&2Uq}}FL5S-OmYtLR&wl|ube^6b|Rx-bYv~VsB)}n%&JOXefzu+8O0IveY{@8o{b3Scppv(u7)%c4DM7gsPA+!9&OVqf#&FKhF| z1+XS&LC6cY@wgIf)>s58u;S>*3_35OH|mSobQUzs2deZzmUc?wV*g5PqS^XH5lm*btWcipij9rps zFRYDZr#FwVsSbBl?{XK&FyCxey2{)@1Y!AEmrab zfSiK+5i*{?aV|%{7^btoC*@0ceKrvpJeE*KXrG!>%GR-Uae|%o>{qP45Ff&L5p^1+ zK>Y0|zFt|sy-LJ8IZ9Mr_G;s#ooz-|Bjq)j&G$K+`@Jke`P&4(*D_%&~w)Bxf&SjTF7RNL*Z{03MpW}Wp&o8 zQ};#9xkB8?-WV98sSw!xYjXnB_TuH{f zE?amqqQfo`d@HRLdhY4D-rUl-nb`c9@Z>28730+V$$?Msh}e7vf>S53wTI=F%_~9Y zt~s4VBXdcaxR4}`j5ykEfTyO_p(l4YLdG1|>vP*QEbsejd4r+9@ebPFu4wY}p@Yxj zmOY`P5lhr$lu}8@c^Sn+y&v(?7TV$`mDiylSL89DzKR*srS}$Ofu5-m7+k8<1h3N# zwrE9FBAbB%wtT;oKFDKKm9&e#1vH=(rGIW!DBvZoqWLF!w6r@Yq*>ACPa*`)w8B4u zBv`~rvS_qR7GoOUv(BKw4!a3sq30+3)JO{hrT@lsfzj}nU|ws;q;TID@L(YCOJUMF zsQLh=5qNSk5!I5yb-K1A#Jl7!DJRR9Dn4(8)JefQ-hRHbcAEJT_nTGi^15myO9VH# zA+Y-T!k1~X46l{WTJyQT8Nm3$NZPdYV}BIzCPyTEcOTu*yC!;H-7kKhc8voL-S^m} zjRzX*xX;m^UPokikK)xn%f8EsHCcNRR+Idf(co;BW`)aoOyFs6UQd%Cp|2nCB>Ij` zM_QvlgmVKeq$ns_yty9<;-d30;kjpwB9qn`(9y@O3KSU>sX#MgYhj6y&+AcdYcP>0 z^0R1XnT(-BGqMVno6CazbWt+m$q=jRx+IJ(&YkwN8_hQIAlS9A_mw?_VFxoYfOv}V=&XS{5N#-8#J^@S7Ng?9 zm0BI|+f@jQ(OZN%S3pZ*^cJ0?T$W9rl*8~qe76F@O!)BArLzyg>(8*>vTHpM>-3MM z?|mfO^uqu@=Uwijb1VI4XeY{;=%h*HiZ>+ANCLRYp?2kujYUx{0;u&6%6dy_uSLqW zlgv;;f`nQxguW4a?jbERC3Pdey<~%C$;1k05pqATF_FOzgQ8km5@i31o4{+ z`_W#0PzxI0x+h9^(?o`h^p;sj-GqpuTA&?`G9f_q53I)pS2M8pgxBG7_z?_z*sdl# z;l=;8Ph+irvot$%J+Jy@{jvU5TZGw)t=l!kUW_!MKJZZ6ZEH%55wB5GL2lNTvq1ub zrMi=c1j#LMLOK>E)XnW}&N@d)HEPwHrcVQlWQia$sFFG<1*DwIkL3OcGH5X?XRgwD zL`?Ezc+i&+Uq!6Xft{?vLUhX2V~ZFXK?#s}6~g(|QAK>(7>bV{L=wln{AeFqo;@9! zZsf1-+N-S&aUBaplCl?;r!!Y3qVlQ=>)FZft)(p@L=M<-W`!WN-Id+Y6rIGL_D z%ki}zh=?E+)=*>QG_60}2go7g6QsVpYXN&sE6&4uVI2kyK|up|L(FxO6xV{Y)rgfm z2dd9tVw=r(jj;Odawo#hlBB6OC{G*kOqkK_iFBd;R?{fgwT5w3+^Zf20i}|&Aayk$ zCPD??Sa#Sf)?+mtK+b^HmIro~6kk!cscefP-qhq+k{@!>-Y_U(XXp#4j{LbBmH;f3 z;_9qsyizD$32nx;;zoW-%)A*r)DaKj>zX6hgCb2*E?ghaL#?H2g{uwu5=XOaJhNuf zrD_|7t}E$LBV}rulAf`jHL6WIV9M3>x$`zXKKiRn{eky!50jN(g0>yKEan zIx{>eFN)m)VgQag0N&D1CtJ6!_<59MF#)W>hFK@CPSr4cVVZK^-}kq>$6jU!b>sII z*nqJ0W(?&%Y0%wM+Gc^E!L*%jV{iH;r(p)euG7TRW7zD3XR){~hFmX$)VzUorAd_~=!Yl6u z4gOjc(6lwoZ8Vd0{H)3=fSf5{CU+5&lg~jS42w>f4j!WW_J;v1FqF4#Kkfw|qHRQB zfrW@ACD1eJ~8F&og)Gkhn&jnS7q6H;INa#h*bO$y`1R`(wLkkEIf zm@j{tBLU%jzYTM$&|H>< zzpcZpVP%CdgX#54wwfKN2V-q_z=TOy1I!8rO*IY4WXYZD66v z47Vs13OU?^U0%&#O#yYHCV)X^h}x8*#lAPDm>Y5R`!K2BwU<9ov*tQ~kn1OF1I_F- zqFf_JWaD`qLsdH*2+K^zh!@$vJR%h;@>@mV*P;iSqyAb)_PTTF5=x+1%SAF=+Oec- zH~IaVJ&~*k?0{c$A4CQKz1!Y?#E6PKi$}cJ@*i|vftzrwT$6YsDM0dWkVHvHO7IJ+ zN=y+Q!&)EdaY;YIoneVw;GMM-a$wnJAMzxrZz1aBcv-b+XqB~#5gG%hNoYXxI1+zY zDb-wMz^&Hw48)J2I*)v4BeyJyq&w2coTcQNs$^BzvQ^civIJ`SU99x?ZPr(_;D%s% z7g#9EL9LCoSpQylsSTC6^){;QY}M4Ak;ByX!&r*<)p?)KPFYT2(IFxPU8~vofO1}v zMRX+dO?L|_(y+u)XD_rUb~)~dvPhSPfPCq?RG=4_t;a>P6UikK;W986R2j@7)*u-m zkBfdA(xJ4gX2{r^)L6Tt7Iup%X0?g@XFjdL#=U?aD7X=U>>y_M)kW?x@W%zSBVFz3 zr-rpOVyuCFUcG{sVSz~Z1KU5s7y7=%fV51#UJ(gFbT>hO$89BT(YApx zL<0sAi!|p$y@+T{HYX~~Kvy;`qP3DA;!<12DD)E)4rd}|hj0sUJC&z^dx^5ba=t!s zLA>6qnVU;l(20dQ1l_@7KZA=EzP#*pAbO*i--&RWOcSWdYf*WQ5mp%8dVuRqUb5X5 z?zEJpo$nBJIj0aSe2kTcM3Icg>_Q8qrgk;X&_;1f{@pxM!RabIlms&&4Zs8F6)>$I zPA8b7e=Z?9jXfchv*pzcGv=Kvqz2kjHN!nT;*$};X(^)on@;fe#Z{E zZ^q1Te6w3%l8<`bWdxb9&)R`YyZpHN0Y?ML&(AdoqELePf&L zoTcrmjfCBtoL{NX!m7~1s!$;*FbV1!iJKP)ykND)yIGg-g?T%8!DM$V+;s6`N^vvS zN{Lj_1*6TJIU(=J~bOS8hl? z3@Vg1-fWG1!z`v}KzmuEb}@w3zak?pu-x)r3|W!IF0`PO6f|=zfz_+ShW@IpJ>}@} z#bd6AFznTGeqq~1JJ*h4;bI-fd~MvNE3B|`z`}?S-Se^pmp@LZ6ipSvS@vo_4`|Vg zux)2HEpF%vr|s!BUM^qy!2}JyZ7^=FU~u`ktpe(K=@Ogj?T`_gAA#v9QI<4fp?12B z?6ARh+YXDywWugfPYwAJy@5G|2fFk7NFL`Dh$qDa-BGvr*pKLwn82%o_@8w3{|@x4 z<7rh|00JEbUqAZYLamly5BWmfVW@V0qfpkS@F({0rg)r(gRSB*XV~QI{CUjRCbV3L zFmNwB?scUB;@0?@>MR7V=k=1Cd31exylX20Ujq+cx;yc zVjan!kHa1G)DFnctYyLauDa70nOPk&UA2BAa4jx>6^B9>YCR?{)?qBY!vXPHEGsy& ze*&*^er85WBTtsSYocVrONog7j)_6h5aTtWg1{hYf&u`HwtX9iHk*9rU@a;~j2MOH zJay_w8m?w#;BmhcE%KY#1>%(L)an_ZwrN%+x-bw-T|>Zn8QKI~wGeFr(GVDZtVn>{ z11xF9-ptYJ#p0IPk?^*KKZp|#Tq0*akS5QZv~uy;4l)k4tYxF$CyiXS@!PczN$}xp zMC$J^B{G{aX7Pojzud`ukGX*SgZG`8E-bm^Q6|Um*6?&CBSQ<#)|X!0TR2#xj!?lX z4H}<-;+RCIPKU&i`$k=Ynbq7G|HfPV{Mc8wX^kiwlt%)2Mp&dtm!%j#6UU`OE0w%c z6SrO8LQSrhMLrGTa+|?EA8tT1MN_+66_(t{D?Uo7aO87>)P_ze0ymsBJ1-vJ1DpvrH(X6yFiua+@a@YiI zZ{CGpav_Ka=U`?n(iVbejMbMohY9KkW3wUfI86s{NCY9;)KXPp6@*4y8AFm6n^D9b zwV2NeNE(sMQg)#Z8Ft8IhoDs$LBarBUSbFpAcH8jyxA>O1K%Pj$e3+oE~85ih*x0o zK1}QKUN>xMvsgM=fIU8FOQQ!GD(jX^%MJZ_OU_#9LNM)1A>iXGV&l zgjpfKuyD!<-ZZtxaj8teGJMO`#Fv3p_BKyoLn^kSh9XoR0D<&6&(3#>uyXh~*=nkc zxQ^dREl9$3`AIe26M(ycM&*yuKpZ)+J(%fcXh@nn$G4JX=Q$!=`+|S*#y$S#!3S|yL zK*bb;SdrwV9T`^hH=|w;A%aq9g|zUMLMRKZ=WL(olY#lqO?QuM_tvEC@L95Mcq*W% zEL1JSUb#rZv=#f&uJ}5tMh!TH_!D&bU~cWKDgahNAZQXK#k+ZPUhimLY>46% z55l>dAQ1X9sFR_eAKTyChMDCI3R@dRz zCZ#6GA>!oeSDS-sn8G0Nap?Ifo}S4W7s?QSg|-moifEE0UkFOsiad8j$jz~4s7^Wu zsp&&mz)W)gZQ`@#}+qP}nHlEtHZQJf?`w$a9g}ftVJ=041l8@wp&0ADS82;y zIAHSD94ufosF{$^M~on3oblYG6u&Qgz`iy_WH&O|fO&HLF2pAmIX z%XEOu)HWkAL%)0lxjtn&th^Z%@zTWfV`JJbinrPn2}YR9A~qosm@Xe%7O^~o4JGue zhciJB8hn<(A9;FJ2v4uyF{2rI`5$A+Z>a7Oz7HR>zH?xN>pl$_94e7SC2M5&t@xPs zp9c;{f5lsPf}7ly>qq99as^(aMEr(ErpWp>sf)EmbT?pA7r*IYvW!(bU#4EV1NL}b zi?6G)mORbC4d>#zv@we|Jw*Y#p-ul&#F9_<+*t3fm{x12GWMWD8&C3!{}+dOenD`| zy%!Po5zJNGBkJ-1mq)n|KaYM4LpQg*T_CLF>kJQ*I17@>2d#PI$_(6%NP^1=g)&TK z;0xtf%@a}jmZ;YvNP}^gdPygg0^11rK)L zJi`KE|98XL5s^m~(%R&|*MW8lW`^fYq?_uBWzs4LcUoIAjTJ%QO#rLz2*o7(4$}g(?b^K2BX9DLOI{{v zCK6~@%Q-4XI}pCkZ?8p!h0b0anFo0y(NG!?I#EM3Xla+A&AR76iq=WgGX7>!v=UH{bxxevk$hmT=f8sN`kdvQp!B$pEWHj#*!-giSlp)&xXXt1iQ5 z9FDxe$HL0?frQ^hfvvEuB0Yh#lFGPES@rw zX+f-H&M-lMe0e-=E0a1y+L<%gbhSjb`r)PRfR4?bk^8HS%&eC$bC*(;CV2U|tVZ@Z zIVFvq2Cf;Bf|Jw3N9f#5F<}#ExggnR`+}I7Nze&`5Fuk;mR&iIxnT=&_3)8~;9+|M zjUi5($FTsL#$j!sX*h?6?J!ch57KUBaqmLjB@w7cxg1OQsXrIY2DYn8>+>f} zR)-!}M-31+VpbDJj-a}U510nh$bRV%{tzLGMvnkCY{Q*7q|1nz>RDVU!6SSZ=%{cl*_j2 zi2(Zr|8Rua&S>AcuZB0qevgj_NjXm4V-6um30c&d7E%t(Kg|x^^rIx&0~8Nx-M@q) zF1^00LQrh0h$>q!Prmp|=fK=$;tlVLi02j2&t9;f^&Z9}GlQyLq=9Q&TpeI;3ffEG- zh&}0HvyTrp|g$c{sEXU0yVXA_J70cq{KGoAyRX0VyV_CaPF~6%+}fi z)iOqSvWE$aAdQjKBEj;qXwsL0$l)vnP*^dnNVoJF_uCRxdaSMGJ94wPgX!Q|s!jxjrsZ48>Gt_P?6Cz8g?99{g1Q*WHH7Q%67W7h*WuW3Uj zuNL(2!0@U^Mn>PAFhkSNN^@D8{;aMF<=j$vK$U53RpeikfAebB-8=K7wqDYj;FGvM zK?S9bSZYc;mmR@k-JDlmBKpA@>fRuZtrpSM)3WJOMQzy|8d(oDz0Qna;Q z82rmYn~0K)C&+4>>)eFExfhj1uWG(h5zXX?jdsBJA6xWUUCiIhoy3qQjux0pxo*D+&4vJl4}T-tve1DC7N6)h4H8 zzlO6~dyIVD%;v0@I6{KjC^0B8yWA&QqKs~9tgfMU5hE)m+sa(BY&!dY$HHFaXCP{E znE4fBO$|MhXtDI~^)!VQDBclHa%2}Pd_rsqBFm+o7xRW=LlFLl?Idw@hbZA%T zdF+-rPfol>sB3PKb(|3I2koAKE<{{|Col4~?B%TX2)&Ghdjsk9KGQ}g!xfw;?e3Fj zYrjwDwsp;A6dtpD5~|Sjh8{Kb(^oCx&vSPj5bu9s?2vcg>3s%XWy-n(+r946of8ax z-nq#6;2A8Ud}Y`h`uBl0CF8UmUPP!;gWwg61)c?2gkC4xw>c5&Fx+ZuT}~&Xhsj9& zG0_eHrnf0A)#@ERucRb(;M_Ng!vWxpCdKRqSfv*_=yj57wZOjAD`W{6jS(GfuyZD^ zybH*s`IOVkt;Y$*yCDPsf^8a?kBCV))lf{Bt?b238qfU&4a`7)K`c$LMa=;-+yV6z z{B#?>MVba(&pL@7$MmuBZ#~`q_<7alTKn8a`x~15@Fl{#-oe!FrF{Q$%KQN(Yddd2 zO*N2}J@K`hy^?UM&uPZYP`$gc2L*rzt#7w%zzQB&LDb>!?-4vteX&Ra_^9gMHGh%+ z!NB^>?=P$87gDA>Ty;vZ-+nEDZR6xOG)8Y^)CeBvCzL(nlWe6YcI%&^3C&CNtGHze zJ<@j_7cbU{=`?ai7oammpF+evq}RnAI8y6vw2w5gBj6Q7EAKqMK}m{7e1OUatp*3R z>N@NUyF_C>d}gJGWi`W@ua&OO8WZ(_gk|ewgkm!t`;9p=xBq8!ZJ+5ydCF^v-3k0r zZ$r8MiQmn$7s)tesIu7HLX*JV3I*-GkH*bzcCGBB9F-U5*2@}ur2;vzZ?0RHt^aZy z!~)XU*As-ixFkwf`{ z9ky-rqF@v{Y)I~1^33hzmY6>j>AhVJ$U^b9I*m2=mAzZUtD7S)wXMT1>V#VgdEa9gB6+9x!GH3Nn&J68&ui>FKrJ>;-dewA;XS$xFlHg!)n2e9(j4c&9{X*#r|BTp z126d0=pDZ_dE}c{eJw#)}I$}e_$#kJEaoc7P{O2hn zX0w1aL9qbP`xJD z2sAf4Y?IL$=k-YbG;TjlCd0YWJj0Zgk501EwFEB7J_uvt+NyBmZOU8J$EdZv`W z8OodD%tLZRx~YFtYI%`p zy78*@?Vzrjw#o4$f2*KXhDRDiF4l-e82g6zr!V)I7E#*x`WU?6nE1s&>-Gn8mGcdc z1`>B&;xl+^eV~emejTvr-knu;gq%Y4$dk9-qcgSJaC;pmGuO4L6wHuFYYu#pW-UQz z=8|N~3k1vM*9Tr^$ULllm4)Q7zT#`|H`oPP`Hl_Lx(V7~j6#}p8a_N$+FrSC?NR(= zWc+ZtY29OT?lRKCvt>-O-=~W^ySG!?PdpNc^dt@~SIQyKJ75@~DZBQ_uO6qc-MePQ z+>3UNZlb&Y#V_OuJN4vHN3()2gzrAqptD^bY1^P^u-84Oj`-VL;ovb5YWGC=)y>ET z!*8wD;0E$-EP<60_^@#d~<0!G6@Arzsnpu&vbl-7P&)dJv1PkxJM~@#}x03$GIPw!*O)aKc<&a zIXrzMB}+TTm@VeK7Sb@_2!Jgrj21I#35L-kd`1jh3|cM~nB&hqId!~lo29%=I5b&B zxc;F%_$i!{M^Q-+BNr11-Nd@n`lu0T$^yCRc#9`8Si8c*$>piC$??t0(%QQ1v`a+b zx-mjXfEBlA?`Tg_8;Dltm1tQSz^)OP5<|?Vw4I!3bsv`Gw*Y*26*?x37d}kDfK&$=(OC|t5E!N~+$Nwp+LD3Pkr81j2R?v> zZZNOIV9N#h%~hE8ir&DgZv)i%dQp%Hf2(AV9#K%UAYS}0XDj|zt_+r3Rl;@65Ylqg zx3iQb{Kowx*(D~d4`{;Sah0QIf9xOb7`oi^2;Y9)F>ardw@SxdV9Duk^pO~;DHDd@ zxd#G1tkL~B3tQH}w5h{?_&~u}Z*O^yc#P@!DII1`HBm89pWc9pAXb5f1LWu_V;hsb zUvq2KlfdBSV*xnB+*?ZfL=7Z=a9GTyU`c(egW%E;~-{=E&v zc&I;DqD15_NF&(s?+EY!?WcbDt>Rq}s_l^1GH2$EbY6h~OpO7jR*SknZ-oSgEB9~* zF$VFM74sC{U8%l!Blt9KV;1?5N%4LcTsbjTrCS)JhLhLDNJ%`p8VW-pc5i`f$N}^Q zA`qS(Cw%kmRL_b}{I7Bt?4p6)Cf>&NM)%@A?fO2-Jt1A>j2@=7;Z=Q+Z$iQ&I^2p7 z4nm>0zJT5C&Q0TBh&IE%5_@FSnKZ%rr0Ceup=%`-vCwRTJETI3AqGT`7kzwd-fF)o zqA7UI0*^RaOdmG9BLvf*)MB+kA_TY+gF8r$(^>H&yKzT3Z^W>|nW}|F7&fBdFV3g3 zTQsn3k501l5b0nxuSC$wtNe(w@fV+45QDg1H?0~|%A^AD4GJ?w4ub>VHxZ^Ip}K_p&?} zwPxKC;d-Ny*=v%;d4M*dCC&}_Jy$nS^$sVlm0S*4^qiS%ZHP*j>w0Sa98*+HGdNE| zyDRkuj!Wc}S%iQKS9en26Inf)HR6f zji-Wt?4_TTqTNZfoN;z4dtra=RQZ^rTA{(22d-K@Sx=FH1~!MpBCM4oHWzn&HpA#o zxFD!)?vdfzT$MKOvyzt*t$jmCv%^28Wu2q04Euq=_Cr@{~-$wI-ymr8G7!690nqEztw2fP4#`0tcLB~%KnpA>G7Sx zk3mQ>N7hZeB=4d_9MLf{n5b9dkP+xjy;fVIRxya9BY|Q84Z&J*EPB;?o`mPF+8Co_ zZz%^;xWNO@Zy2sBKyCsl?juX^i71qM9ZX16wk`o@^rXMyV*O$yK?=Xr?vPy@9WYE7 zhMTgD*X?Bh{&%iSYaDD{>fpFAOCt-0qFzHs#A6BXi$q{&}B}Wbj8bNr@asH8_-=!)RyBen0@_oigdN>D9{2tLSZQBt* z!>Xb$o|cWzJ%EhJr7x9WJ8XP4y-)}lzo-nOXKVC^?AlBoyM|*KNS0#c4>bgVXjd!t zj!;=A3qr<1TM|yX*B?o3N9ma@i>`Rq6rH;i^kn7+9TXM zMy#(_HRR7{mg+*{-rP40f}mVqrg74zSvl10!+lZ|jP1 zA#{G@tr9E=qg2QW;*JZ6LWpw%vm5T;E&_`#rFi>o?2eEBy+2-M7S8RP@Kbof%i;Z} zpP_ygDz8=3;wXHwgn1!E=gc@m^y=^E%`X=*9-r8AEAaq@WgHCRM4#(Kefvyeonx`= z!IP>(d&eKx9QKR~yv42jLr8;}x)6Tyo0&5euX(iEnxz$!=@DI&u{Ic8=^%co2~#qQ zeDrZCA}KnOqW#*Y`x>x$-Mnq_y5Zq9hu@mEF?qfG0^!x9(_q)+w*Gm2-x;^!mvV7e zEnDTJVp9$2wrtyNGjm;Y#R78TcjhRWYW~t`SE;0vmn15U0rG!xCTljY!WBe zVB%F&yQh9#u6cDi6mp>@o)k~pB$?u-1$={7Y-(+ z_6#=ON&*V%D3`7|xSZqg#*95D2IU5h)VUTP2!QSi zVTQ6;RXSQKf>pY{CquLxSrEaE@9}x9K>Y_;lZ-LpgRyS*+&^Ep_>+*fE>hoQ{br`i zA;B4ytCQZ(8S1InLGbdL#Y(SwIT-#RxU4ATJJSlQdrPp^v8r0+kt;}yqB$o?W;}v; zc>1KKBMrxTNHlb<*?cpf0TJimdzA(>d*_)CV_x2AS60KlDt4XvstT@}iW!2&n z6=hfbVl=QBwp(WsoivTBUYF)|ST?}1+AqNO(|sJ(lD~_0GU1x5?{eU+q5$$pH2G;dSX}OH~dXE5LS&g({(w1LNnITB$-osBVhaH6Nd{2aPAQKhz z0kTzCSo*UuKaSe*fUx&J}OOa67()t`fXf$a_E3ajy{f z=Tr6Y#L`6US7LRi$uC4PN4jw6cpdsWbGRHPyAZ9CldiPKv^-7I4Mih_3iSY2I_Xf? zhcRoVZzw3JSo%n6M!z|pPD%5Q!F*rVZ*>X&J_t^^sC>lPYE#cZtyEgKF@$nSnd92n zomAOuSa9J9pFs?+8bj$vB{)A57M*Q7{E#Kxf^26?tV&~thp!VT37N3l()c4;t{36@=cmLtU28BM=sM<_c|wXTD&@m)wiDcre#`!WUq z!l?6V3?Nrr${2kMjZ{RPmN~M#AEM>7qTvmM;)hd!n!{{K1My7PuMut5RqIeMnH2Nb zsNj_|z3-Cm?-rHaFFpmpj;3WXIl!?Io_(nS=l%py_=0G%Jtlz$^%h zBuTHoabz&5Y&$48@qjvljnnWpl4m|1?E+N|hm(>uC@Xx6<(P*3FkF#bp%Vd!lL|aR zmrE;!NlzK<@Az-5$J%Dt^}5^6=gpn+Fuv(aPwz+#u_?CeBPTq;kf%bst?0e)maeH~92LP5O}vu5M>fPZlyg9x_xLQyOQkhdrEZ z(SO`}sO-fR1+^H8R94Y=c#b{NY9vn9=-ptuU0T29E_ybr_{=$wiKGoDFa$bZ8a%+0 zteA-vd{P8q?-wFgn%!KM+vaXOe5NXS_?lVukRoznnt9l^b^-&I^dOGbL&pCwQ!b~k5Uxn%KM|HkZ`oH}NCAc z4JV(Qdi5iseFH`p5(Xxkfjo&@mAags?&2pL5)dweY*25F#2yBtS4tCMgFnme(0_cz zk{apFwSNVybXvPX@T6_;Sp4Rmpq;jX=PDaXqTO6>w2d=Q3up@Z+lRtEZ&jbo{UqZm zpg9pPnz)>Ln~D;*mE`C z*-I!)yl2yx!CQ@*w(|X|@A(CZPe^Ybok@-TQS<#m80;YCmk-2UF5X8@lgI%k^a04{ zca?{5gf*vOHe`cyLjfGmOi>tV%}K!UnPb~>btnC~JyXl|&QVT!1;gRtKR@S28;lS2 zu!kB+Yru?(W3b)eDjMzQ(8cZF$$#EFACy#SuC2ewg-FW$Nj#cFZ0BDc;3qFyQ^+k%`d(@ z>`AWK46%{bZNAuCtI!U;s$t9(B6>DUxkA$n?sGuLmzVh$<%{9&jVh&QVb-|X*=?iR zxO_YUo;)^Q*k9`h3)p?(KMusltp*odn230d0rlIApSv6DP#RfSza<5RnuDyAqMw`^ zmueW7?jQ;SAk!YAou3n&P6^=Nmy3WOQy%|GZ5we8ZM}Kf>0>9G6K|`;`DAW~wT#%2 zn*{B{!Mo6WZPm!6RBj2Q`exl3g9tJ3ZRM%<`FPCYf_>@R(Y&lfSO&H)=E`t<@JiH+ z`I-D^eQ(3`!Lj4wMS3+Q>#vC8TU>_kDof$&Qh_0%6|9_5g4vykG;GbL#Cl<4&JW}q z;W)CI=eK-(c9pijh~AF3$9}~b65vuEoiFw9u2H?^^2d+3I;f2@bn zuPHeOE#>3;=0fKg4wU7bFRUnFdEy?&?k6n_@!WYYe`Jb#OS(nD2)PlrdZ|DXkSlF8 zfh1;xEM;=en%zb>y5T|gelU-{5i)&ndCw(?)l4xumrCwZo@r(|JE6!?hfSS+wZbi7 z$|%3l_QRKBi#$^9i%Ff+7`!;w0sSTZ}yQ|B>~VQW;My z+}>>3!s6m)&4ty}6nAv8-oYuVbVPAINZ~q;0Q;fgX_#quaLA>8@6zLa<4w2iFc@cK zDxu{_tqe6IRE?I*`HSLXhoxD%qI|O=x#1AAZH#IqUEX>0;Op_@<=1frqKC@V)PC`c zv;)v{Mc$-t`!POZ1#EJe@W(wICmM-y@CqtX>Y`xPuw_3gSyt&@(J+ueVX#MYGI0xfs<83;OP^RQEmJrJiUEV68c{c=Ht~D_FJ7=MIZxj<{T<8?E^_ToyUnb- z;h400X=CZ-=HiWHd&ZMPxH?XU99P%rWaJz$z);Lg-eA*4YpQFqe947x1a7GED<<;! zkQ%(s?N`ePg%my0c2dbtoy3u8q4fE-)XdyLPjUWU1La+3fzL;)h3-^k23JevA1lW6 zntCm^B$BA^^B(=q+4RrC$91q7kknb4L?!oh1;fewU?m^VE3qbL;Ik*tp}sDh)w}=B zk(Sdx)(Z=?-lbM2R!4d(Y*kvSUBu9QV1BRW77jl-l0m?pNT0N-T!O!&rPCX~;7jv_ zr?eF>zK-96y}~jGNgEM%E5mGm)x4Jkh3=un!>`iP{|KJMo{6iOxIEDZ*Fn{ZV%jlz z1aj-;Y@Pk@HM;{nWBVvb^ zZI!KmADD~{)ixi*NT?`sV+;EMo~`;7jaY=p?VmK!w#bNYM-A$>R)FG3sc=ZdBuKCb z&Tbj8GNl@SLcJp+vRAXl){TsMDxP?0&y;?)yr`x*)G!$c;A`ACXysjUfJM3WPEj}fAT*p zIrsbe-1G`I`TIFwua_@K3Eb2-metLBW~)kw#Z}Al>9pLm&xLObi_GRQK(pWzWq9U) z3vWpNLY`)xydpTvT>STC=a0udHM`FTw8o!&Aj*goH!%uJywjjECPif!5}?B;TnOcO z?;bETm;kIV&9R*Z)Uv=HDRy0oKz+pkYJE~1AB}tE;$LhqfmqGHZ8NiDVH{ICIMFA2(gTkCehLjf_NTlvP6a#mfD7rD za!Jn+J?w9KlD4`=D`{U?$OLOA?UH@f6GUnowCjCh9QN9agIHNH=i{OYhiB9@`-aGS zV*v}4;6xF7Rk8k@+G>R`U+mdpz)T+>(=e@Rjc9EzmD{K^SZmTu=1FNgVbGz7fD)yh znYMFc)s&KY+e?1~6p(sQ&CK}_ZD=Q}`qdSz(f$u2)}0M2CPTU_zIt=9p}tlDaFiYR z-Cn`){vuciO1jz9tvHhQ(md-UIE-?Q-<#>t>T999kIMFeg+RAlZ&&4jB%)|kz(AIA zQ7nsmiGhOx2IllO?E+6;y%$I_xZy!JAP5+oQHEIXxJL__hY=wtt90{OS2{PJd}AP~P|QBtiU` zahmrq4!}+Q{Bx(CJVLoa4 znooST%6d>8xVzi+h_Oq*fLPCQZaY8uZnl?O8D}}pR2(rLgH!qy-EN!fZ|tgt}B zRM_6f9>aok3;)Eh)p=z;0$438TEL7NC(8Abs;kK9x|hieOG)orF?#KMIb5S+i!DxH z_o`*?YRe!B*3RE@h*6Kd4EUs8ibrMyK zOCUXV%MmeTAem$3ay=kWS%R%3b7zDE*;gyA8G_+9^-~h|M~Zik8a8x;cFp z2667_jT(kOf9qrTr7ug#;}sS7%zoRrnfglY_ZQ@x#Azg~)q(3q{~ca$W@C4M>nLyh z60u!{eIJbW8=b|=ctydCkROW?jTt+EHYx03B<0ZO`oK;Q0%y}p2BLTV2}Fm;HUwCZdfPm z4^8_;Ni-^Y`Iztblc)=8>vAZ>HFfiEmAfNZk%Bbp<^W*<&d$s@hp(i5GSPJZkv$hv zMH5AUHyuO7#S@>l4XLBbH4M5vn1S|v7}~oLyDN0!booiu2)q-c)H&A#;wyId^&XP7 z--qk;K~e=|Rb`8!d(^rxJ`jyFoiOli)o%dy_(O^2$J&J~oD0D=9mq{pRqkzhg(PLk zZ3L2eL z{PeD*mtO+jvU94uv`SwASy&g0%{Eccx7JMi?^&svGVgwScTa3J6jTqNXo#-%h#hJ& z)Rt@@sF?1X{~i||3ttgfBSY^BM6jeXOY2eD(`zUgwjT-Lzjli8I_*DFq$=${sXooF z_kt{WXm-AJDl3B$tLPX?Sw+|W#U z=39BKSrbXS?xXLpSyPF7wqwgIm#?LJz^!-hT8ftnFqXTJHqF0S?;^zmj{f%?lp7NK zYh!5O-V%L+BWlj8htA{xM5&Ay!|9#Vt=C8yH|&+W5&CQ{zml$Dv#=d zChfOD94EtDqtqr$;OMebWut;ZlL(uwZ8IOruP(H-GM-g+BS)FnJx$-Mo^^+XI8cAi zRnAfEQDWj1_6iGu2Fq)(Cl}W{9WC53>iniRC-hs>`7D~R_~BKJrv?PSYdah7I3;>j zJf*VhqbTbNS_ei9;?Oa*Tb{q2^zS~y2M!dYN~TjVI}WASX{(T)6m*rF_GZ?;MB#&*lkiK$LuST?d=jLaa-+d-eQjrQPnMk=TqV&09 zyPVB=65qp}03>xCHH_QvZ!gks+X$|N!t{s@1EOc91sh(_YCnp}9n(TOXm`7dYmAf` z4afn|TCcd}Ba6 zu2n~w`7~GdJa9$-dK}~9fcMNjIVRCd<;$nDuqUD~~xd45K8 zvT&?hsah)oU)qtE>Gu$dk*5f|8MOCw!kn8H?cmq_p7XzqU|&t+`HyPrOlQD9S}vO} zUuFq5AuXQ^WJP&RAx~08ZeQ>=@97s7UVlvE4!6h54l90M0v<@qXMHwLxK!^)q|$eC z6!_HtX7xv>7<^YV%Khda7UPKVX~sO7^^Q;s??j~AA2|gS>f`iPB*FC24=FwD#6OZ3 zL$cVUnMEhQga>AFc9jr}s#1*!NqC|3nNXk^JRVI&Pmb za*%V8z*_T8B+KH{!t_M-S@pgec2E5^lzIai8o>G4a)LF;Ruv zf+ElSC+@A(7G4Y8tlqIi**mH~Ys6S~_E0>&{M$rVJ`~e#*01{a$KrScWilM&PzFk} z%bDV2&ros`>qlQj+oN|K`idXCw9;hl-@l1?fm!4Pd;|2jr0YA96=Nmt5Uz*@kxNa{ zQj6Q{@rx|gy1M#`iW3fY(1})@p+3>&GPkmMM$gHD6nE33+G9!^hT@5C^^u(1Pu>TYhUTEkv)hXfRe z9a87&p8C7US=}aTW?F7iHXSU8n<*X)hpcCvh)0?1b#^22y}Tbz}ww> z^wnk%o1EEtcNnrL?nAVCsY|HnSRges;n<7Sp=cxgjfiHIO)VGc@e-=%{fXoHNp|I>}H)qxcd_W#n{Q`#NWY6a;mjW~P1EKXeYt7n_v2h;* zMAe=|>j<9es-L+O&%<#vw}(dNBQA}gc^CF`oV{(-3wHpwKK8J?dG1^?RvQKC5kPYEHjzPVp3waEl=Jg!GDWJJ2LT!f9g^pz=r>N z1PYhRTR1}`4c96k+k1Ei$djgun>dCjmeh(4Y3A(c8&A}QI8I5Gc*dy}QENiMO#Vz7 zG_E0)*|Cfr--Flx|LRuF%C^c{Twai3!enasvXW?}v3E<>Mleg>Tsfneekoq#4@;n) zGnL@dy~dV|e@^iXUoiB_{BCJA9i=Br8L5=ZuJ6^Xy#3ZAE%GwXL|-`vj6ZtSM?y%c zD@$=oO}Oatzss0p7ij1wDs4Av2}Ue*S8VvypQWVg6{~uY<=LbsMPo1Dff0(>B{H7B zcVtfZG(D-PRoQG-9|bzTXEnzjk3Bp2a_e5jdcV~jBmKMMUdShB`cF>w@4&*p5aY7- z{_#RzU6z>OetJ<=Ri<41Fs(AM)^sEV1XxglyPN@|KO=>k)a{H(C6mCSa% z^q!BI@%s}J4G4#$1VN!rw8a~XEISV>xA|!ukWN1oR$r7}e+az23O*9M^cK?rU-tBb z)_aBje2Y!}XSkF@gY)i8-R<3-e(uhCN@W?MHXoc&)Zu) z*Kc0l_uV-$jvps4H@R-gXX|bW&>j+OvQnZ7wOMhw?X|rH4HX?FEj2wMI8l>Qa)PF( z2lt_cQ8;@21xwJ-=cA-BOH7W@Qu69cw-vj-i5} z4pU$ZaQz)Cl<@i1aTOf`%{084*u31wE#)1 zVIdhHrcdEiRXOZr$sw|-(wJ?&Abj=szQ|cmx$NkXbDyQ%PA)_tYsAO@FXmJ}4^B!b z?pPn9bRCgOP{+8Xa+JN~gtD@+;qtq^;e`HPZ`k5GUt-4vAJjTyAZnNG=B|tX%C+4Z zeNxDF`yWAO#)g{6dhLDoT6YzGzh$5K)7>+>cs`$avNH07ZD?7-v=*GoEU?d$5O-Mu zA3^jZ^_AB zP*$QkQ1Pd0D6?C9HL-SMk2S1P(N2`PCDXBLz(2p}PxRk5>76&&+S3fTzJ4KVfO1V+ z=DizMS&7;mT}(>OyS*skIer-ZTO#L@$diQKxI)To@C{=IaWdwV8v|FpaqAG8t$ssj{3MG* zsN2#W7+xB(Rg!_&P?h47^n9B7@h-=6q>HVmg}+6#A{m9CKNP|dlDaH`dAX#uVa8B@ z--I72S1U$b7ISaZX>$ZXKe@<=ao5CWuZvn3z|9k>I_O?NpSMU*Nu@w&%QaCIzE*UK z*d!Rb8UeU>)XtH;D0Q(8K2nP%ZbF^5Z)Z5Qh)HVtC&rP0bWt`biV%o=ut9szlaAY> zaMHIb`NrzzZYduqXOGGHedEB1r6~>tscdiocp$>*@*^S28|=1RAmI!J(XbWG4qL8p zNCtvf7>X8$ZP#Fg1Hnvm#S0@2o0R;)&?egAB{7Gs%>Mtmk|c3PZBBuZ$&ww2Qdna* z>45$ZxeAohFC!VlaO?KayOEM

G zg`3XUx)=!NHTT=RCH|;=B*NaJPn2=9>Jk2m_^p_sU7%dS(qa`yj}$qCQXyR{U%8aI z5i^PbC1Mz%Or}PmVkvVS`+tkI|F^jE`Pp4ewre*fbt4(wveJrZfW^JxE)8>3d2D9)+X2ZV! z)fV|URf$p`DjW1QDn$;(cMJX@*djiOj+|Whu*+bugD^LocKVBet_F z5M=h!zDhb72#Dje6QxACfn@{%@`J#nVleStGv>w~3G%JmjM%6#EyCAbtv2jYp{=2d z{BF>I1N46VdS2gtFf78=5&n=oMu1p8s09Z-HV_#?nKqWoi4kOsWGmxz5OpfWPtn#g z;qvDf6qtlZ66otA$~T4_(%(ahm!D&>-?8`p_17`|^AVeYnNzNXIo}tGpu*R4- zwm;;C(^3TOjz21_i*(_2Hr)B$f4KdNf7E2QkU0pwM!VH#B;QvHuj(rG2JZI(Q?8D#!-jf= zw7Y0_639WgQ1NHIa5hA*P>E@Evvsu(`e$aLFS-+7lpcZ1A`q=W=yCH?cy>zU{gC@V zBSl<;;Nd-Yru+<6q%?0-DETn`?`|3I8^EE@5W?9%% zdf{hq*--efpp8Ybmf9LJ^X>M!0DTSIvNQ|VPF^$%&J$j_n*PapU5QD z0HnJ<*isl?XSuJro^?{bNRwfMV{oE-1D`JS3$=4T@+|E&#EOcrN0DUj{de+4pfgt zKnMlXGMaG8+U^XysADrTTeCI3284@$F$0Q5eH+?sp;AmMXlqNi6#MtCY=8|TbdBY% zPZmIeIy{znhiM5Xq%8+zbt>wn!@cV^kG=owsqUaT1_<=`5$PYnBl-Ia5E<)WE3G$ln{xP^ z4581)JHbSbI%)09Bn@w)m4Sf!puCu*R>V2cR8J?J5~PK wcAT|)0H4jcT!eS~`vF(AHxE?D>}Kv(1&r@8BjeM-S0-a0vm)9B^6%OBf2!FYiU0rr diff --git a/web/theme/scroll.svg b/web/theme/scroll.svg deleted file mode 100644 index dc7e5b5..0000000 --- a/web/theme/scroll.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/theme/vbemu.css b/web/theme/vbemu.css deleted file mode 100644 index 69410ea..0000000 --- a/web/theme/vbemu.css +++ /dev/null @@ -1,194 +0,0 @@ -/******************************** CPU Window *********************************/ - -.tk.window.cpu .client { - padding: 1px; -} - -.tk.window.cpu .scr-dasm { - border-right-width: 0; - box-shadow : 1px 0 0 var(--tk-control-shadow); -} - -.tk.window.cpu .scr-system { - border-width: 1px 1px 0 0; - box-shadow : -0.5px 0.5px 0 0.5px var(--tk-control-shadow); -} - -.tk.window.cpu .scr-program { - border-width: 0 1px 1px 0; - box-shadow : -0.5px -0.5px 0 0.5px var(--tk-control-shadow); -} - -.tk.window.cpu .disassembler { - background : var(--tk-window); - color : var(--tk-window-text); -} - -.tk.window.cpu .disassembler div { - cursor : default; - line-height: calc(1em + 2px); - isolation : isolate; - user-select: none; -} - -.tk.window.cpu .disassembler .address { - margin-left: 2px; -} - -.tk.window.cpu .disassembler .byte { - margin-left: 0.5em; - text-align : center; -} - -.tk.window.cpu .disassembler .operands { - margin-right: 2px; -} - -.tk.window.cpu .disassembler .byte.b0, -.tk.window.cpu .disassembler .mnemonic, -.tk.window.cpu .disassembler .operands { - margin-left: 1em; -} - -.tk.window.cpu .disassembler .pc { - background: var(--tk-selected-blur); - left : 1px; - right : 1px; -} -.tk.window.cpu .disassembler:focus-within .pc { - background: var(--tk-selected); -} - -.tk.window.cpu .disassembler .is-pc { - color: var(--tk-selected-blur-text); -} -.tk.window.cpu .disassembler:focus-within .is-pc { - color: var(--tk-selected-text); -} - -.tk.window.cpu .registers { - background: var(--tk-window); - color : var(--tk-window-text); -} - -.tk.window.cpu .registers > * { - padding: 0 1px; -} -.tk.window.cpu .registers > *:first-child { - padding-top: 1px; -} -.tk.window.cpu .registers > *:last-child { - padding-bottom: 1px; -} - -.tk.window.cpu .registers .expand .box { - border : none; - border-radius: 2px; - margin : 0 1px 0 0; -} -.tk.window.cpu .registers .expand .box:before { - background: transparent; - content : ""; - display : block; - height : 11px; - width : 11px; -} - -.tk.window.cpu .registers .expand[role="checkbox"] .box:before { - background : currentcolor; - mask : /**/url("./expand.svg") center no-repeat; - -webkit-mask: /**/url("./expand.svg") center no-repeat; -} -.tk.window.cpu .registers .expand[aria-checked="true"] .box:before { - background : currentcolor; - mask : /**/url("./collapse.svg") center no-repeat; - -webkit-mask: /**/url("./collapse.svg") center no-repeat; -} -.tk.window.cpu .registers .expand:focus .box { - background: var(--tk-control-active); -} -.tk.window.cpu .registers .main { - column-gap: 0.5em; -} - -.tk.window.cpu .registers .expansion { - gap : 1px 1em; - padding: 2px 2px 2px 1.4em; -} - -.tk.window.cpu .registers .main { - column-gap: 0.5em; -} - -.tk.window.cpu .registers .text-box { - background: transparent; - border : none; - padding : 0 1px; -} - -.tk.window.cpu .registers .text-box:focus { - outline: 1px solid var(--tk-selected); -} - -.tk.window.cpu .registers .text-dec { - column-gap: 2px; -} - -.tk.window.cpu .registers .text-dec .label { - text-align: center; - min-width : 13px; -} - -.tk.window.cpu .registers *[aria-disabled="true"]:is(.label, .text-box) { - color: var(--tk-control-shadow); -} - - - -/******************************* Memory Window *******************************/ - -.tk.window.memory .client { - gap : 1px; - padding: 1px; -} - -.tk.window.memory .hex-editor { - align-items: center; - background : var(--tk-window); - color : var(--tk-window-text); -} - -.tk.window.memory .hex-editor div { - cursor : default; - line-height: calc(1em + 2px); - user-select: none; -} - -.tk.window.memory .hex-editor .addr { - margin-left: 2px; -} - -.tk.window.memory .hex-editor .byte { - margin-left: 0.5em; - text-align : center; -} - -.tk.window.memory .hex-editor .b0, -.tk.window.memory .hex-editor .b8 { - margin-left: 1em; -} - -.tk.window.memory .hex-editor .b15 { - margin-right: 2px; -} - -.tk.window.memory .hex-editor .edit { - background: var(--tk-selected-blur); - color : var(--tk-selected-blur-text); - outline : 1px solid var(--tk-selected-blur); -} -.tk.window.memory .hex-editor:focus-within .edit { - background: var(--tk-selected); - color : var(--tk-selected-text); - outline : 1px solid var(--tk-selected); -} diff --git a/web/theme/virtual.css b/web/theme/virtual.css deleted file mode 100644 index a697a8f..0000000 --- a/web/theme/virtual.css +++ /dev/null @@ -1,63 +0,0 @@ -:root { - --tk-control : #000000; - --tk-control-active : #550000; - --tk-control-border : #ff0000; - --tk-control-highlight : #550000; - --tk-control-shadow : #aa0000; - --tk-control-text : #ff0000; - --tk-desktop : #000000; - --tk-selected : #aa0000; - --tk-selected-blur : #550000; - --tk-selected-blur-text : #ff0000; - --tk-selected-text : #000000; - --tk-splitter-focus : #ff0000aa; - --tk-window : #000000; - --tk-window-blur-close : #000000; - --tk-window-blur-close-text : #aa0000; - --tk-window-blur-title : #000000; - --tk-window-blur-title2 : #000000; - --tk-window-blur-title-text : #aa0000; - --tk-window-close : #550000; - --tk-window-close-focus : #ff0000; - --tk-window-close-focus-text: #550000; - --tk-window-close-text : #ff0000; - --tk-window-text : #ff0000; - --tk-window-title : #550000; - --tk-window-title2 : #550000; - --tk-window-title-text : #ff0000; -} - -input, select { - filter: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9InYiPjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VHcmFwaGljIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMSAwIiAvPjwvZmlsdGVyPjwvc3ZnPg==#v"); -} - -.tk.scroll-bar .unit-less, -.tk.scroll-bar .unit-more, -.tk.scroll-bar .thumb { - background: #550000; -} -.tk.scroll-bar .track { - background: #000000; -} - -.tk.scroll-bar:focus, -.tk.scroll-bar:focus .unit-less, -.tk.scroll-bar:focus .unit-more, -.tk.scroll-bar:focus .thumb { - background : #aa0000; - border-color: #ff0000; - color : #000000; -} -.tk.scroll-bar:focus .track { - background: #550000; -} -.tk.scroll-bar:focus .thumb { - box-shadow: 0 0 0 1px #ff0000; -} - -.tk.window { - box-shadow: 1px 1px 0 #550000; -} -.tk.window:focus-within { - box-shadow: 1px 1px 0 #aa0000; -} diff --git a/web/toolkit/App.js b/web/toolkit/App.js deleted file mode 100644 index 8eafa70..0000000 --- a/web/toolkit/App.js +++ /dev/null @@ -1,249 +0,0 @@ -let register = Toolkit => Toolkit.App = - -// Root application container and localization manager -class App extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(options) { - super(null, Object.assign({ - tabIndex: -1 - }, options)); - - // Configure instance fields - this.components = new Set(); - this.dragElement = null; - this.lastFocus = null; - this.locale = null; - this.locales = new Map(); - - // Configure event handlers - this.addEventListener("focusin", e=>this.onFocus(e)); - this.addEventListener("keydown", e=>this.onKey (e)); - this.addEventListener("keyup" , e=>this.onKey (e)); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Child element focus gained - onFocus(e) { - - // Error checking - if (e.target != document.activeElement) - return; - - // Target is self - if (e.target == this.element) - return this.restoreFocus(); - - // Ensure the child is not contained in a MenuBar - for (let elm = e.target; elm != this.element; elm = elm.parentNode) { - if (elm.getAttribute("role") == "menubar") - return; - } - - // Track the (non-menu) element as the most recent focused component - this.lastFocus = e.target; - } - - // Key press, key release - onKey(e) { - if (this.dragElement == null || e.rerouted) - return; - this.dragElement.dispatchEvent(Object.assign(new Event(e.type), { - altKey : e.altKey, - ctrlKey : e.ctrlKey, - key : e.key, - rerouted: true, - shiftKey: e.shiftKey - })); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Install a locale from URL - async addLocale(url) { - let data; - - // Load the file as JSON, using UTF-8 with or without a BOM - try { data = JSON.parse(new TextDecoder().decode( - await (await fetch(url)).arrayBuffer() )); } - catch { return null; } - - // Error checking - if (!data.id || !data.name) - return null; - - // Flatten the object to keys - let locale = new Map(); - let entries = Object.entries(data); - let stack = []; - while (entries.length != 0) { - let entry = entries.shift(); - - // The value is a non-array object - if (entry[1] instanceof Object && !Array.isArray(entry[1])) { - entries = entries.concat(Object.entries(entry[1]) - .map(e=>[ entry[0] + "." + e[0], e[1] ])); - } - - // The value is a primitive or array - else locale.set(entry[0].toLowerCase(), entry[1]); - } - - this.locales.set(data.id, locale); - return data.id; - } - - // Specify a localization dictionary - setLocale(id) { - if (!this.locales.has(id)) - return false; - this.locale = this.locales.get(id); - for (let comp of this.components) - comp.localize(); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Begin dragging on an element - get drag() { return this.dragElement; } - set drag(event) { - - // Begin dragging - if (event) { - this.dragElement = event.currentTarget; - this.dragPointer = event.pointerId; - this.dragElement.setPointerCapture(event.pointerId); - } - - // End dragging - else { - if (this.dragElement) - this.dragElement.releasePointerCapture(this.dragPointer); - this.dragElement = null; - this.dragPointer = null; - } - - } - - // Configure components for automatic localization, or localize a message - localize(a, b) { - return a instanceof Object ? this.localizeComponents(a, b) : - this.localizeMessage(a, b); - } - - // Return focus to the most recent focused element - restoreFocus() { - - // Error checking - if (!this.lastFocus) - return false; - - // Unable to restore focus - if (!this.isVisible(this.lastFocus)) - return false; - - // Transfer focus to the most recent element - this.lastFocus.focus({ preventScroll: true }); - return true; - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Configure components for automatic localization - localizeComponents(comps, add) { - - // Process all components - for (let comp of (Array.isArray(comps) ? comps : [comps])) { - - // Error checking - if ( - !(comp instanceof Toolkit.Component) || - !(comp.localize instanceof Function) - ) continue; - - // Update the collection and component text - this.components[add ? "add" : "delete"](comp); - comp.localize(); - } - - } - - // Localize a message - localizeMessage(message, substs, circle = new Set()) { - let parts = []; - - // Separate the substitution keys from the literal text - for (let x = 0;;) { - - // Locate the start of the next substitution key - let y = message.indexOf("{", x); - let z = y == -1 ? -1 : message.indexOf("}", y + 1); - - // No substitution key or malformed substitution expression - if (z == -1) { - parts.push(message.substring(z == -1 ? x : y)); - break; - } - - // Append the literal text and the substitution key - parts.push(message.substring(x, y), message.substring(y + 1, z)); - x = z + 1; - } - - // Process all substitutions - for (let x = 1; x < parts.length; x += 2) { - let key = parts[x].toLowerCase(); - let value; - - // The substitution key is already in the recursion chain - if (circle.has(key)) { - parts[x] = "{\u21ba" + key.toUpperCase() + "}"; - continue; - } - - // Resolve the substitution key from the argument - if (substs && substs.has(key)) { - value = substs.get(key); - - // Do not recurse for this substitution - if (!value[1]) { - parts[x] = value[0]; - continue; - } - - // Substitution text - value = value[0]; - } - - // Resolve the substitution from the current locale - else if (this.locale && this.locale.has(key)) - value = this.locale.get(key); - - // A matching substitution key was not found - else { - parts[x] = "{\u00d7" + key.toUpperCase() + "}"; - continue; - } - - // Perform recursive substitution - circle.add(key); - parts[x] = this.localizeMessage(value, substs, circle); - circle.delete(key); - } - - return parts.join(""); - } - -}; - -export { register }; diff --git a/web/toolkit/Button.js b/web/toolkit/Button.js deleted file mode 100644 index 932e8f0..0000000 --- a/web/toolkit/Button.js +++ /dev/null @@ -1,119 +0,0 @@ -let register = Toolkit => Toolkit.Button = - -// Push button -class Button extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, options = Object.assign({ - class : "tk button", - role : "button", - tabIndex: "0" - }, options)); - - // Configure options - if ("disabled" in options) - this.disabled = options.disabled; - this.doNotFocus = !("doNotFocus" in options) || options.doNotFocus; - - // Display text - this.content = new Toolkit.Label(app); - this.add(this.content); - - // Event handlers - this.addEventListener("keydown" , e=>this.onKeyDown (e)); - this.addEventListener("pointerdown", e=>this.onPointerDown(e)); - this.addEventListener("pointermove", e=>this.onPointerMove(e)); - this.addEventListener("pointerup" , e=>this.onPointerUp (e)); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Key press - onKeyDown(e) { - if ( - !(e.altKey || e.ctrlKey || e.shiftKey || this.disabled) && - (e.key == " " || e.key == "Enter") - ) this.activate(); - } - - // Pointer down - onPointerDown(e) { - - // Gain focus - if ( - !this.doNotFocus && - this.isFocusable() && - this.element != document.activeElement - ) this.element.focus(); - else e.preventDefault(); - - // Do not drag - if ( - e.button != 0 || - this.disabled || - this.element.hasPointerCapture(e.pointerId) - ) return; - - // Begin dragging - this.element.setPointerCapture(e.pointerId); - this.element.classList.add("pushed"); - Toolkit.handle(e); - } - - // Pointer move - onPointerMove(e) { - - // Do not drag - if (!this.element.hasPointerCapture(e.pointerId)) - return; - - // Process dragging - this.element.classList[this.isWithin(e) ? "add" : "remove"]("pushed"); - Toolkit.handle(e); - } - - // Pointer up - onPointerUp(e) { - - // Do not activate - if (e.button != 0 || !this.element.hasPointerCapture(e.pointerId)) - return; - - // End dragging - this.element.releasePointerCapture(e.pointerId); - this.element.classList.remove("pushed"); - Toolkit.handle(e); - - // Activate the button if applicable - if (this.isWithin(e)) - this.activate(); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Simulate a click on the button - activate() { - if (!this.disabled) - this.element.dispatchEvent(new Event("action")); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Update localization strings - localize() { - this.localizeText(this.content); - this.localizeLabel(); - this.localizeTitle(); - } - -} - -export { register }; diff --git a/web/toolkit/Checkbox.js b/web/toolkit/Checkbox.js deleted file mode 100644 index d718447..0000000 --- a/web/toolkit/Checkbox.js +++ /dev/null @@ -1,157 +0,0 @@ -let register = Toolkit => Toolkit.Checkbox = - -// Check box -class Checkbox extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, options = Object.assign({ - class : "tk checkbox", - role : "checkbox", - tabIndex: "0" - }, options, { style: Object.assign({ - alignItems : "center", - display : "inline-grid", - gridTemplateColumns: "max-content auto" - }, options.style || {}) })); - - // Configure element - this.element.setAttribute("aria-checked", "false"); - this.addEventListener("keydown" , e=>this.onKeyDown (e)); - this.addEventListener("pointerdown", e=>this.onPointerDown(e)); - this.addEventListener("pointermove", e=>this.onPointerMove(e)); - this.addEventListener("pointerup" , e=>this.onPointerUp (e)); - - // Icon area - this.box = document.createElement("div"); - this.box.className = "tk box"; - this.append(this.box); - - // Display text - this.uiLabel = new Toolkit.Label(app); - this.add(this.uiLabel); - - // Configure options - this.checked = options.checked; - this.disabled = !!options.disabled; - this.instant = !!options.instant; - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Key press - onKeyDown(e) { - if ( - !(e.altKey || e.ctrlKey || e.shiftKey || this.disabled) && - (e.key == " " || e.key == "Enter") - ) this.setChecked(!this.checked); - } - - // Pointer down - onPointerDown(e) { - - // Gain focus - if (!this.disabled) - this.element.focus(); - else e.preventDefault(); - - // Do not drag - if ( - e.button != 0 || - this.disabled || - this.element.hasPointerCapture(e.pointerId) - ) return; - - // Begin dragging - this.element.setPointerCapture(e.pointerId); - - // Use instant activation - if (this.instant) - return this.onPointerUp(e); - - // Do not use instant activation - this.element.classList.add("pushed"); - Toolkit.handle(e); - } - - // Pointer move - onPointerMove(e) { - - // Do not drag - if (!this.element.hasPointerCapture(e.pointerId)) - return; - - // Process dragging - this.element.classList[this.isWithin(e) ? "add" : "remove"]("pushed"); - Toolkit.handle(e); - } - - // Pointer up - onPointerUp(e) { - - // Do not activate - if (e.button != 0 || !this.element.hasPointerCapture(e.pointerId)) - return; - - // End dragging - this.element.releasePointerCapture(e.pointerId); - this.element.classList.remove("pushed"); - Toolkit.handle(e); - - // Activate the check box if applicable - if (this.isWithin(e)) - this.setChecked(this.checked !== true); - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // The check box is checked - get checked() { - let ret = this.element.getAttribute("aria-checked"); - return ret == "mixed" ? ret : ret == "true"; - } - set checked(checked) { - checked = checked == "mixed" ? checked : !!checked; - if (checked == this.checked) - return; - this.element.setAttribute("aria-checked", checked); - } - - // Specify the display text - setText(text, localize) { - this.uiLabel.setText(text, localize); - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Update localization strings - localize() { - this.uiLabel.localize(); - this.localizeTitle(); - } - - - - ///////////////////////////// Private Methods ///////////////////////////// - - // Specify the checked state - setChecked(checked) { - checked = !!checked; - if (checked == this.checked) - return; - let previous = this.checked - this.checked = checked; - this.element.dispatchEvent( - Object.assign(new Event("input"), { previous: previous })); - } - -} - -export { register }; diff --git a/web/toolkit/Component.js b/web/toolkit/Component.js deleted file mode 100644 index 1c62414..0000000 --- a/web/toolkit/Component.js +++ /dev/null @@ -1,473 +0,0 @@ -let register = Toolkit => Toolkit.Component = - -// Base class from which all toolkit components are derived -class Component { - - //////////////////////////////// Constants //////////////////////////////// - - // Non-attributes - static NON_ATTRIBUTES = new Set([ - "checked", "disabled", "doNotFocus", "group", "hover", "max", "min", - "name", "orientation", "overflowX", "overflowY", "tag", "text", - "value", "view", "visibility", "visible" - ]); - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - - // Configure element - this.element = document.createElement(options.tag || "div"); - this.element.component = this; - for (let entry of Object.entries(options)) { - if ( - Toolkit.Component.NON_ATTRIBUTES.has(entry[0]) || - entry[0] == "type" && options.tag != "input" - ) continue; - if (entry[0] == "style" && entry[1] instanceof Object) - Object.assign(this.element.style, entry[1]); - else this.element.setAttribute(entry[0], entry[1]); - } - - // Configure instance fields - this._isLocalized = false; - this.app = app; - this.display = options.style && options.style.display; - this.style = this.element.style; - this.text = null; - this.visibility = !!options.visibility; - this.visible = !("visible" in options) || options.visible; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Add a child component - add(comp) { - - // Error checking - if ( - !(comp instanceof Toolkit.Component) || - comp instanceof Toolkit.App || - comp.app != (this.app || this) - ) return false; - - // No components have been added yet - if (!this.children) - this.children = []; - - // The child already has a parent: remove it - if (comp.parent) { - comp.parent.children.splice( - comp.parent.children.indexOf(comp), 1); - } - - // Add the component to self - this.children.push(comp); - this.append(comp.element); - comp.parent = this; - return true; - } - - // Register an event listener on the element - addEventListener(type, listener) { - - // No event listeners have been registered yet - if (!this.listeners) - this.listeners = new Map(); - if (!this.listeners.has(type)) - this.listeners.set(type, []); - - // The listener has already been registered for this event - let listeners = this.listeners.get(type); - if (listeners.indexOf(listener) != -1) - return listener; - - // Resize events are implemented by a ResizeObserver - if (type == "resize") { - if (!this.resizeObserver) { - this.resizeObserver = new ResizeObserver(()=> - this.element.dispatchEvent(new Event("resize"))); - this.resizeObserver.observe(this.element); - } - } - - // Visibility events are implemented by an IntersectionObserver - else if (type == "visibility") { - if (!this.visibilityObserver) { - this.visibilityObserver = new IntersectionObserver( - ()=>this.element.dispatchEvent(Object.assign( - new Event("visibility"), - { visible: this.isVisible() } - )), - { root: document.body } - ); - this.visibilityObserver.observe(this.element); - } - } - - // Register the listener with the element - listeners.push(listener); - this.element.addEventListener(type, listener); - return listener; - } - - // Component cannot be interacted with - get disabled() { return this.element.hasAttribute("disabled"); } - set disabled(disabled) { this.setDisabled(disabled); } - - // Move focus into the component - focus() { - this.element.focus({ preventScroll: true }); - } - - // Specify whether the component is localized - get isLocalized() { return this._isLocalized; } - set isLocalized(isLocalized) { - if (isLocalized == this._isLocalized) - return; - this._isLocalized = isLocalized; - (this instanceof Toolkit.App ? this : this.app) - .localize(this, isLocalized); - } - - // Determine whether an element is actually visible - isVisible(element = this.element) { - if (!document.body.contains(element)) - return false; - for (; element instanceof Element; element = element.parentNode) { - let style = getComputedStyle(element); - if (style.display == "none" || style.visibility == "hidden") - return false; - } - return true; - } - - // Produce an ordered list of registered event listeners for an event type - listEventListeners(type) { - return this.listeners && this.listeners.has(type) && - this.listeners.get(type).list.slice() || []; - } - - // Remove a child component - remove(comp) { - if (comp.parent != this || !this.children) - return false; - let index = this.children.indexOf(comp); - if (index == -1) - return false; - this.children.splice(index, 1); - comp.element.remove(); - comp.parent = null; - return true; - } - - // Unregister an event listener from the element - removeEventListener(type, listener) { - - // Not listening to events of the specified type - if (!this.listeners || !this.listeners.has(type)) - return listener; - - // Listener is not registered - let listeners = this.listeners.get(type); - let index = listeners.indexOf(listener); - if (index == -1) - return listener; - - // Unregister the listener - this.element.removeEventListener(listener); - listeners.splice(index, 1); - - // Delete the ResizeObserver - if ( - type == "resize" && - listeners.list.length == 0 && - this.resizeObserver - ) { - this.resizeObserver.disconnect(); - delete this.resizeObserver; - } - - // Delete the IntersectionObserver - else if ( - type == "visibility" && - listeners.list.length == 0 && - this.visibilityObserver - ) { - this.visibilityObserver.disconnect(); - delete this.visibilityObserver; - } - - return listener; - } - - // Specify accessible name - setLabel(text, localize) { - - // Label is another component - if ( - text instanceof Toolkit.Component || - text instanceof HTMLElement - ) { - this.element.setAttribute("aria-labelledby", - (text.element || text).id); - this.setString("label", null, false); - } - - // Label is the given text - else { - this.element.removeAttribute("aria-labelledby"); - this.setString("label", text, localize); - } - - } - - // Specify role description text - setRoleDescription(text, localize) { - this.setString("roleDescription", text, localize); - } - - // Specify inner text - setText(text, localize) { - this.setString("text", text, localize); - } - - // Specify tooltip text - setTitle(text, localize) { - this.setString("title", text, localize); - } - - // Specify substitution text - substitute(key, text = null, recurse = false) { - if (text === null) { - if (this.substitutions.has(key)) - this.substitutions.delete(key); - } else this.substitutions.set(key, [ text, recurse ]); - if (this.localize instanceof Function) - this.localize(); - } - - // Determine whether the element wants to be visible - get visible() { - let style = this.element.style; - return style.display != "none" && style.visibility != "hidden"; - } - - // Specify whether the element is visible - set visible(visible) { - visible = !!visible; - - // Visibility is not changing - if (visible == this.visible) - return; - - let comps = [ this ].concat( - Array.from(this.element.querySelectorAll("*")) - .map(c=>c.component) - ).filter(c=> - c instanceof Toolkit.Component && - c.listeners && - c.listeners.has("visibility") - ) - ; - let prevs = comps.map(c=>c.isVisible()); - - // Allow the component to be shown - if (visible) { - if (!this.visibility) { - if (this.display) - this.element.style.display = this.display; - else this.element.style.removeProperty("display"); - } else this.element.style.removeProperty("visibility"); - } - - // Prevent the component from being shown - else { - this.element.style.setProperty( - this.visibility ? "visibility" : "display", - this.visibility ? "hidden" : "none" - ); - } - - for (let x = 0; x < comps.length; x++) { - let comp = comps[x]; - visible = comp.isVisible(); - if (visible == prevs[x]) - continue; - comp.element.dispatchEvent(Object.assign( - new Event("visibility"), - { visible: visible } - )); - } - - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Add a child component to the primary client region of this component - append(element) { - this.element.append(element instanceof Toolkit.Component ? - element.element : element); - } - - // Determine whether a component or element is a child of this component - contains(child) { - return this.element.contains(child instanceof Toolkit.Component ? - child.element : child); - } - - // Generate a list of focusable descendant elements - getFocusable(element = this.element) { - let cache; - return Array.from(element.querySelectorAll( - "*:is(a[href],area,button,details,input,textarea,select," + - "[tabindex='0']):not([disabled])" - )).filter(e=>{ - for (; e instanceof Element; e = e.parentNode) { - let style = - (cache || (cache = new Map())).get(e) || - cache.set(e, getComputedStyle(e)).get(e) - ; - if (style.display == "none" || style.visibility == "hidden") - return false; - } - return true; - }); - } - - // Specify the inner text of the primary client region of this component - get innerText() { return this.element.textContent; } - set innerText(text) { this.element.innerText = text; } - - // Determine whether an element is focusable - isFocusable(element = this.element) { - return element.matches( - ":is(a[href],area,button,details,input,textarea,select," + - "[tabindex='0'],[tabindex='-1']):not([disabled])" - ); - } - - // Determine whether a pointer event is within the element - isWithin(e, element = this.element) { - let bounds = element.getBoundingClientRect(); - return ( - e.clientX >= bounds.left && e.clientX < bounds.right && - e.clientY >= bounds.top && e.clientY < bounds.bottom - ); - } - - // Common processing for localizing the accessible name - localizeLabel(element = this.element) { - - // There is no label or the label is another element - if (!this.label || element.hasAttribute("aria-labelledby")) { - element.removeAttribute("aria-label"); - return; - } - - // Localize the label - let text = this.label; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.setAttribute("aria-label", text); - } - - // Common processing for localizing the accessible role description - localizeRoleDescription(element = this.element) { - - // There is no role description - if (!this.roleDescription) { - element.removeAttribute("aria-roledescription"); - return; - } - - // Localize the role description - let text = this.roleDescription; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.setAttribute("aria-roledescription", text); - } - - // Common processing for localizing inner text - localizeText(element = this.element) { - - // There is no title - if (!this.text) { - element.innerText = ""; - return; - } - - // Localize the text - let text = this.text; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.innerText = text; - } - - // Common processing for localizing the tooltip text - localizeTitle(element = this.element) { - - // There is no title - if (!this.title) { - element.removeAttribute("title"); - return; - } - - // Localize the title - let text = this.title; - text = !text[1] ? text[0] : - this.app.localize(text[0], this.substitutions); - element.setAttribute("title", text); - } - - // Common handler for configuring whether the component is disabled - setDisabled(disabled, element = this.element) { - element[disabled ? "setAttribute" : "removeAttribute"] - ("disabled", ""); - element.setAttribute("aria-disabled", disabled ? "true" : "false"); - } - - // Specify display text - setString(key, value, localize = true) { - - // There is no method to update the display text - if (!(this.localize instanceof Function)) - return; - - // Working variables - let app = this instanceof Toolkit.App ? this : this.app; - - // Remove the string - if (value === null) { - if (app && this[key] != null && this[key][1]) - app.localize(this, false); - this[key] = null; - } - - // Set or replace the string - else { - if (app && localize && (this[key] == null || !this[key][1])) - app.localize(this, true); - this[key] = [ value, localize ]; - } - - // Update the display text - this.localize(); - } - - // Retrieve the substitutions map - get substitutions() { - if (!this._substitutions) - this._substitutions = new Map(); - return this._substitutions; - } - -}; - -export { register }; diff --git a/web/toolkit/Desktop.js b/web/toolkit/Desktop.js deleted file mode 100644 index 16815bb..0000000 --- a/web/toolkit/Desktop.js +++ /dev/null @@ -1,86 +0,0 @@ -let register = Toolkit => Toolkit.Desktop = - -// Layered window manager -class Desktop extends Toolkit.Component { - - //////////////////////////////// Constants //////////////////////////////// - - // Comparator for ordering child windows - static CHILD_ORDER(a, b) { - return b.element.style.zIndex - a.element.style.zIndex; - } - - - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, Object.assign({ - class: "tk desktop" - }, options, { style: Object.assign({ - position: "relative", - zIndex : "0" - }, options.style || {})} )); - - // Configure event listeners - this.addEventListener("resize", e=>this.onResize()); - } - - - - ///////////////////////////// Event Handlers ////////////////////////////// - - // Element resized - onResize() { - - // The element is hidden: its size is indeterminate - if (!this.isVisible()) - return; - - // Don't allow children to be out-of-frame - if (this.children != null) { - for (let child of this.children) { - child.left = child.left; - child.top = child.top; - } - } - - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Add a child component - add(comp) { - super.add(comp); - this.bringToFront(comp); - } - - // Retrieve the foremost visible window - getActiveWindow() { - if (this.children != null) { - for (let child of this.children) { - if (child.isVisible()) - return child; - } - } - return null; - } - - - - ///////////////////////////// Package Methods ///////////////////////////// - - // Reorder children so that a particular child is in front - bringToFront(child) { - this.children.splice(this.children.indexOf(child), 1); - this.children.push(child); - let z = 1 - this.children.length; - for (let child of this.children) - child.element.style.zIndex = z++; - } - -} - -export { register }; diff --git a/web/toolkit/DropDown.js b/web/toolkit/DropDown.js deleted file mode 100644 index e95bdb2..0000000 --- a/web/toolkit/DropDown.js +++ /dev/null @@ -1,154 +0,0 @@ -let register = Toolkit => Toolkit.DropDown = - -class DropDown extends Toolkit.Component { - - ///////////////////////// Initialization Methods ////////////////////////// - - constructor(app, options = {}) { - super(app, Object.assign({ - class: "tk drop-down", - tag : "select" - }, options)); - - // Configure instance fields - this.items = []; - } - - - - ///////////////////////////// Public Methods ////////////////////////////// - - // Add an item - add(text, localize, value) { - - // Record the item data - this.items.push([ text, localize, value ]); - - // Add an