From 5ba3ea7af572611778d74f0cad51cb0cff10f9a8 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 15 Dec 2023 12:29:22 -0500 Subject: [PATCH 01/40] Fix silly typos in "whats.new" file. --- whats.new | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/whats.new b/whats.new index 31f53a36..dc156735 100644 --- a/whats.new +++ b/whats.new @@ -34,7 +34,7 @@ New in version 4.00: ISOlink changes ... ------------------- - Add "-sgx" CLI parameter to put a SuperGRAFX string into CD-ROM projects. -- Slight changes to the CLI format to allow HuC uses to name CD projects. +- Slight changes to the CLI format to allow HuC users to name CD projects. - Change sector location and format of the directory information. - Change HuC location for patching directory info into HuC projects. - Change default startup location for HuC projects. @@ -49,7 +49,7 @@ New in version 4.00: - Allow KickC to run multiple passes to resolve forward-referenced symbols. - Fix some data directives not expanding the ROM size during early passes. - Enable JMP from one procedure to another to allow tail-call optimization. -- Enable expression evalution to be used in CALL pseudo-op. +- Enable expression evaluation to be used in CALL pseudo-op. - Change default procedure packing to match PCEAS v3.21, use "-O" for enable the newer optimized procedure packing. - Fix procedure relocation from breaking some code declared after procedures. From b6e6c256b0f3bdd8a627b9f93136fe4f658c9b73 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 15 Dec 2023 12:51:39 -0500 Subject: [PATCH 02/40] Change GulliverBoy HuVIDEO example to check for the correct CD, and skip video playback if not. --- .../asm/elmer/ted2-core-gulliver/gulliver.asm | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/examples/asm/elmer/ted2-core-gulliver/gulliver.asm b/examples/asm/elmer/ted2-core-gulliver/gulliver.asm index d7b1a665..fed599a3 100644 --- a/examples/asm/elmer/ted2-core-gulliver/gulliver.asm +++ b/examples/asm/elmer/ted2-core-gulliver/gulliver.asm @@ -135,6 +135,9 @@ SUPPORT_TIMING = 1 ; Include the HuVIDEO timing information. ; .zp + +which_video: ds 1 + .bss .code @@ -241,13 +244,14 @@ core_main: ; Turn the display off and initialize the screen mode. .endif .sign_on: PRINTF "\eX3\eY22GulliverBoy HuVIDEO Player" - PRINTF "\eX35\eY22GulliverBoy HuVIDEO Player" PRINTF "\eX2\eY23Buffer used when refill: $xx" PRINTF "\eX2\eY24Buffer used refill done: $xx" PRINTF "\eX2\eY25Buffer refill time 1st: $xx" PRINTF "\eX2\eY26Buffer refill time rest: $xx" + PRINTF "\eX35\eY22GulliverBoy HuVIDEO Player" + PRINTF "\eX34\eY23Buffer used when refill: $xx" PRINTF "\eX34\eY24Buffer used refill done: $xx" PRINTF "\eX34\eY25Buffer refill time 1st: $xx" @@ -262,11 +266,30 @@ core_main: ; Turn the display off and initialize the screen mode. beq !+ ; Are we ready? jmp .main_loop +!: lda #$25 ; Default to NOT Gulliver CD! + + ldx tnomax ; Gulliver CD has 10 BCD tracks. + cpx #$10 + bne !+ + ldx outmin ; Gulliver CD has 73 BCD minutes. + cpx #$73 + bne !+ + ldx outsec ; Gulliver CD has 23 BCD seconds. + cpx #$23 + bne !+ + ldx outfrm ; Gulliver CD has 53 BCD frames. + cpx #$53 + bne !+ + +; lda #$24 ; Star Boy Movie. + lda #$22 ; Title Movie + ; Play the "GulliverBoy" title HuVIDEO. -!: -; lda #$24 ; Star Boy - lda #$22 ; Title Movie +!: sta Date: Fri, 15 Dec 2023 12:54:47 -0500 Subject: [PATCH 03/40] Add more alternative far-call implementations to common.asm as unused examples of methods. --- examples/asm/elmer/include/common.asm | 132 +++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/examples/asm/elmer/include/common.asm b/examples/asm/elmer/include/common.asm index 37b789ba..1ffe527b 100644 --- a/examples/asm/elmer/include/common.asm +++ b/examples/asm/elmer/include/common.asm @@ -224,11 +224,13 @@ inc.h_di_mpr4: inc.h <_di ; Increment hi-byte of _di. ; tya ; rts ; -; N.B. This costs 36 bytes, and takes 82 cycles vs 18 for the trampoline +; N.B. This costs 32 bytes, and takes 82 cycles vs 18 for the trampoline ; code (when you exclude preserving YA in zero-page). ; ; N.B. This is NOT re-entrant, and must NOT be used in an IRQ handler if ; _temp is not saved and restored! +; +; N.B. This was written as an excerise, and I wouldn't recommend using it! ; .if 0 @@ -264,3 +266,131 @@ far_call: sta.l <_bp ; Preserve YA registers as rts ; Jump to routine. .endif + + + +; *************************************************************************** +; *************************************************************************** +; +; Far-call a function in another bank. +; +; This is compatible with PCEAS's "-newproc" procedure calls, but avoids +; generating a 10-byte procedure trampoline. +; +; To use this ... +; +; brk +; tst #bank( myfunc ), myfunc - 1 +; +; The "TST" instruction itself is skipped and NOT executed after the call, +; it only exists to make things easier to read in a listing/debugger. +; +; The called .PROC routine must exit with "jmp leave_proc" and not "rts". +; +; leave_proc: pla +; tam6 +; tya +; rts +; +; N.B. This costs 45 bytes, and takes 103 cycles (or 84 on HuCARD) vs 18 for +; the trampoline code (when you exclude preserving YA in zero-page). +; +; N.B. This is NOT re-entrant, and must NOT be used in an IRQ handler if +; _temp is not saved and restored! +; +; N.B. This was written as an excerise, and I wouldn't recommend using it! +; + + .if 0 + +irq2_handler: phx ; Preserve X register + tsx ; + tst #$10, $2102, x ; Is the B flag set? + beq .got_irq2 ; + plx ; Restore X register. + + ; Handle interrupt as BRK. + +.got_brk: plp ; Restore interrupt flag. + + sta.l <_bp ; Preserve YA registers as + sty.h <_bp ; an address parameter. + + pla ; Get return address lo-byte. + sta.l <_temp + clc ; Skip the far_call() + adc #2 ; address parameter. + tay + + pla ; Get return address hi-byte. + sta.h <_temp + adc #0 + pha ; Put updated return address. + phy + + tma6 ; Preserve MPR6. + pha + + ldy #2 ; Push far_call() addr. + lda [_temp], y + pha + dey + lda [_temp], y + pha + + lda [_temp] ; Read far_call() bank. + tam6 + + rts ; Jump to routine. + + ; Handle interrupt as IRQ2. + +.got_irq2: plx ; Process as an interrupt. + ; ... + + .endif + + + +; *************************************************************************** +; *************************************************************************** +; +; Far-call a function in another bank. +; +; This is a potential alternative procedure call trampoline that uses only 10 +; bytes of common memory per bank of procedures, instead of 10 bytes for each +; individual procedure call, BUT it uses the X register as a procedure-index, +; and it needs a table of addresses at the end of every procedure bank. +; +; To use this ... +; +; ldx #procedure-index +; jsr far_call_nn +; +; The called .PROC routine must exit with "jmp leave_proc" and not "rts". +; +; leave_proc: pla +; tam6 +; tya +; rts +; +; N.B. This costs 21 cycles vs 18 for the .newproc trampoline code (when you +; exclude preserving YA in zero-page). +; +; N.B. This was written as an excerise, and definitely not for HuC! +; + + .if 0 + +far_call_nn: +; sta.l <_bp ; 4 Preserve YA registers as +; sty.h <_bp ; 4 an address parameter. + + tma6 ; 4 Preserve MPR6. + pha ; 3 + + lda #bank_number ; 2 + tam6 ; 5 + jmp [$DF00, x] ; 7 + + .endif ; 21 From 294a9f465b3838cf384c39841e7a28c1783195f5 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 15 Dec 2023 13:36:25 -0500 Subject: [PATCH 04/40] Rename "rom-bare-vdctest" example to "rom-bare-rcrtest" because that's really what it is. --- examples/asm/elmer/Makefile | 3 ++- .../{rom-bare-vdctest => rom-bare-rcrtest}/Makefile | 8 ++++---- .../c8hvuld.bat | Bin .../c8hvuld.chr | Bin .../vdctest.asm => rom-bare-rcrtest/rcrtest.asm} | 6 +++--- .../testspr.png | Bin 6 files changed, 9 insertions(+), 8 deletions(-) rename examples/asm/elmer/{rom-bare-vdctest => rom-bare-rcrtest}/Makefile (55%) rename examples/asm/elmer/{rom-bare-vdctest => rom-bare-rcrtest}/c8hvuld.bat (100%) rename examples/asm/elmer/{rom-bare-vdctest => rom-bare-rcrtest}/c8hvuld.chr (100%) rename examples/asm/elmer/{rom-bare-vdctest/vdctest.asm => rom-bare-rcrtest/rcrtest.asm} (98%) rename examples/asm/elmer/{rom-bare-vdctest => rom-bare-rcrtest}/testspr.png (100%) diff --git a/examples/asm/elmer/Makefile b/examples/asm/elmer/Makefile index f5d43750..3f1a31ca 100644 --- a/examples/asm/elmer/Makefile +++ b/examples/asm/elmer/Makefile @@ -2,8 +2,9 @@ # PREREQS = ipl-scd -SUBDIRS = rom-bare-tiatest rom-bare-vdctest scd-bios-hello scd-bios-hello-error \ +SUBDIRS = rom-bare-rcrtest rom-bare-tiatest \ rom-core-hello rom-core-okitest \ + scd-bios-hello scd-bios-hello-error \ cd-core-1stage cd-core-2stage scd-core-1stage scd-core-1stage-error \ scd-core-2stage scd-core-2stage-error scd-core-fastcd \ rom-kickc-hello rom-kickc-shmup ted2-core-hwdetect ted2-core-sdcard \ diff --git a/examples/asm/elmer/rom-bare-vdctest/Makefile b/examples/asm/elmer/rom-bare-rcrtest/Makefile similarity index 55% rename from examples/asm/elmer/rom-bare-vdctest/Makefile rename to examples/asm/elmer/rom-bare-rcrtest/Makefile index 359e6219..2fcf586e 100644 --- a/examples/asm/elmer/rom-bare-vdctest/Makefile +++ b/examples/asm/elmer/rom-bare-rcrtest/Makefile @@ -1,11 +1,11 @@ -all: vdctest.pce +all: rcrtest.pce include ../Make_ex.inc AFLAGS ?= -newproc -strip -m -l 2 -S SRC_INC = pceas.inc pcengine.inc joypad.asm tty.asm vce.asm vdc.asm -SRC_OVL = vdctest.asm +SRC_OVL = rcrtest.asm -vdctest.pce: $(SRC_OVL) $(SRC_INC) - $(AS) $(AFLAGS) -raw vdctest.asm +rcrtest.pce: $(SRC_OVL) $(SRC_INC) + $(AS) $(AFLAGS) -raw rcrtest.asm diff --git a/examples/asm/elmer/rom-bare-vdctest/c8hvuld.bat b/examples/asm/elmer/rom-bare-rcrtest/c8hvuld.bat similarity index 100% rename from examples/asm/elmer/rom-bare-vdctest/c8hvuld.bat rename to examples/asm/elmer/rom-bare-rcrtest/c8hvuld.bat diff --git a/examples/asm/elmer/rom-bare-vdctest/c8hvuld.chr b/examples/asm/elmer/rom-bare-rcrtest/c8hvuld.chr similarity index 100% rename from examples/asm/elmer/rom-bare-vdctest/c8hvuld.chr rename to examples/asm/elmer/rom-bare-rcrtest/c8hvuld.chr diff --git a/examples/asm/elmer/rom-bare-vdctest/vdctest.asm b/examples/asm/elmer/rom-bare-rcrtest/rcrtest.asm similarity index 98% rename from examples/asm/elmer/rom-bare-vdctest/vdctest.asm rename to examples/asm/elmer/rom-bare-rcrtest/rcrtest.asm index fae982cb..d856c39d 100644 --- a/examples/asm/elmer/rom-bare-vdctest/vdctest.asm +++ b/examples/asm/elmer/rom-bare-rcrtest/rcrtest.asm @@ -1,9 +1,9 @@ ; *************************************************************************** ; *************************************************************************** ; -; vdctest.asm +; rcrtest.asm ; -; VDC tester HuCARD example of using the basic HuCARD startup library code. +; RCR tester HuCARD example of using the basic HuCARD startup library code. ; ; Copyright John Brandwood 2022. ; @@ -207,7 +207,7 @@ bare_main: ; Turn the display off and initialize the screen mode. ; Loop around updating the display each frame. - PRINTF "\e<\eX1\eY5\eP2****PC ENGINE VDC IRQ TEST****\eP0\eX1\eY7\x1E\x1F\eP2:Change Delay\eP0 SEL\eP2:Resolution\eP0" + PRINTF "\e<\eX1\eY5\eP2****PC ENGINE RCR IRQ TEST****\eP0\eX1\eY7\x1E\x1F\eP2:Change Delay\eP0 SEL\eP2:Resolution\eP0" lda #1 ; Delay first VBLANK cycles sta irq_cnt ; update. diff --git a/examples/asm/elmer/rom-bare-vdctest/testspr.png b/examples/asm/elmer/rom-bare-rcrtest/testspr.png similarity index 100% rename from examples/asm/elmer/rom-bare-vdctest/testspr.png rename to examples/asm/elmer/rom-bare-rcrtest/testspr.png From 733e0ffa3a5e854894e93145095e0ed45666d999 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 15 Dec 2023 14:15:55 -0500 Subject: [PATCH 05/40] Add "rom-bare-buftest" example to show that VDC reads are buffered and not direct. --- examples/asm/elmer/Makefile | 2 +- examples/asm/elmer/README.md | 9 +- examples/asm/elmer/rom-bare-buftest/Makefile | 11 + .../asm/elmer/rom-bare-buftest/buftest.asm | 227 ++++++++++++++++++ 4 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 examples/asm/elmer/rom-bare-buftest/Makefile create mode 100644 examples/asm/elmer/rom-bare-buftest/buftest.asm diff --git a/examples/asm/elmer/Makefile b/examples/asm/elmer/Makefile index 3f1a31ca..af925c20 100644 --- a/examples/asm/elmer/Makefile +++ b/examples/asm/elmer/Makefile @@ -2,7 +2,7 @@ # PREREQS = ipl-scd -SUBDIRS = rom-bare-rcrtest rom-bare-tiatest \ +SUBDIRS = rom-bare-buftest rom-bare-rcrtest rom-bare-tiatest \ rom-core-hello rom-core-okitest \ scd-bios-hello scd-bios-hello-error \ cd-core-1stage cd-core-2stage scd-core-1stage scd-core-1stage-error \ diff --git a/examples/asm/elmer/README.md b/examples/asm/elmer/README.md index 534cf0c7..ca430f15 100644 --- a/examples/asm/elmer/README.md +++ b/examples/asm/elmer/README.md @@ -14,12 +14,15 @@ This directory contains a number of example programs and utilities for developin * ted2-bios-usbcd - A modified System Card 3.0 HuCARD that runs a CD-ROM overlay uploaded through USB, rather than loading it from CD-ROM. +* rom-bare-buftest + - A HuCARD ROM to compare a PC Engine emulator's buffered VDC reading with real PC Engine console hardware. + +* rom-bare-rcrtest + - A HuCARD ROM to compare a PC Engine emulator's RCR interrupt handling with real PC Engine console hardware. + * rom-bare-tiatest - A HuCARD ROM to compare a PC Engine emulator's TIA-to-VDC cycle timing with real PC Engine console hardware. -* rom-bare-vdctest - - A HuCARD ROM to compare a PC Engine emulator's VDC RCR interrupt handling with real PC Engine console hardware. - * rom-core-okitest - A HuCARD ROM to compare a PC Engine emulator's ADPCM playback flags and ADPCM write speed with real PC Engine console hardware. diff --git a/examples/asm/elmer/rom-bare-buftest/Makefile b/examples/asm/elmer/rom-bare-buftest/Makefile new file mode 100644 index 00000000..7b419c8d --- /dev/null +++ b/examples/asm/elmer/rom-bare-buftest/Makefile @@ -0,0 +1,11 @@ +all: buftest.pce + +include ../Make_ex.inc + +AFLAGS ?= -newproc -strip -m -l 2 -S + +SRC_INC = pceas.inc pcengine.inc joypad.asm tty.asm vce.asm vdc.asm +SRC_OVL = buftest.asm + +buftest.pce: $(SRC_OVL) $(SRC_INC) + $(AS) $(AFLAGS) -raw buftest.asm diff --git a/examples/asm/elmer/rom-bare-buftest/buftest.asm b/examples/asm/elmer/rom-bare-buftest/buftest.asm new file mode 100644 index 00000000..fb4c3df0 --- /dev/null +++ b/examples/asm/elmer/rom-bare-buftest/buftest.asm @@ -0,0 +1,227 @@ +; *************************************************************************** +; *************************************************************************** +; +; vdcbuffer.asm +; +; VDC buffer test HuCARD example of using the basic HuCARD startup library code. +; +; Copyright John Brandwood 2023. +; +; Distributed under the Boost Software License, Version 1.0. +; (See accompanying file LICENSE_1_0.txt or copy at +; http://www.boost.org/LICENSE_1_0.txt) +; +; *************************************************************************** +; *************************************************************************** +; +; The purpose of this example is to check whether the VDC's VRAM reading is +; buffered or direct. +; +; *************************************************************************** +; *************************************************************************** +; +; The PC Engine's memory map is set to ... +; +; MPR0 = bank $FF : PCE hardware +; MPR1 = bank $F8 : PCE RAM with Stack & ZP +; MPR2 = bank $00 : HuCARD ROM +; MPR3 = bank $01 : HuCARD ROM +; MPR4 = bank $02 : HuCARD ROM +; MPR5 = bank $03 : HuCARD ROM +; MPR6 = bank $04 : HuCARD ROM +; MPR7 = bank $00 : HuCARD ROM with the startup code and IRQ vectors. +; +; *************************************************************************** +; *************************************************************************** + + + ; + ; Create some equates for a very generic VRAM layout, with a + ; 64*32 BAT, followed by the SAT, then followed by the tiles + ; for the ASCII character set. + ; + ; This uses the first 8KBytes of VRAM ($0000-$0FFF). + ; + +BAT_LINE = 64 +BAT_SIZE = 64 * 32 +SAT_ADDR = BAT_SIZE ; SAT takes 16 tiles of VRAM. +CHR_ZERO = BAT_SIZE / 16 ; 1st tile # after the BAT. +CHR_0x10 = CHR_ZERO + 16 ; 1st tile # after the SAT. +CHR_0x20 = CHR_ZERO + 32 ; ASCII ' ' CHR tile #. + + .list + .mlist + + include "bare-startup.asm" ; No "CORE(not TM)" library! + + include "common.asm" ; Common helpers. + include "vdc.asm" ; Useful VDC routines. + include "font.asm" ; Useful font routines. + include "joypad.asm" ; Joypad routines. + include "tty.asm" ; Useful TTY print routines. + + + +; *************************************************************************** +; *************************************************************************** +; +; Constants and Variables. +; + + .zp + +value ds 2 ; Data value from VRAM read. + + .bss + + + +; *************************************************************************** +; *************************************************************************** +; +; bare_main - This is executed after startup library initialization. +; + + .code + +bare_main: ; Turn the display off and initialize the screen mode. + + jsr init_256x224 ; Initialize VDC & VRAM. + + ; Upload the font to VRAM. + + stz <_di + 0 ; Destination VRAM address. + lda #>(CHR_0x10 * 16) + sta <_di + 1 + + lda #$FF ; Put font in colors 4-7, + sta <_al ; so bitplane 2 = $FF and + stz <_ah ; bitplane 3 = $00. + + lda #16 + 96 ; 16 graphics + 96 ASCII. + sta <_bl + + lda #my_font + sta <_bp + 1 + ldy #^my_font + + call dropfnt8x8_vdc ; Upload font to VRAM. + + ; Upload the palette data to the VCE. + + stz <_al ; Start at palette 0 (BG). + lda #2 ; Copy 2 palettes of 16 colors. + sta <_ah + lda #screen_pal + sta <_bp + 1 + ldy #^screen_pal + call load_palettes ; Add to the palette queue. + + call xfer_palettes ; Transfer queue to VCE now. + + ; Turn on the BG & SPR layers, then wait for a soft-reset. + + call set_dspon ; Enable background. + + ; + + PRINTF "\e<\eX1\eY1\eP0***PC ENGINE VDC R/W BUFFER***\n\n\eP1" + + stz.l <_di ; _di = $8000 + lda #$80 + sta <_di + + jsr vdc_di_to_mawr ; Set MAWR=$8000. + + lda #$34 ; Write $1234 to VRAM $8000. + sta VDC_DL + lda #$12 + sta VDC_DH + + tii $2200,$2200,16 ; 113 cycle delay. + + jsr vdc_di_to_marr ; Set MARR=$8000, trigger read. + + tii $2200,$2200,16 ; 113 cycle delay. + + jsr vdc_di_to_mawr ; Set MAWR=$8000. + + lda #$55 ; Write $AA55 to VRAM $8000. + sta VDC_DL + lda #$AA + sta VDC_DH + + tii $2200,$2200,16 ; 113 cycle delay. + + lda VDC_DL ; Read contents of VRAM $8000. + sta.l Date: Sun, 17 Dec 2023 10:24:09 -0500 Subject: [PATCH 06/40] Add bank information to variable definitions in core.inc and bare-startup.asm --- examples/asm/elmer/include/bare-startup.asm | 6 ++-- examples/asm/elmer/include/core.inc | 32 ++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/asm/elmer/include/bare-startup.asm b/examples/asm/elmer/include/bare-startup.asm index 6de5f3da..5c686c28 100644 --- a/examples/asm/elmer/include/bare-startup.asm +++ b/examples/asm/elmer/include/bare-startup.asm @@ -370,10 +370,10 @@ DATA_BANK = BASE_BANK + 1 + RESERVE_BANKS _temp ds 2 ; Use within any ASM routine. _bank ds 1 ; Use within any ASM routine. -base_zp1st = $2003 ; 1st free user address. -base_zpend = $20EC +base_zp1st = $F8:2003 ; 1st free user address. +base_zpend = $F8:20EC -base_ram1st = $22D0 ; After the System Card! +base_ram1st = $F8:22D0 ; After the System Card! .bss .org base_ram1st diff --git a/examples/asm/elmer/include/core.inc b/examples/asm/elmer/include/core.inc index 27b65dca..4e707662 100644 --- a/examples/asm/elmer/include/core.inc +++ b/examples/asm/elmer/include/core.inc @@ -71,9 +71,9 @@ sound_hook = nmi_hook ; Sound Driver to run in VBL. ; .if USING_PSGDRIVER -core_zpend = $20E6 +core_zpend = $F8:20E6 .else -core_zpend = $20EC +core_zpend = $F8:20EC .endif USING_PSGDRIVER .if SUPPORT_SGX @@ -91,23 +91,23 @@ core_zpend = $20EC PCE_VDC_OFFSET = $00 ; Offset to PCE VDC hw & vars. SGX_VDC_OFFSET = $10 ; Offset to SGX VDC hw & vars. -_temp = $2000 ; Use within any ASM routine. -_bank = $2002 ; Use within any ASM routine. -sgx_crl = $2003 ; SGX shadow (vdc_crl = $20F3). -sgx_crh = $2004 ; SGX shadow (vdc_crh = $20F4). -core_1stbank = $2005 ; 1st bank of library code. -sgx_sr = $2006 ; SGX shadow (vdc_sr = $20F6). -sgx_reg = $2007 ; SGX shadow (vdc_reg = $20F7). +_temp = $F8:2000 ; Use within any ASM routine. +_bank = $F8:2002 ; Use within any ASM routine. +sgx_crl = $F8:2003 ; SGX shadow (vdc_crl = $20F3). +sgx_crh = $F8:2004 ; SGX shadow (vdc_crh = $20F4). +core_1stbank = $F8:2005 ; 1st bank of library code. +sgx_sr = $F8:2006 ; SGX shadow (vdc_sr = $20F6). +sgx_reg = $F8:2007 ; SGX shadow (vdc_reg = $20F7). -core_zp1st = $2008 ; 1st free user address. +core_zp1st = $F8:2008 ; 1st free user address. .else SUPPORT_SGX -_temp = $2000 ; Use within any ASM routine. -_bank = $2002 ; Use within any ASM routine. -core_1stbank = $2003 ; 1st bank of engine code. +_temp = $F8:2000 ; Use within any ASM routine. +_bank = $F8:2002 ; Use within any ASM routine. +core_1stbank = $F8:2003 ; 1st bank of engine code. -core_zp1st = $2004 ; 1st free user address. +core_zp1st = $F8:2004 ; 1st free user address. .endif SUPPORT_SGX @@ -120,9 +120,9 @@ core_zp1st = $2004 ; 1st free user address. ; .if USING_PSGDRIVER -core_ram1st = $2680 +core_ram1st = $F8:2680 .else -core_ram1st = $22D0 +core_ram1st = $F8:22D0 .endif USING_PSGDRIVER .bss From 7f519e7f2f18a5e5e2800a0ff89a5234f45338c8 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Mon, 18 Dec 2023 11:42:52 -0500 Subject: [PATCH 07/40] Fix some typos in vdc.asm --- examples/asm/elmer/include/vdc.asm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/asm/elmer/include/vdc.asm b/examples/asm/elmer/include/vdc.asm index d186a27c..e2eb22f0 100644 --- a/examples/asm/elmer/include/vdc.asm +++ b/examples/asm/elmer/include/vdc.asm @@ -587,18 +587,18 @@ init_240x208 .proc call clear_vram_sgx .endif - lda #<.mode_240x224 ; Disable BKG & SPR layers but + lda #<.mode_240x208 ; Disable BKG & SPR layers but sta.l <_bp ; enable RCR & VBLANK IRQ. - lda #>.mode_240x224 + lda #>.mode_240x208 sta.h <_bp .if SUPPORT_SGX call sgx_detect ; Are we really on an SGX? beq !+ - ldy #^.mode_240x224 ; Set SGX 1st, with no VBL. + ldy #^.mode_240x208 ; Set SGX 1st, with no VBL. call set_mode_sgx .endif -!: ldy #^.mode_240x224 ; Set VDC 2nd, VBL allowed. +!: ldy #^.mode_240x208 ; Set VDC 2nd, VBL allowed. call set_mode_vdc call wait_vsync ; Wait for the next VBLANK. @@ -607,7 +607,7 @@ init_240x208 .proc ; A reduced 240x208 screen to save VRAM. -.mode_240x224: db $80 ; VCE Control Register. +.mode_240x208: db $80 ; VCE Control Register. db VCE_CR_5MHz ; Video Clock db VDC_MWR ; Memory-access Width Register From 00dfa3a15fad32fd55732005317621170bfad09e Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Mon, 18 Dec 2023 11:55:17 -0500 Subject: [PATCH 08/40] Add rom-bare-mwrtest example to show how resolution and MWR effect VRAM r/w speed. --- examples/asm/elmer/Makefile | 2 +- examples/asm/elmer/README.md | 3 + examples/asm/elmer/rom-bare-mwrtest/Makefile | 11 + .../asm/elmer/rom-bare-mwrtest/mwrtest.asm | 1029 +++++++++++++++++ 4 files changed, 1044 insertions(+), 1 deletion(-) create mode 100644 examples/asm/elmer/rom-bare-mwrtest/Makefile create mode 100644 examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm diff --git a/examples/asm/elmer/Makefile b/examples/asm/elmer/Makefile index af925c20..73e40439 100644 --- a/examples/asm/elmer/Makefile +++ b/examples/asm/elmer/Makefile @@ -2,7 +2,7 @@ # PREREQS = ipl-scd -SUBDIRS = rom-bare-buftest rom-bare-rcrtest rom-bare-tiatest \ +SUBDIRS = rom-bare-buftest rom-bare-mwrtest rom-bare-rcrtest rom-bare-tiatest \ rom-core-hello rom-core-okitest \ scd-bios-hello scd-bios-hello-error \ cd-core-1stage cd-core-2stage scd-core-1stage scd-core-1stage-error \ diff --git a/examples/asm/elmer/README.md b/examples/asm/elmer/README.md index ca430f15..b8e1424d 100644 --- a/examples/asm/elmer/README.md +++ b/examples/asm/elmer/README.md @@ -17,6 +17,9 @@ This directory contains a number of example programs and utilities for developin * rom-bare-buftest - A HuCARD ROM to compare a PC Engine emulator's buffered VDC reading with real PC Engine console hardware. +* rom-bare-mwrtest + - A HuCARD ROM to compare a PC Engine emulator's VDC VRAM read/write timings with real PC Engine console hardware. + * rom-bare-rcrtest - A HuCARD ROM to compare a PC Engine emulator's RCR interrupt handling with real PC Engine console hardware. diff --git a/examples/asm/elmer/rom-bare-mwrtest/Makefile b/examples/asm/elmer/rom-bare-mwrtest/Makefile new file mode 100644 index 00000000..482ac3d7 --- /dev/null +++ b/examples/asm/elmer/rom-bare-mwrtest/Makefile @@ -0,0 +1,11 @@ +all: mwrtest.pce + +include ../Make_ex.inc + +AFLAGS ?= -newproc -strip -m -l 2 -S + +SRC_INC = pceas.inc pcengine.inc joypad.asm tty.asm vce.asm vdc.asm +SRC_OVL = mwrtest.asm + +mwrtest.pce: $(SRC_OVL) $(SRC_INC) + $(AS) $(AFLAGS) -raw mwrtest.asm diff --git a/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm b/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm new file mode 100644 index 00000000..4890c668 --- /dev/null +++ b/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm @@ -0,0 +1,1029 @@ +; *************************************************************************** +; *************************************************************************** +; +; mwrtest.asm +; +; VDC timing test HuCARD example using the basic HuCARD startup library code. +; +; Copyright John Brandwood 2023. +; +; Distributed under the Boost Software License, Version 1.0. +; (See accompanying file LICENSE_1_0.txt or copy at +; http://www.boost.org/LICENSE_1_0.txt) +; +; *************************************************************************** +; *************************************************************************** +; +; The purpose of this example is to compare the accuracy of emulation with +; real console hardware. +; +; This checks how many CPU cycles (on average) it takes to execute different +; code sequences that read/write to VRAM, in both burst mode and also if the +; the background is enabled, with different MWR access-slot settings, and if +; during VBLANK or not. +; +; Because of the VDC's round-robin method of VDC memory access, and its need +; to read background data during the line's display, the CPU is subject to a +; lot of delays that aren't taken into account by the documented cycle times +; in the HuC6280 manual. +; +; Sprites are disabled during the tests to avoid any access slot contention +; between the CPU and the loading of SPR data during hblank. +; +; *************************************************************************** +; *************************************************************************** +; +; The PC Engine's memory map is set to ... +; +; MPR0 = bank $FF : PCE hardware +; MPR1 = bank $F8 : PCE RAM with Stack & ZP +; MPR2 = bank $00 : HuCARD ROM +; MPR3 = bank $01 : HuCARD ROM +; MPR4 = bank $02 : HuCARD ROM +; MPR5 = bank $03 : HuCARD ROM +; MPR6 = bank $04 : HuCARD ROM +; MPR7 = bank $00 : HuCARD ROM with the startup code and IRQ vectors. +; +; *************************************************************************** +; *************************************************************************** + + + ; + ; Create some equates for a very generic VRAM layout, with a + ; 64*32 BAT, followed by the SAT, then followed by the tiles + ; for the ASCII character set. + ; + ; This uses the first 8KBytes of VRAM ($0000-$0FFF). + ; + +BAT_LINE = 64 +BAT_SIZE = 64 * 32 +SAT_ADDR = BAT_SIZE ; SAT takes 16 tiles of VRAM. +CHR_ZERO = BAT_SIZE / 16 ; 1st tile # after the BAT. +CHR_0x10 = CHR_ZERO + 16 ; 1st tile # after the SAT. +CHR_0x20 = CHR_ZERO + 32 ; ASCII ' ' CHR tile #. + + .list + .mlist + + include "bare-startup.asm" ; No "CORE(not TM)" library! + + include "common.asm" ; Common helpers. + include "vdc.asm" ; Useful VDC routines. + include "font.asm" ; Useful font routines. + include "joypad.asm" ; Joypad routines. + include "tty.asm" ; Useful TTY print routines. + + + +; *************************************************************************** +; *************************************************************************** +; +; Constants and Variables. +; + +test_buff = $F8:2800 ; 6KB area to run test code. + +WAIT_LINES = 16 ; #lines to wait for test. + + .zp + +screen_rez ds 1 ; 0=256, 1=352, 2=512. +want_delay ds 2 ; Minimum 0, Maximum 500. +delay_count ds 2 ; Minimum 8, Maximum 510. + +which_rcr ds 1 + +which_test ds 1 + +test_flag ds 1 ; +test_addr ds 2 ; Addr of test sequence. +test_size ds 1 ; Addr of test sequence. + +test_rti ds 2 ; Addr of test result. + +burst_count: ds 2 ; #times that the test code was +burst_extra: ds 1 ; repeated and #extra bytes. + +shown_count: ds 2 ; #times that the test code was +shown_extra: ds 1 ; repeated and #extra bytes. + + .bss + +delay_code ds 256 ; Configurable delay code. + + + +; *************************************************************************** +; *************************************************************************** +; +; bare_main - This is executed after startup library initialization. +; + + .code + + ; Reset the IRQ hooks when switching resolution. + +bare_main: jsr bare_clr_hooks + + ; Turn the display off and initialize the screen mode. + ; + ; N.B. BG & SPR are disabled, so the VDC is in "burst mode". + + jsr init_vdc_rez ; Initialize VDC & VRAM. + + ; Upload the font to VRAM. + + stz <_di + 0 ; Destination VRAM address. + lda #>(CHR_0x10 * 16) + sta <_di + 1 + + lda #$FF ; Put font in colors 4-7, + sta <_al ; so bitplane 2 = $FF and + stz <_ah ; bitplane 3 = $00. + + lda #16 + 96 ; 16 graphics + 96 ASCII. + sta <_bl + + lda #my_font + sta <_bp + 1 + ldy #^my_font + + call dropfnt8x8_vdc ; Upload font to VRAM. + + ; Upload the palette data to the VCE. + + stz <_al ; Start at palette 0 (BG). + lda #3 ; Copy 3 palettes of 16 colors. + sta <_ah + lda #screen_pal + sta <_bp + 1 + ldy #^screen_pal + call load_palettes ; Add to the palette queue. + + call xfer_palettes ; Transfer queue to VCE now. + + ; Identify this program. + + PRINTF "\e<\eX1\eY1\eP0**PC ENGINE VDC MWR VRAM R/W**\eP1\eX3\eY3\x1C\x1D\eP0:Change Test\eP1 \x1E\x1F\eP0:VDC Mode\eP2\n\n" + + ldy $7000 ; 5 + st0 #VDC_MAWR ; 5 + st1 #<$7000 ; 5 + st2 #>$7000 ; 5 + + cli ; 2 Allow next RCR. + + ; Make sure that the next line's RCR interrupt is stable. + +.wait: ds 256, $EA ; 256 NOP instructions. + + jmp .wait ; Shouldn't get here! + + + +; *************************************************************************** +; *************************************************************************** +; +; hsync_proc2 - Wait (455 + want_delay) cycles before starting a TIA. +; +; It takes 23 cycles to get to "hsync_proc2" (including IRQ response). +; + +hsync_proc2: pla ; 4 Throw away the flags and + pla ; 4 the return address to + pla ; 4 hsync_proc1 + + bit VDC_SR ; 6 Acknowledge IRQ. + + ; It takes 41 cycles from RCR to get to here ... + + jsr set_next_rcr ; 75 = 7 + 68 + + st0 #VDC_VWR ; 5 + lda #VDC_VWR ; 2 + sta #cycles. + rol.h tia_delay, x + + clc ; Add the delay_count that + lda.l tia_delay, x ; we used. + adc.l delay_count + sta.l tia_delay, x + lda.h tia_delay, x + adc.h delay_count + sta.h tia_delay, x + + sec ; Subtract 8, i.e. + lda.l tia_delay, x ; (want_delay - delay_count) + sbc.l #8 + sta.l tia_delay, x + lda.h tia_delay, x + sbc.h #8 + sta.h tia_delay, x + + sec ; Subtract the #cycles total + lda.l #455 * WAIT_LINES ; from the time between the + sbc.l tia_delay, x ; two RCR interrupts to get + sta.l tia_delay, x ; the time taken for the TIA. + lda.h #455 * WAIT_LINES + sbc.h tia_delay, x + sta.h tia_delay, x + + .endif + + rmb0 .BAT_SIZE ; Size of BAT in words. + sta <_bl + + call clear_vram_vdc ; Clear VRAM. + .if SUPPORT_SGX + call clear_vram_sgx + .endif + + lda #<.mode_240x192 ; Disable BKG & SPR layers but + sta.l <_bp ; enable RCR & VBLANK IRQ. + lda #>.mode_240x192 + sta.h <_bp + + .if SUPPORT_SGX + call sgx_detect ; Are we really on an SGX? + beq !+ + ldy #^.mode_240x192 ; Set SGX 1st, with no VBL. + call set_mode_sgx + .endif +!: ldy #^.mode_240x192 ; Set VDC 2nd, VBL allowed. + call set_mode_vdc + + call wait_vsync ; Wait for the next VBLANK. + + leave ; All done, phew! + + ; A reduced 240x192 screen to run the tests during VBLANK. + +.mode_240x192: db $80 ; VCE Control Register. + db VCE_CR_5MHz ; Video Clock + + db VDC_MWR ; Memory-access Width Register + dw VDC_MWR_64x32 + VDC_MWR_1CYCLE + db VDC_HSR ; Horizontal Sync Register + dw VDC_HSR_240 + db VDC_HDR ; Horizontal Display Register + dw VDC_HDR_240 + db VDC_VPR ; Vertical Sync Register + dw VDC_VPR_192 + db VDC_VDW ; Vertical Display Register + dw VDC_VDW_192 + db VDC_VCR ; Vertical Display END position Register + dw VDC_VCR_192 + db VDC_DCR ; DMA Control Register + dw $0010 ; Enable automatic VRAM->SATB + db VDC_DVSSR ; VRAM->SATB address $0400 + dw $0800 + db VDC_BXR ; Background X-Scroll Register + dw $0008 + db VDC_BYR ; Background Y-Scroll Register + dw $0000 + db VDC_RCR ; Raster Counter Register + dw $0000 ; Never occurs! + db VDC_CR ; Control Register + dw $000C ; Enable VSYNC & RCR IRQ + db 0 + + .endp + + + +; *************************************************************************** +; *************************************************************************** +; +; init_336x224 - R-Type USA's 336x224 screen with slow 2-cycle MWR. +; + +init_336x224 .proc + +.BAT_SIZE = 64 * 32 +.SAT_ADDR = .BAT_SIZE ; SAT takes 16 tiles of VRAM. +.CHR_ZERO = .BAT_SIZE / 16 ; 1st tile # after the BAT. +.CHR_0x10 = .CHR_ZERO + 16 ; 1st tile # after the SAT. +.CHR_0x20 = .CHR_ZERO + 32 ; ASCII ' ' CHR tile #. + + call clear_vce ; Clear all palettes. + + lda.l #.CHR_0x20 ; Tile # of ' ' CHR. + sta.l <_ax + lda.h #.CHR_0x20 + sta.h <_ax + + lda #>.BAT_SIZE ; Size of BAT in words. + sta <_bl + + call clear_vram_vdc ; Clear VRAM. + .if SUPPORT_SGX + call clear_vram_sgx + .endif + + lda #<.mode_336x224 ; Disable BKG & SPR layers but + sta.l <_bp ; enable RCR & VBLANK IRQ. + lda #>.mode_336x224 + sta.h <_bp + + .if SUPPORT_SGX + call sgx_detect ; Are we really on an SGX? + beq !+ + ldy #^.mode_336x224 ; Set SGX 1st, with no VBL. + call set_mode_sgx + .endif +!: ldy #^.mode_336x224 ; Set VDC 2nd, VBL allowed. + call set_mode_vdc + + call wait_vsync ; Wait for the next VBLANK. + + leave ; All done, phew! + + ; R-Type USA's 336x224 screen with slow 2-cycle MWR. + +.mode_336x224: db $80 ; VCE Control Register. + db VCE_CR_7MHz + 4 ; Video Clock + Artifact Reduction + + db VDC_MWR ; Memory-access Width Register + dw VDC_MWR_64x32 + VDC_MWR_2CYCLE + db VDC_HSR ; Horizontal Sync Register + dw VDC_HSR_336 + db VDC_HDR ; Horizontal Display Register + dw VDC_HDR_336 + db VDC_VPR ; Vertical Sync Register + dw VDC_VPR_224 + db VDC_VDW ; Vertical Display Register + dw VDC_VDW_224 + db VDC_VCR ; Vertical Display END position Register + dw VDC_VCR_224 + db VDC_DCR ; DMA Control Register + dw $0010 ; Enable automatic VRAM->SATB + db VDC_DVSSR ; VRAM->SATB address $0800 + dw $0800 + db VDC_BXR ; Background X-Scroll Register + dw $0008 + db VDC_BYR ; Background Y-Scroll Register + dw $0000 + db VDC_RCR ; Raster Counter Register + dw $0000 ; Never occurs! + db VDC_CR ; Control Register + dw $000C ; Enable VSYNC & RCR IRQ + db 0 + + .endp + + + +; *************************************************************************** +; *************************************************************************** +; +; init_vdc_rez - Switch between screen resolutions. +; + + .code + +init_vdc_rez: lda Date: Tue, 19 Dec 2023 15:55:11 -0500 Subject: [PATCH 09/40] Add some CPU-delayed read and write tests to rom-bare-mwrtest. --- .../asm/elmer/rom-bare-mwrtest/mwrtest.asm | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm b/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm index 4890c668..ac97e617 100644 --- a/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm +++ b/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm @@ -864,6 +864,22 @@ table_slo: dwl test_name1 dwl test_name9 dwl test_name10 dwl test_name11 + dwl test_name12 + dwl test_name13 + dwl test_name14 + dwl test_name15 + dwl test_name16 + dwl test_name17 + dwl test_name18 + dwl test_name19 + dwl test_name20 + dwl test_name21 + dwl test_name22 + dwl test_name23 + dwl test_name24 + dwl test_name25 + dwl test_name26 + dwl test_name27 table_shi: dwh test_name1 dwh test_name2 @@ -876,6 +892,22 @@ table_shi: dwh test_name1 dwh test_name9 dwh test_name10 dwh test_name11 + dwh test_name12 + dwh test_name13 + dwh test_name14 + dwh test_name15 + dwh test_name16 + dwh test_name17 + dwh test_name18 + dwh test_name19 + dwh test_name20 + dwh test_name21 + dwh test_name22 + dwh test_name23 + dwh test_name24 + dwh test_name25 + dwh test_name26 + dwh test_name27 table_alo: dwl test_code1 dwl test_code2 @@ -888,6 +920,22 @@ table_alo: dwl test_code1 dwl test_code9 dwl test_code10 dwl test_code11 + dwl test_code12 + dwl test_code13 + dwl test_code14 + dwl test_code15 + dwl test_code16 + dwl test_code17 + dwl test_code18 + dwl test_code19 + dwl test_code20 + dwl test_code21 + dwl test_code22 + dwl test_code23 + dwl test_code24 + dwl test_code25 + dwl test_code26 + dwl test_code27 table_ahi: dwh test_code1 dwh test_code2 @@ -900,6 +948,22 @@ table_ahi: dwh test_code1 dwh test_code9 dwh test_code10 dwh test_code11 + dwh test_code12 + dwh test_code13 + dwh test_code14 + dwh test_code15 + dwh test_code16 + dwh test_code17 + dwh test_code18 + dwh test_code19 + dwh test_code20 + dwh test_code21 + dwh test_code22 + dwh test_code23 + dwh test_code24 + dwh test_code25 + dwh test_code26 + dwh test_code27 table_len: db test_size1 db test_size2 @@ -912,6 +976,22 @@ table_len: db test_size1 db test_size9 db test_size10 db test_size11 + db test_size12 + db test_size13 + db test_size14 + db test_size15 + db test_size16 + db test_size17 + db test_size18 + db test_size19 + db test_size20 + db test_size21 + db test_size22 + db test_size23 + db test_size24 + db test_size25 + db test_size26 + db test_size27 ; 1st test - Baseline, should take 5 cycles. @@ -988,6 +1068,138 @@ test_code11: st0 #VDC_MARR st0 #VDC_VRR test_size11 = * - test_code11 + ; 12th test - Can you sync to VDC clock? + +test_name12: STRING " nop\n st2 #$00\n" +test_code12: nop + st2 #0 +test_size12 = * - test_code12 + + ; 13th test - Can you sync to VDC clock? + +test_name13: STRING " sxy\n st2 #$00\n" +test_code13: sxy + st2 #0 +test_size13 = * - test_code13 + + ; 14th test - Can you sync to VDC clock? + +test_name14: STRING " nop\n nop\n st2 #$00\n" +test_code14: nop + nop + st2 #0 +test_size14 = * - test_code14 + + ; 15th test - Can you sync to VDC clock? + +test_name15: STRING " nop\n sxy\n st2 #$00\n" +test_code15: nop + sxy + st2 #0 +test_size15 = * - test_code15 + + ; 16th test - Can you sync to VDC clock? + +test_name16: STRING " nop\n nop\n nop\n st2 #$00\n" +test_code16: nop + nop + nop + st2 #0 +test_size16 = * - test_code16 + + ; 17th test - Can you sync to VDC clock? + +test_name17: STRING " nop\n nop\n sxy\n st2 #$00\n" +test_code17: nop + nop + sxy + st2 #0 +test_size17 = * - test_code17 + + ; 18th test - Can you sync to VDC clock? + +test_name18: STRING " nop\n sxy\n sxy\n st2 #$00\n" +test_code18: nop + sxy + sxy + st2 #0 +test_size18 = * - test_code18 + + ; 19th test - Can you sync to VDC clock? + +test_name19: STRING " sxy\n sxy\n sxy\n st2 #$00\n" +test_code19: sxy + sxy + sxy + st2 #0 +test_size19 = * - test_code19 + + ; 20th test - Can you sync to VDC clock? + +test_name20: STRING " nop\n lda $0003\n" +test_code20: nop + lda VDC_DH +test_size20 = * - test_code20 + + ; 21st test - Can you sync to VDC clock? + +test_name21: STRING " sxy\n lda $0003\n" +test_code21: sxy + lda VDC_DH +test_size21 = * - test_code21 + + ; 22nd test - Can you sync to VDC clock? + +test_name22: STRING " nop\n nop\n lda $0003\n" +test_code22: nop + nop + lda VDC_DH +test_size22 = * - test_code22 + + ; 23rd test - Can you sync to VDC clock? + +test_name23: STRING " nop\n sxy\n lda $0003\n" +test_code23: nop + sxy + lda VDC_DH +test_size23 = * - test_code23 + + ; 24th test - Can you sync to VDC clock? + +test_name24: STRING " nop\n nop\n nop\n lda $0003\n" +test_code24: nop + nop + nop + lda VDC_DH +test_size24 = * - test_code24 + + ; 25th test - Can you sync to VDC clock? + +test_name25: STRING " nop\n nop\n sxy\n lda $0003\n" +test_code25: nop + nop + sxy + lda VDC_DH +test_size25 = * - test_code25 + + ; 26th test - Can you sync to VDC clock? + +test_name26: STRING " nop\n sxy\n sxy\n lda $0003\n" +test_code26: nop + sxy + sxy + lda VDC_DH +test_size26 = * - test_code26 + + ; 27th test - Can you sync to VDC clock? + +test_name27: STRING " sxy\n sxy\n sxy\n lda $0003\n" +test_code27: sxy + sxy + sxy + lda VDC_DH +test_size27 = * - test_code27 + ; *************************************************************************** From 71e608c6d2707b5b8361d6f1b79730c4825805cc Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Tue, 19 Dec 2023 15:56:48 -0500 Subject: [PATCH 10/40] Split VDC VRAM clearing writes to take advantage of the VDC's memory-slot behavior. --- examples/asm/elmer/include/vdc.asm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/asm/elmer/include/vdc.asm b/examples/asm/elmer/include/vdc.asm index e2eb22f0..6beeee20 100644 --- a/examples/asm/elmer/include/vdc.asm +++ b/examples/asm/elmer/include/vdc.asm @@ -120,9 +120,9 @@ clear_vram_x: bsr clear_bat_x ; Clear the BAT. ; cly ; Clear the rest of VRAM. .clr_loop: pha stz VDC_DL, x -.clr_pair: stz VDC_DH, x +.clr_pair: stz VDC_DH, x ; Seperate writes to minimize + dey ; VDC MWR penalty. stz VDC_DH, x - dey bne .clr_pair pla dec a @@ -177,9 +177,9 @@ clear_bat_x: stz <_di + 0 ; Set VDC or SGX destination lda <_ax + 0 sta VDC_DL, x lda <_ax + 1 -.bat_pair: sta VDC_DH, x +.bat_pair: sta VDC_DH, x ; Seperate writes to minimize + dey ; VDC MWR penalty. sta VDC_DH, x - dey bne .bat_pair pla From a06a08b5683db8aafedfb9be05eda2f7da89be1d Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Sat, 23 Dec 2023 13:01:24 -0500 Subject: [PATCH 11/40] Add more VDC test code sequences to rom-bare-mwrtest. --- .../asm/elmer/rom-bare-mwrtest/mwrtest.asm | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm b/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm index ac97e617..ffa6dc78 100644 --- a/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm +++ b/examples/asm/elmer/rom-bare-mwrtest/mwrtest.asm @@ -880,6 +880,12 @@ table_slo: dwl test_name1 dwl test_name25 dwl test_name26 dwl test_name27 + dwl test_name28 + dwl test_name29 + dwl test_name30 + dwl test_name31 + dwl test_name32 + dwl test_name33 table_shi: dwh test_name1 dwh test_name2 @@ -908,6 +914,12 @@ table_shi: dwh test_name1 dwh test_name25 dwh test_name26 dwh test_name27 + dwh test_name28 + dwh test_name29 + dwh test_name30 + dwh test_name31 + dwh test_name32 + dwh test_name33 table_alo: dwl test_code1 dwl test_code2 @@ -936,6 +948,12 @@ table_alo: dwl test_code1 dwl test_code25 dwl test_code26 dwl test_code27 + dwl test_code28 + dwl test_code29 + dwl test_code30 + dwl test_code31 + dwl test_code32 + dwl test_code33 table_ahi: dwh test_code1 dwh test_code2 @@ -964,6 +982,12 @@ table_ahi: dwh test_code1 dwh test_code25 dwh test_code26 dwh test_code27 + dwh test_code28 + dwh test_code29 + dwh test_code30 + dwh test_code31 + dwh test_code32 + dwh test_code33 table_len: db test_size1 db test_size2 @@ -992,6 +1016,12 @@ table_len: db test_size1 db test_size25 db test_size26 db test_size27 + db test_size28 + db test_size29 + db test_size30 + db test_size31 + db test_size32 + db test_size33 ; 1st test - Baseline, should take 5 cycles. @@ -1200,6 +1230,59 @@ test_code27: sxy lda VDC_DH test_size27 = * - test_code27 + ; 28th test - Can you change VDC_AR during a VRAM write? + +test_name28: STRING " st2 #$00\n st0 #19\n st0 #2\n" +test_code28: st2 #$00 + st0 #VDC_DVSSR + st0 #VDC_VWR +test_size28 = * - test_code28 + + ; 29th test - Can you change VDC_AR during a VRAM write? + +test_name29: STRING " st2 #$00\n st0 #19\n st1 #$00\n st0 #2\n" +test_code29: st2 #$00 + st0 #VDC_DVSSR + st1 #$00 + st0 #VDC_VWR +test_size29 = * - test_code29 + + ; 30th test - Can you change VDC_MAWR during a VRAM write? + +test_name30: STRING " st2 #$00\n st0 #$00\n st1 #$00\n st0 #$02\n" +test_code30: st2 #$00 + st0 #VDC_MAWR + st1 #$00 + st0 #VDC_VWR +test_size30 = * - test_code30 + + ; 31st test - Can you change VDC_MAWR during a VRAM write? + +test_name31: STRING " st2 #$00\n st0 #$00\n st2 #$70\n st0 #$02\n" +test_code31: st2 #$00 + st0 #VDC_MAWR + st2 #$70 + st0 #VDC_VWR +test_size31 = * - test_code31 + + ; 32nd test - Can you change VDC_MARR during a VRAM write? + +test_name32: STRING " st2 #$00\n st0 #$01\n st1 #$00\n st0 #$02\n" +test_code32: st2 #$00 + st0 #VDC_MARR + st1 #$00 + st0 #VDC_VWR +test_size32 = * - test_code32 + + ; 33rd test - Can you change VDC_MARR during a VRAM write? + +test_name33: STRING " st2 #$00\n st0 #$01\n st2 #$70\n st0 #$02\n" +test_code33: st2 #$00 + st0 #VDC_MARR + st2 #$70 + st0 #VDC_VWR +test_size33 = * - test_code33 + ; *************************************************************************** From a1515a53693754382d73f05ff7f0ef2961025eb8 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Sat, 23 Dec 2023 13:02:44 -0500 Subject: [PATCH 12/40] Allow selection of # sprites shown in rom-bare-tiatest. --- .../asm/elmer/rom-bare-tiatest/tiatest.asm | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/examples/asm/elmer/rom-bare-tiatest/tiatest.asm b/examples/asm/elmer/rom-bare-tiatest/tiatest.asm index 13251752..bfe79b8e 100644 --- a/examples/asm/elmer/rom-bare-tiatest/tiatest.asm +++ b/examples/asm/elmer/rom-bare-tiatest/tiatest.asm @@ -5,7 +5,7 @@ ; ; TIA tester HuCARD example of using the basic HuCARD startup library code. ; -; Copyright John Brandwood 2022. +; Copyright John Brandwood 2022-2023. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at @@ -89,6 +89,7 @@ WAIT_LINES = 22 ; #lines to wait for TIA. screen_rez ds 1 ; 0=256, 1=352, 2=512. want_delay ds 2 ; Minimum 0, Maximum 500. delay_count ds 2 ; Minimum 8, Maximum 510. +num_sprites ds 1 ; Minimum 1, Maximum 16. move_spr ds 1 ; @@ -227,6 +228,11 @@ bare_main: jsr bare_clr_hooks bit VDC_SR plp + ; Initialize the number of sprites on the line. + + lda #16 + sta Date: Sat, 23 Dec 2023 13:04:04 -0500 Subject: [PATCH 13/40] Attempt to improve description of TIA delays for VRAM_XFER_SIZE equate in vdc.asm --- examples/asm/elmer/include/vdc.asm | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/asm/elmer/include/vdc.asm b/examples/asm/elmer/include/vdc.asm index 6beeee20..c72fd924 100644 --- a/examples/asm/elmer/include/vdc.asm +++ b/examples/asm/elmer/include/vdc.asm @@ -26,13 +26,19 @@ ; ; Choose how much to transfer to VRAM in a single chunk, normally 16-bytes. ; -; 32-byte TIA takes 270/364 cycles in 5MHz, 242/312 cycles in 7MHz. (8.44 cycles-per-byte best-case at 5MHz.) -; 24-byte TIA takes 210/298 cycles in 5MHz, 186/256 cycles in 7MHz. (8.75 cycles-per-byte best-case at 5MHz.) -; 16-byte TIA takes 142/234 cycles in 5MHz, 128/200 cycles in 7MHz. (8.88 cycles-per-byte best-case at 5MHz.) +; The cycle timings for a TIA-to-VRAM depend upon how the VDC's MWR CPU slots +; line up to the CPU's writes, and how long the VDC has to halt the CPU while +; it fetches the next scanline's sprite data. +; +; These cycle timings are for 0 sprites (best) and 16 sprites (worst) ... +; +; 32-byte TIA takes 270..364 cycles in 5MHz, 242..312 cycles in 7MHz. (8.44 cycles-per-byte best-case at 5MHz.) +; 24-byte TIA takes 210..298 cycles in 5MHz, 186..256 cycles in 7MHz. (8.75 cycles-per-byte best-case at 5MHz.) +; 16-byte TIA takes 142..234 cycles in 5MHz, 128..200 cycles in 7MHz. (8.88 cycles-per-byte best-case at 5MHz.) ; ; If a user wishes to be able to put RCR interrupts one-line-after-another, ; then it is only safe to use 32-byte chunks if there are no TIMER or IRQ2 -; interrupts ... which is almost-impossible to rely on in library code. +; interrupts ... which is almost-impossible to rely on in library code! ; .ifndef VRAM_XFER_SIZE From 555975235f70252995f007654c2a80131c9da2ae Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Sat, 23 Dec 2023 13:08:07 -0500 Subject: [PATCH 14/40] Remove IO_PORT test for IFU in fastcd example, document that TEDPro does not set IO_PORT bit. --- examples/asm/elmer/include/cdrom.asm | 4 +++- examples/asm/elmer/scd-core-fastcd/fastcd.asm | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/asm/elmer/include/cdrom.asm b/examples/asm/elmer/include/cdrom.asm index 652afd70..02c62550 100644 --- a/examples/asm/elmer/include/cdrom.asm +++ b/examples/asm/elmer/include/cdrom.asm @@ -1684,7 +1684,9 @@ cdr_init_disc .proc ; ; Check that the CD-ROM IFU is present. ; ; -; ; N.B. The System Card does not check this! +; ; N.B. The System Card does not check the IO-PORT! +; ; +; ; N.B. The Turbo Everdrive Pro does not emulate this! ; ; ldy #CDERR_NO_CDIFU ; lda IO_PORT diff --git a/examples/asm/elmer/scd-core-fastcd/fastcd.asm b/examples/asm/elmer/scd-core-fastcd/fastcd.asm index 91482f7c..857de09d 100644 --- a/examples/asm/elmer/scd-core-fastcd/fastcd.asm +++ b/examples/asm/elmer/scd-core-fastcd/fastcd.asm @@ -243,19 +243,26 @@ core_main: ; Turn the display off and initialize the screen mode. test_init_disc .proc + ; N.B. The System Card does not check the IO-PORT! + ; + ; N.B. The Turbo Everdrive Pro does not emulate this! + lda IO_PORT bpl !+ - PRINTF "No CD-ROM Interface Unit!\n\n" +; PRINTF "No CD-ROM Interface Unit!\n\n" +; +; ldy #CDERR_NO_CDIFU +; jmp .finished - ldy #CDERR_NO_CDIFU - jmp .finished + PRINTF "CD-ROM IFU not detected\n\n" + bra .reset -!: PRINTF "CD-ROM Interface present\n" +!: PRINTF "CD-ROM IFU present\n" ; Reset the CD-ROM drive. - call cdr_reset +.reset: call cdr_reset ; Wait for the CD-ROM drive to release SCSI_BSY. From 60686911639a84e26f18044718cd66431f5f4eab Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Sat, 23 Dec 2023 13:46:26 -0500 Subject: [PATCH 15/40] Add missing PCEAS info about <,>,^ and linear() to whats.new --- whats.new | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/whats.new b/whats.new index dc156735..f99105a7 100644 --- a/whats.new +++ b/whats.new @@ -59,7 +59,8 @@ New in version 4.00: - Allow .EQU and .RSSET values to use "$xx:xxxx" to specify bank and addr. - Fix zero-byte procedure relocation when at the end of a bank. - Fix operator precedence of unary "<" and ">". - +- Add "linear(label_name)" operator to get the label's 32-bit address-offset + from the start of the ROM/SCD/CD memory. New in version 3.99: @@ -194,6 +195,7 @@ New in version 3.99: - Add "-pad" to PCEAS to pad out HuCard ROM images to next power-of-2 size. - Change HuC to use "-raw" and "-pad" options to avoid 384KB HuCard problem. - Add warning whenever a 384KB HuCard image is created to highlight problem. +- Allow unary <,>,^ in PCEAS as synonyms for low(),high(),bank(). New in version 3.21: From a207701c98773b564c35de0c02ee3050012fd4b5 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Sat, 23 Dec 2023 15:03:59 -0500 Subject: [PATCH 16/40] Add 17th and 18th sprites on the line to tiatest.asm --- .../asm/elmer/rom-bare-tiatest/tiatest.asm | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/asm/elmer/rom-bare-tiatest/tiatest.asm b/examples/asm/elmer/rom-bare-tiatest/tiatest.asm index bfe79b8e..d263bf57 100644 --- a/examples/asm/elmer/rom-bare-tiatest/tiatest.asm +++ b/examples/asm/elmer/rom-bare-tiatest/tiatest.asm @@ -101,7 +101,7 @@ tia_delay ds 2 * 2 delay_code ds 256 ; Modified delay before TIA. ram_tia ds 6144 ; TIA + lots of delay after. -ram_sat ds 128 +ram_sat ds 18*8 ; SAT for 18 sprites. @@ -220,7 +220,7 @@ bare_main: jsr bare_clr_hooks lda #$60 ; RTS sta ram_tia + 6143 - tii hex_sat, ram_sat, 16 * 8 + tii hex_sat, ram_sat, 18 * 8 stz > 5 dw %0011000010000001 ; CGY=3, CGX=0 (16x64). + + dw 64 + 80 - 4 ; '0' sprite. + dw 32 + $C0 + dw $1000 >> 5 + dw %0011000010000000 ; CGY=3, CGX=0 (16x64). + + dw 64 + 80 + 4 ; '1' sprite. + dw 32 + $C8 + dw $1040 >> 5 + dw %0011000010000001 ; CGY=3, CGX=0 (16x64). From 2c8c8afcbca4e91c465f45075cc2c2d4fc257897 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Sun, 24 Dec 2023 12:19:38 -0500 Subject: [PATCH 17/40] Recheck and confirm #sprites-per-line and add SAT-entries-searched info to pcengine-notes.txt --- examples/asm/elmer/doc/pcengine-notes.txt | 68 +++++++++++++++-------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/examples/asm/elmer/doc/pcengine-notes.txt b/examples/asm/elmer/doc/pcengine-notes.txt index 3e6947a5..4fd3b659 100644 --- a/examples/asm/elmer/doc/pcengine-notes.txt +++ b/examples/asm/elmer/doc/pcengine-notes.txt @@ -67,7 +67,6 @@ ; the next line's sprites during the blank time after it is done displaying ; the current line's pixel data. ; -; ; VDC @ 5.36MHz -> width = total # chr on line = 42 chr ; VDC @ 7.16MHz -> width = total # chr on line = 56 chr ; VDC @ 10.74MHz -> width = total # chr on line = 85 chr @@ -75,46 +74,67 @@ ; Sprites-per-line shown (@ 1-clk-per-access) = (width - 2 - (hdw + 1)) * 2 ; Sprites-per-line shown (@ 2-clk-per-access) = (width - 2 - (hdw + 1)) ; +; The hdw value also limits the number of sprite SAT entries that the VDC is +; able to scan to find the sprites that are on the next line. +; ; ; VDC @ 5.36MHz, MWR=$x0 (1-clk-per-access) ; -; hds $02 hdw $1F -> 32 chr = 256 pxl -> 16 sprites -; hds $02 hdw $20 -> 33 chr = 264 pxl -> 14 sprites -; hds $02 hdw $21 -> 34 chr = 272 pxl -> 12 sprites +; hds $02 hdw $1C -> 29 chr = 232 pxl -> 59 SAT searched, 16 sprites shown +; hds $02 hdw $1D -> 30 chr = 240 pxl -> 61 SAT searched, 16 sprites shown +; hds $02 hdw $1E -> 31 chr = 248 pxl -> 63 SAT searched, 16 sprites shown +; hds $02 hdw $1F -> 32 chr = 256 pxl -> 64 SAT searched, 16 sprites shown +; hds $02 hdw $20 -> 33 chr = 264 pxl -> 64 SAT searched, 14 sprites shown +; hds $02 hdw $21 -> 34 chr = 272 pxl -> 64 SAT searched, 12 sprites shown ; ; ; VDC @ 7.16MHz, MWR=$x0 (1-clk-per-access) ; -; hds $06 hdw $25 -> 38 chr = 304 pxl -> 16 sprites +; hds $07 hdw $1C -> 29 chr = 232 pxl -> 59 SAT searched, 16 sprites shown +; hds $07 hdw $1D -> 30 chr = 240 pxl -> 61 SAT searched, 16 sprites shown +; hds $07 hdw $1E -> 31 chr = 248 pxl -> 63 SAT searched, 16 sprites shown +; hds $07 hdw $1F -> 32 chr = 256 pxl -> 64 SAT searched, 16 sprites shown +; ... +; hds $06 hdw $25 -> 38 chr = 304 pxl -> 64 SAT searched, 16 sprites shown ; ... -; hds $03 hdw $2B -> 44 chr = 352 pxl -> 16 sprites -; hds $03 hdw $2C -> 45 chr = 360 pxl -> 16 sprites -; hds $03 hdw $2D -> 46 chr = 368 pxl -> 16 sprites -; hds $03 hdw $2E -> 47 chr = 376 pxl -> 14 sprites -; hds $03 hdw $2F -> 48 chr = 384 pxl -> 12 sprites +; hds $03 hdw $2B -> 44 chr = 352 pxl -> 64 SAT searched, 16 sprites shown +; hds $03 hdw $2C -> 45 chr = 360 pxl -> 64 SAT searched, 16 sprites shown +; hds $03 hdw $2D -> 46 chr = 368 pxl -> 64 SAT searched, 16 sprites shown +; hds $03 hdw $2E -> 47 chr = 376 pxl -> 64 SAT searched, 14 sprites shown +; hds $03 hdw $2F -> 48 chr = 384 pxl -> 64 SAT searched, 12 sprites shown ; ; ; VDC @ 7.16MHz, MWR=$xA (2-clk-per-access) ; -; hds $06 hdw $25 -> 38 chr = 304 pxl -> 16 sprites -; hds $05 hdw $26 -> 39 chr = 312 pxl -> 15 sprites -; hds $05 hdw $27 -> 40 chr = 320 pxl -> 14 sprites -; hds $04 hdw $28 -> 41 chr = 328 pxl -> 13 sprites -; hds $04 hdw $29 -> 42 chr = 336 pxl -> 12 sprites -; hds $03 hdw $2A -> 43 chr = 344 pxl -> 11 sprites -; hds $03 hdw $2B -> 44 chr = 352 pxl -> 10 sprites +; hds $07 hdw $1C -> 29 chr = 232 pxl -> 58 SAT searched, 16 sprites shown +; hds $07 hdw $1D -> 30 chr = 240 pxl -> 60 SAT searched, 16 sprites shown +; hds $07 hdw $1E -> 31 chr = 248 pxl -> 62 SAT searched, 16 sprites shown +; hds $07 hdw $1F -> 32 chr = 256 pxl -> 64 SAT searched, 16 sprites shown +; ... +; hds $06 hdw $25 -> 38 chr = 304 pxl -> 64 SAT searched, 16 sprites shown +; hds $05 hdw $26 -> 39 chr = 312 pxl -> 64 SAT searched, 15 sprites shown +; hds $05 hdw $27 -> 40 chr = 320 pxl -> 64 SAT searched, 14 sprites shown +; hds $04 hdw $28 -> 41 chr = 328 pxl -> 64 SAT searched, 13 sprites shown +; hds $04 hdw $29 -> 42 chr = 336 pxl -> 64 SAT searched, 12 sprites shown +; hds $03 hdw $2A -> 43 chr = 344 pxl -> 64 SAT searched, 11 sprites shown +; hds $03 hdw $2B -> 44 chr = 352 pxl -> 64 SAT searched, 10 sprites shown ; ; ; VDC @ 10.74MHz, MWR=$xA (2-clk-per-access) ; -; hds $0B hdw $3B -> 60 chr = 480 pxl -> 16 sprites +; hds $17 hdw $1C -> 29 chr = 232 pxl -> 58 SAT searched, 16 sprites shown +; hds $17 hdw $1D -> 30 chr = 240 pxl -> 60 SAT searched, 16 sprites shown +; hds $17 hdw $1E -> 31 chr = 248 pxl -> 62 SAT searched, 16 sprites shown +; hds $17 hdw $1F -> 32 chr = 256 pxl -> 64 SAT searched, 16 sprites shown +; ... +; hds $0B hdw $3B -> 60 chr = 480 pxl -> 64 SAT searched, 16 sprites shown ; ... -; hds $0B hdw $3F -> 64 chr = 512 pxl -> 16 sprites -; hds $0B hdw $40 -> 65 chr = 520 pxl -> 16 sprites -; hds $0B hdw $41 -> 66 chr = 528 pxl -> 16 sprites -; hds $0B hdw $42 -> 67 chr = 536 pxl -> 16 sprites -; hds $0B hdw $43 -> 68 chr = 544 pxl -> 15 sprites -; hds $0B hdw $44 -> 69 chr = 552 pxl -> 14 sprites +; hds $0B hdw $3F -> 64 chr = 512 pxl -> 64 SAT searched, 16 sprites shown +; hds $0B hdw $40 -> 65 chr = 520 pxl -> 64 SAT searched, 16 sprites shown +; hds $0B hdw $41 -> 66 chr = 528 pxl -> 64 SAT searched, 16 sprites shown +; hds $0B hdw $42 -> 67 chr = 536 pxl -> 64 SAT searched, 16 sprites shown +; hds $0B hdw $43 -> 68 chr = 544 pxl -> 64 SAT searched, 15 sprites shown +; hds $0B hdw $44 -> 69 chr = 552 pxl -> 64 SAT searched, 14 sprites shown ; From 3e178d6cadff303f29cedba9ab53a89c8c6db54c Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Mon, 1 Jan 2024 13:27:17 -0500 Subject: [PATCH 18/40] Protect VDC initialization from stray IRQs, and fix VRAM copy. --- examples/asm/elmer/include/vdc.asm | 182 ++++++++++++++++++++--------- 1 file changed, 126 insertions(+), 56 deletions(-) diff --git a/examples/asm/elmer/include/vdc.asm b/examples/asm/elmer/include/vdc.asm index c72fd924..998c4d52 100644 --- a/examples/asm/elmer/include/vdc.asm +++ b/examples/asm/elmer/include/vdc.asm @@ -286,6 +286,14 @@ set_mode_vdc .proc .done: lda Date: Tue, 9 Jan 2024 15:16:16 -0500 Subject: [PATCH 19/40] Add new optimized-but-still-slow mul_xxx routines to math.asm Update Gulliver HuVIDEO example, the only project using mul_xxx! --- examples/asm/elmer/include/math.asm | 431 ++++++++++++++++-- examples/asm/elmer/include/movie.asm | 15 +- .../asm/elmer/ted2-core-gulliver/gulliver.asm | 2 +- 3 files changed, 391 insertions(+), 57 deletions(-) diff --git a/examples/asm/elmer/include/math.asm b/examples/asm/elmer/include/math.asm index 0ed17efd..e96e0ec8 100644 --- a/examples/asm/elmer/include/math.asm +++ b/examples/asm/elmer/include/math.asm @@ -5,7 +5,7 @@ ; ; Basic (i.e. slow) math routines. ; -; Copyright John Brandwood 2021-2022. +; Copyright John Brandwood 2021-2024. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at @@ -19,32 +19,114 @@ ; *************************************************************************** ; *************************************************************************** ; -; mul_8x16u - Simple 16-bit x 8-bit multiply. +; mul_8x8u - Simple 8-bit x 8-bit -> 16-bit unsigned multiply. ; -; Args: _ax = 16-bit Multiplicand / Result -; Args: _cl = 8-bit Multiplier +; Args: _al = 8-bit Multiplier +; Args: _cl = 16-bit Multiplicand +; +; Returns: _ax = 16-bit Result +; +; Returns: Y=0,Z-flag,N-flag. +; +; Preserved: _cx,_dx +; +; Derived from Leo J Scanlon's 16x16 multiply in the book "6502 Software +; Design" with an optimization to rotate the result into the multiplier. +; +; This is very similar to TobyLobster's modificaton to the 16x16 multiply +; from "The Merlin 128 Macro Assembler" disk, but with optimized branches. +; +; See https://github.com/TobyLobster/multiply_test/blob/main/tests/mult2.a +; +; Takes between 158..190 cycles, 174 on average. +; +; Note: Unrolling the loop once saves 16 cycles, but IMHO that's not worth +; the extra 8-bytes of code. +; +; Note: Fully unrolling the loop saves 36 cycles, but the extra 49 bytes of +; code mean that it should be a procedure, and then the trampoline overhead +; would actually make it no faster! ; -mul_8x16u: .proc +mul_8x8u: cla ; Clear top byte of result. - cla ; Clear Result. - clx - lsr <_cl ; Shift and test multiplier. - bcc .loop + ldy #8 ; Loop 8 times. -.add: clc ; Add _ax to the Result. - adc.l <_ax - sax - adc.h <_ax - sax + lsr <_ax + 0 ; Divide multiplier by 2 and + bcc .rotate ; clear 8th bit (important). -.loop: asl.l <_ax ; _ax = _ax * 2 - rol.h <_ax - lsr <_cl ; Shift and test multiplier. - bcs .add - bne .loop - sta.l <_ax ; Save Result. - stx.h <_ax +.add: clc ; Add the 8-bit multiplicand + adc <_cl ; to top 8-bits of the result. + +.rotate: ror a ; Rotate result into the top + ror <_ax + 0 ; bits of the multiplier. + + dey + bcs .add ; Add multiplicand to top byte? + bne .rotate ; Completed 8 loops? + + sta <_ax + 1 ; Save top byte of result. + + rts + + + +; *************************************************************************** +; *************************************************************************** +; +; mul_16x8u - Simple 16-bit x 8-bit -> 24-bit unsigned multiply. +; +; Args: _al = 8-bit Multiplier +; Args: _cx = 16-bit Multiplicand +; +; Returns: _ax,_bl = 24-bit Result +; +; Returns: A=Y=0,Z-flag,N-flag. +; +; Preserved: _cx,_dx +; +; Derived from Leo J Scanlon's 16x16 multiply in the book "6502 Software +; Design" with an optimization to rotate the result into the multiplier. +; +; This is very similar to TobyLobster's modificaton to the 16x16 multiply +; from "The Merlin 128 Macro Assembler" disk, but with optimized branches. +; +; See https://github.com/TobyLobster/multiply_test/blob/main/tests/mult2.a +; +; Takes between 243..403 cycles, 323 on average, including 47 cycle procedure +; call overhead vs 14 cycles for a JSR+RTS. +; + +mul_16x8u: .proc + + cla ; Clear top word of result. + sta <_ax + 1 + + ldy #8 ; Loop 8 times. + + lsr <_ax + 0 ; Divide multiplier by 2 and + bcc .rotate ; clear 8th bit (important). + +.add: tax ; Preserve top byte of result. + + clc ; Add the 16-bit multiplicand + lda <_ax + 1 ; to top 16-bits of the result. + adc.l <_cx + sta <_ax + 1 + + txa ; Restore top byte of result. + adc.h <_cx + +.rotate: ror a ; Rotate result into the top + ror <_ax + 1 ; bits of the multiplier ... + + ror <_ax + 0 ; and divide multiplier by 2. + + dey + bcs .add ; Add multiplicand to top word? + bne .rotate ; Completed 8 loops? + + sta <_ax + 2 ; Save top byte of result. leave @@ -55,38 +137,287 @@ mul_8x16u: .proc ; *************************************************************************** ; *************************************************************************** ; -; mul_8x24u - Simple 24-bit x 8-bit multiply. +; mul_16x16u - Simple 16-bit x 16-bit -> 32-bit unsigned multiply. ; -; Args: _ax,_bl = 24-bit Multiplicand / Result -; Args: _cl = 8-bit Multiplier +; Args: _ax = 16-bit Multiplier +; Args: _cx = 16-bit Multiplicand +; +; Returns: _ax,_bx = 32-bit Result +; +; Returns: A=Y=0,Z-flag,N-flag. +; +; Preserved: _cx,_dx +; +; Derived from Leo J Scanlon's 16x16 multiply in the book "6502 Software +; Design" with an optimization to rotate the result into the multiplier. +; +; This is the same as TobyLobster's modificaton to the 16x16 multiply from +; Dr Jefyll "http://forum.6502.org/viewtopic.php?f=9&t=689&start=0#p19958", +; but with optimized branches. +; +; See https://github.com/TobyLobster/multiply_test/blob/main/tests/mult60.a +; +; Takes between 441..809 cycles, 625 on average, including 47 cycle procedure +; call overhead vs 14 cycles for a JSR+RTS. +; +; Note: Unrolling the outer loop saves 12..60 cycles, but IMHO that's not +; worth the extra 22-bytes of code! ; -mul_8x24u: .proc +mul_16x16u: .proc - cla ; Clear Result. - clx - cly - lsr <_cl ; Shift and test multiplier. - bcc .loop + cla ; Clear top word of result. + sta <_ax + 2 -.add: clc ; Add _ax to the Result. - adc <_ax + 0 - sax ; x = lo-byte, a = mi-byte - adc <_ax + 1 - sax - say ; y = lo-byte, a = hi-byte - adc <_ax + 2 - say + ldx #-2 ; Multiplier is 2 bytes long. -.loop: asl <_ax + 0 ; _ax = _ax * 2 - rol <_ax + 1 - rol <_ax + 2 - lsr <_cl ; Shift and test multiplier. - bcs .add - bne .loop - sta <_ax + 0 ; Save Result. - stx <_ax + 1 - sty <_ax + 2 +.loop: ldy #8 ; 8 bits in a byte. + + lsr <_ax + 2, x ; Divide multiplier by 2 and + bcc .rotate ; clear 8th bit (important). + +.add: pha ; Preserve top byte of result. + + clc ; Add the 16-bit multiplicand + lda <_ax + 2 ; to top 16-bits of the result. + adc.l <_cx + sta <_ax + 2 + + pla ; Restore top byte of result. + adc.h <_cx + +.rotate: ror a ; Rotate result into the top + ror <_ax + 2 ; bits of the multiplier ... + + ror <_ax + 2, x ; and divide multiplier by 2. + + dey + bcs .add ; Add multiplicand to top word? + bne .rotate ; Completed 8 bits? + + inx ; Loop to the next higher byte + bne .loop ; in the multiplier. + + sta <_ax + 3 ; Save top byte of result. + + leave + + .endp + + + +; *************************************************************************** +; *************************************************************************** +; +; mul_24x8u - Simple 24-bit x 8-bit -> 32-bit unsigned multiply. +; +; Args: _al = 8-bit Multiplier +; Args: _cx,_dl = 24-bit Multiplicand +; +; Returns: _ax,_bx = 32-bit Result +; +; Returns: A=Y=0,Z-flag,N-flag. +; +; Preserved: _cx,_dx +; +; Derived from Leo J Scanlon's 16x16 multiply in the book "6502 Software +; Design" with an optimization to rotate the result into the multiplier. +; +; This is very similar to TobyLobster's modificaton to the 16x16 multiply +; from "The Merlin 128 Macro Assembler" disk, but with optimized branches. +; +; See https://github.com/TobyLobster/multiply_test/blob/main/tests/mult2.a +; +; Takes between 295..551 cycles, 423 on average, including 47 cycle procedure +; call overhead vs 14 cycles for a JSR+RTS. +; + +mul_24x8u: .proc + + cla ; Clear top 24-bits of result. + sta <_ax + 2 + sta <_ax + 1 + + ldy #8 ; Loop 8 times. + + lsr <_ax + 0 ; Divide multiplier by 2 and + bcc .rotate ; clear 8th bit (important). + +.add: tax ; Preserve top byte of result. + + clc ; Add the 24-bit multiplicand + lda <_ax + 1 ; to top 24-bits of the result. + adc <_cx + 0 + sta <_ax + 1 + + lda <_ax + 2 + adc <_cx + 1 + sta <_ax + 2 + + txa ; Restore top byte of result. + adc <_cx + 2 + +.rotate: ror a ; Rotate result into the top + ror <_ax + 2 ; bits of the multiplier ... + ror <_ax + 1 + + ror <_ax + 0 ; and divide multiplier by 2. + + dey + bcs .add ; Add multiplicand to top bits? + bne .rotate ; Completed 8 loops? + + sta <_ax + 3 ; Save top byte of result. + + leave + + .endp + + + +; *************************************************************************** +; *************************************************************************** +; +; mul_24x16u - Simple 24-bit x 16-bit -> 40-bit unsigned multiply. +; +; Args: _ax = 16-bit Multiplier +; Args: _cx,_dl = 24-bit Multiplicand +; +; Returns: _ax,_bx,Y = 40-bit Result +; +; Returns: A=Y,Z-flag,N-flag. +; +; Preserved: _cx,_dx +; +; Derived from Leo J Scanlon's 16x16 multiply in the book "6502 Software +; Design" with an optimization to rotate the result into the multiplier. +; +; This is the same as TobyLobster's modificaton to the 16x16 multiply from +; Dr Jefyll "http://forum.6502.org/viewtopic.php?f=9&t=689&start=0#p19958", +; but with optimized branches. +; +; See https://github.com/TobyLobster/multiply_test/blob/main/tests/mult60.a +; +; Takes between 539..1099 cycles, 819 on average, including 47 cycle procedure +; call overhead vs 14 cycles for a JSR+RTS. +; + +mul_24x16u: .proc + + cla ; Clear top 24-bits of result. + sta <_ax + 3 + sta <_ax + 2 + + ldx #-2 ; Multiplier is 2 bytes long. + +.loop: ldy #8 ; 8 bits in a byte. + + lsr <_ax + 2, x ; Divide multiplier by 2 and + bcc .rotate ; clear 8th bit (important). + +.add: pha ; Preserve top byte of result. + + clc ; Add the 24-bit multiplicand + lda <_ax + 2 ; to top 24-bits of the result. + adc <_cx + 0 + sta <_ax + 2 + + lda <_ax + 3 + adc <_cx + 1 + sta <_ax + 3 + + pla ; Restore top byte of result. + adc <_cx + 2 + +.rotate: ror a ; Rotate result into the top + ror <_ax + 3 ; bits of the multiplier ... + ror <_ax + 2 + + ror <_ax + 2, x ; and divide multiplier by 2. + + dey + bcs .add ; Add multiplicand to top bits? + bne .rotate ; Completed 8 bits? + + inx ; Loop to the next higher byte + bne .loop ; in the multiplier. + + tay ; Save top byte of result. + + leave + + .endp + + + +; *************************************************************************** +; *************************************************************************** +; +; mul_32x8u - Simple 32-bit x 8-bit -> 40-bit unsigned multiply. +; +; Args: _ax = 8-bit Multiplier +; Args: _cx,_dx = 32-bit Multiplicand +; +; Returns: _ax,_bx,Y = 40-bit Result +; +; Returns: A=Y,Z-flag,N-flag. +; +; Preserved: _cx,_dx +; +; Derived from Leo J Scanlon's 16x16 multiply in the book "6502 Software +; Design" with an optimization to rotate the result into the multiplier. +; +; This is very similar to TobyLobster's modificaton to the 16x16 multiply +; from "The Merlin 128 Macro Assembler" disk, but with optimized branches. +; +; See https://github.com/TobyLobster/multiply_test/blob/main/tests/mult2.a +; +; Takes between 345..697 cycles, 521 on average, including 47 cycle procedure +; call overhead vs 14 cycles for a JSR+RTS. +; + +mul_32x8u: .proc + + cla ; Clear top 32-bits of result. + sta <_ax + 3 + sta <_ax + 2 + sta <_ax + 1 + + ldy #8 ; Loop 8 times. + + lsr <_ax + 0 ; Divide multiplier by 2 and + bcc .rotate ; clear 8th bit (important). + +.add: tax ; Preserve top byte of result. + + clc ; Add the 32-bit multiplicand + lda <_ax + 1 ; to top 32-bits of the result. + adc <_cx + 0 + sta <_ax + 1 + + lda <_ax + 2 + adc <_cx + 1 + sta <_ax + 2 + + lda <_ax + 3 + adc <_cx + 2 + sta <_ax + 3 + + txa ; Restore top byte of result. + adc <_cx + 3 + +.rotate: ror a ; Rotate result into the top + ror <_ax + 3 ; bits of the multiplier ... + ror <_ax + 2 + ror <_ax + 1 + + ror <_ax + 0 ; and divide multiplier by 2. + + dey + bcs .add ; Add multiplicand to top bits? + bne .rotate ; Completed 8 loops? + + tay ; Save top byte of result. leave @@ -101,10 +432,12 @@ mul_8x24u: .proc ; ; Args: _ax = Dividend / Quotient ; Args: _bx = Dividend / Quotient (if 32-bit) -; Args: _cl = Dividor +; Args: _cl = Divisor ; ; Returns: Y=A,Z-flag,N-flag = Remainder. ; +; Preserved: _cx,_dx +; div16_7u .proc @@ -139,6 +472,8 @@ div16_7u .proc ; ; Returns: Y=A,Z-flag,N-flag = Remainder. ; +; Preserved: _cx,_dx +; div32_7u .proc diff --git a/examples/asm/elmer/include/movie.asm b/examples/asm/elmer/include/movie.asm index 4094a31b..4cdec10f 100644 --- a/examples/asm/elmer/include/movie.asm +++ b/examples/asm/elmer/include/movie.asm @@ -418,10 +418,10 @@ huv_proc_header:lda #BUFFER_1ST_BANK ; Map the workspace into MPR3. lda.l HUV_FRM_COUNT ; Save the #frames in sta.l 24-bit lda <_ax + 0 ; Preserve page-offset into and #7 ; the sector. @@ -526,10 +525,10 @@ huv_play_from: stz.l bg_x1 ; Reset screen flip. lda.l Date: Tue, 9 Jan 2024 15:56:35 -0500 Subject: [PATCH 20/40] For clarity, change core.inc to use .zp allocation instead of equates. --- examples/asm/elmer/include/core.inc | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/asm/elmer/include/core.inc b/examples/asm/elmer/include/core.inc index 4e707662..1c52ea9e 100644 --- a/examples/asm/elmer/include/core.inc +++ b/examples/asm/elmer/include/core.inc @@ -5,7 +5,7 @@ ; ; Base include for the "CORE(not TM)" PC Engine library code. ; -; Copyright John Brandwood 2021-2022. +; Copyright John Brandwood 2021-2024. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at @@ -88,33 +88,33 @@ core_zpend = $F8:20EC ; the X register, and then index into either set of hardware ; registers and the "sgx_reg" or "vdc_reg" shadow-variables. -PCE_VDC_OFFSET = $00 ; Offset to PCE VDC hw & vars. -SGX_VDC_OFFSET = $10 ; Offset to SGX VDC hw & vars. +PCE_VDC_OFFSET = $00 ; Offset to PCE VDC chip & shadow vars. +SGX_VDC_OFFSET = $10 ; Offset to SGX VDC chip & shadow vars. -_temp = $F8:2000 ; Use within any ASM routine. -_bank = $F8:2002 ; Use within any ASM routine. -sgx_crl = $F8:2003 ; SGX shadow (vdc_crl = $20F3). -sgx_crh = $F8:2004 ; SGX shadow (vdc_crh = $20F4). -core_1stbank = $F8:2005 ; 1st bank of library code. -sgx_sr = $F8:2006 ; SGX shadow (vdc_sr = $20F6). -sgx_reg = $F8:2007 ; SGX shadow (vdc_reg = $20F7). + .zp + .org $2000 +_temp ds 2 ; $F8:2000 Use within any ASM routine. +_bp_bank ds 1 ; $F8:2002 Use within any ASM routine. +sgx_crl ds 1 ; $F8:2003 SGX shadow (vdc_crl = $20F3). +sgx_crh ds 1 ; $F8:2004 SGX shadow (vdc_crh = $20F4). +core_1stbank ds 1 ; $F8:2005 1st bank of library code. +sgx_sr ds 1 ; $F8:2006 SGX shadow (vdc_sr = $20F6). +sgx_reg ds 1 ; $F8:2007 SGX shadow (vdc_reg = $20F7). -core_zp1st = $F8:2008 ; 1st free user address. +core_zp1st = * ; $F8:2008 1st free user address. .else SUPPORT_SGX -_temp = $F8:2000 ; Use within any ASM routine. -_bank = $F8:2002 ; Use within any ASM routine. -core_1stbank = $F8:2003 ; 1st bank of engine code. + .zp + .org $2000 +_temp ds 2 ; Use within any ASM routine. +_bp_bank ds 1 ; Use within any ASM routine. +core_1stbank ds 1 ; 1st bank of engine code. -core_zp1st = $F8:2004 ; 1st free user address. +core_zp1st = * ; 1st free user address. .endif SUPPORT_SGX - .zp - .org core_zp1st - .code - ; ; The kernel code in RAM follows the System Card's RAM variables. ; From 137472ad6717ba39cc5d320072916b03e78820d1 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Wed, 10 Jan 2024 13:42:02 -0500 Subject: [PATCH 21/40] Update decompressors to use normal MPR3 mapping, and to return a _farptr. Add documentation for compressors to use, and their command line flags. --- examples/asm/elmer/include/unpack-lzsa1.asm | 29 +++++++--- examples/asm/elmer/include/unpack-lzsa2.asm | 29 +++++++--- examples/asm/elmer/include/unpack-zx0.asm | 59 +++++++++++++-------- 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/examples/asm/elmer/include/unpack-lzsa1.asm b/examples/asm/elmer/include/unpack-lzsa1.asm index eeff1b31..1e5f6b21 100644 --- a/examples/asm/elmer/include/unpack-lzsa1.asm +++ b/examples/asm/elmer/include/unpack-lzsa1.asm @@ -5,9 +5,9 @@ ; ; HuC6280 decompressor for Emmanuel Marty's LZSA1 format. ; -; The code is 171 bytes for the small version, and 197 bytes for the normal. +; The code is 172 bytes for the small version, and 198 bytes for the normal. ; -; Copyright John Brandwood 2019-2021. +; Copyright John Brandwood 2019-2024. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at @@ -15,6 +15,18 @@ ; ; *************************************************************************** ; *************************************************************************** +; +; N.B. The decompressor expects the data to be compressed without a header! +; +; Use Emmanuel Marty's LZSA compressor which can be found here ... +; https://github.com/emmanuel-marty/lzsa +; +; To create an LZSA1 file to decompress to RAM +; +; lzsa -r -f 1 +; +; *************************************************************************** +; *************************************************************************** @@ -72,7 +84,9 @@ lzsa1_offset = lzsa1_winptr ; lzsa1_to_ram - Decompress data stored in Emmanuel Marty's LZSA1 format. ; ; Args: _bp, Y = _farptr to compressed data in MPR3. -; Args: _di = ptr to output address in RAM. +; Args: _di = ptr to output address in RAM (anywhere except MPR3!). +; +; Returns: _bp, Y = _farptr to byte after compressed data. ; ; Uses: _bp, _di, _ax, _bl ! ; @@ -82,11 +96,9 @@ lzsa1_to_ram .proc tma3 ; Preserve MPR3. pha - tya ; Map lzsa1_srcptr to MPR3. - beq !+ - tam3 + jsr set_bp_to_mpr3 ; Map lzsa1_srcptr to MPR3. -!: clx ; Initialize hi-byte of length. + clx ; Initialize hi-byte of length. cly ; Initialize source index. ; @@ -291,6 +303,9 @@ lzsa1_to_ram .proc pla ; Decompression completed, pop pla ; return address. + tma3 ; Return final MPR3 in Y reg. + tay + pla ; Restore MPR3. tam3 diff --git a/examples/asm/elmer/include/unpack-lzsa2.asm b/examples/asm/elmer/include/unpack-lzsa2.asm index cb6d3308..98112b84 100644 --- a/examples/asm/elmer/include/unpack-lzsa2.asm +++ b/examples/asm/elmer/include/unpack-lzsa2.asm @@ -5,9 +5,9 @@ ; ; HuC6280 decompressor for Emmanuel Marty's LZSA2 format. ; -; The code is 247 bytes for the small version, and 262 bytes for the normal. +; The code is 248 bytes for the small version, and 263 bytes for the normal. ; -; Copyright John Brandwood 2019-2021. +; Copyright John Brandwood 2019-2024. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at @@ -15,6 +15,18 @@ ; ; *************************************************************************** ; *************************************************************************** +; +; N.B. The decompressor expects the data to be compressed without a header! +; +; Use Emmanuel Marty's LZSA compressor which can be found here ... +; https://github.com/emmanuel-marty/lzsa +; +; To create an LZSA2 file to decompress to RAM +; +; lzsa -r -f 2 +; +; *************************************************************************** +; *************************************************************************** @@ -73,7 +85,9 @@ lzsa2_nibble = _dl ; 1 byte. ; lzsa2_to_ram - Decompress data stored in Emmanuel Marty's LZSA2 format. ; ; Args: _bp, Y = _farptr to compressed data in MPR3. -; Args: _di = ptr to output address in RAM. +; Args: _di = ptr to output address in RAM (anywhere except MPR3!). +; +; Returns: _bp, Y = _farptr to byte after compressed data. ; ; Uses: _bp, _di, _ax, _bx, _cx, _dl ! ; @@ -83,11 +97,9 @@ lzsa2_to_ram .proc tma3 ; Preserve MPR3. pha - tya ; Map lzsa2_srcptr to MPR3. - beq !+ - tam3 + jsr set_bp_to_mpr3 ; Map lzsa2_srcptr to MPR3. -!: clx ; Hi-byte of length or offset. + clx ; Hi-byte of length or offset. cly ; Initialize source index. stz +; +; To create a ZX0 file to decompress to VRAM, using a 4KB ring-buffer in RAM +; +; salvador -classic -w 4096 +; ; *************************************************************************** ; *************************************************************************** @@ -79,7 +90,9 @@ zx0_bitbuf = _dl ; 1 byte. ; zx0_to_ram - Decompress data stored in Einar Saukas's ZX0 "classic" format. ; ; Args: _bp, Y = _farptr to compressed data in MPR3. -; Args: _di = ptr to output address in RAM. +; Args: _di = ptr to output address in RAM (anywhere except MPR3!). +; +; Returns: _bp, Y = _farptr to byte after compressed data. ; ; Uses: _bp, _di, _ax, _bx, _cx, _dh ! ; @@ -89,15 +102,9 @@ zx0_to_ram .proc tma3 ; Preserve MPR3. pha - .ifdef _KICKC jsr set_bp_to_mpr3 ; Map zx0_srcptr to MPR3. - .else - tya ; Map zx0_srcptr to MPR3. - beq !+ - tam3 - .endif -!: ldx #$40 ; Initialize bit-buffer. + ldx #$40 ; Initialize bit-buffer. ldy #$FF ; Initialize offset to $FFFF. sty Date: Wed, 10 Jan 2024 14:29:52 -0500 Subject: [PATCH 22/40] Change ZX0 sample command-line to 2KB window to match default window equates. --- examples/asm/elmer/include/unpack-zx0.asm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/asm/elmer/include/unpack-zx0.asm b/examples/asm/elmer/include/unpack-zx0.asm index 758221f9..5cdba888 100644 --- a/examples/asm/elmer/include/unpack-zx0.asm +++ b/examples/asm/elmer/include/unpack-zx0.asm @@ -27,9 +27,9 @@ ; ; salvador -classic ; -; To create a ZX0 file to decompress to VRAM, using a 4KB ring-buffer in RAM +; To create a ZX0 file to decompress to VRAM, using a 2KB ring-buffer in RAM ; -; salvador -classic -w 4096 +; salvador -classic -w 2048 ; ; *************************************************************************** ; *************************************************************************** From 39025486f5de7474601a980ea97a9e44470cb136 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Wed, 17 Jan 2024 18:30:47 -0500 Subject: [PATCH 23/40] Improve comments in cdrom.asm after recent findings about the SCSI abort. --- examples/asm/elmer/include/cdrom.asm | 92 ++++++++++++++++++---------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/examples/asm/elmer/include/cdrom.asm b/examples/asm/elmer/include/cdrom.asm index 02c62550..c7696648 100644 --- a/examples/asm/elmer/include/cdrom.asm +++ b/examples/asm/elmer/include/cdrom.asm @@ -107,6 +107,9 @@ SUPPORT_TIMING = 0 ; SCSI bus signals. +SCSI_SEL = $81 ; IFU_SCSI_CTL When PCE selects the CD-ROM device +SCSI_RST = $60 ; IFU_SCSI_CTL When aborting the current command + SCSI_MSK = $F8 ; IFU_SCSI_FLG SCSI_BSY = $80 ; IFU_SCSI_FLG Bus is Busy or Free SCSI_REQ = $40 ; IFU_SCSI_FLG Target Requests next Xfer @@ -114,7 +117,7 @@ SCSI_MSG = $20 ; IFU_SCSI_FLG Bus contains a Message SCSI_CXD = $10 ; IFU_SCSI_FLG Bus contains Control or /Data SCSI_IXO = $08 ; IFU_SCSI_FLG Bus Initiator or Target -SCSI_ACK = $80 ; IFU_IRQ_MSK +SCSI_ACK = $80 ; IFU_IRQ_MSK Initiator's side of REQ/ACK handshake ; SCSI bus phases. @@ -198,7 +201,7 @@ cplay_scsi_buf = scsi_send_buf ; Reuse the SCSI buffer. .if SUPPORT_TIMING scsi_stat_indx: ds 1 ; Track CD-ROM loading speed -scsi_stat_time: ds 256 ; (cdr_read_bnk & +scsi_stat_time: ds 256 ; (cdr_read_bnk & .endif SUPPORT_TIMING .code @@ -222,7 +225,12 @@ scsi_stat_time: ds 256 ; (cdr_read_bnk & ; *************************************************************************** ; *************************************************************************** ; -; scsi_handshake - Clock a byte of data onto the SCSI bus. +; scsi_handshake - Clock a byte of data on the SCSI bus in or out. +; +; Used after a SCSI_REQ from the CD-ROM target to acknowledge the data byte +; sent or received to/from IFU_SCSI_DAT. +; +; This is the manual version of using IFU_SCSI_AUTO to read a byte. ; ; N.B. On a HuCARD, this must be available for cdr_cplay_irq2! ; @@ -377,7 +385,12 @@ cdrom_group .procgroup ; *************************************************************************** ; *************************************************************************** ; -; scsi_handshake - Clock a byte of data onto the SCSI bus. +; scsi_handshake - Clock a byte of data on the SCSI bus in or out. +; +; Used after a SCSI_REQ from the CD-ROM target to acknowledge the data byte +; sent or received to/from IFU_SCSI_DAT. +; +; This is the manual version of using IFU_SCSI_AUTO to read a byte. ; ; On a CDROM, this can be in the same bank as the rest! ; @@ -398,7 +411,7 @@ scsi_handshake: lda #SCSI_ACK ; Send SCSI_ACK signal. ; *************************************************************************** ; *************************************************************************** ; -; scsi_get_phase - As a subroutine this helps provide a delay between reads! +; scsi_get_phase - As a subroutine to provide deskew delay between reads! ; scsi_get_phase: lda IFU_SCSI_FLG @@ -430,33 +443,49 @@ scsi_delay: clx ; The inner loop takes 3584 ; ; scsi_initiate - Initiate CD-ROM command. ; +; Normally the CD-ROM is already idle, and the process is simple. But ... +; +; When the CD-ROM is already busy processing a command, such as either ADPCM +; streaming or when there is an existing unfinalized command (which Sherlock +; Holmes games do all the time), then the current command must be stopped. +; +; In that case the PC Engine asserts the SCSI_RST signal and then delays for +; 30ms in order to give the CD-ROM time to abort its current command. +; +; After the abort the CD-ROM could end up in PHASE_STAT_IN or PHASE_COMMAND, +; and so the PC Engine just ignores any data coming from the CD-ROM until it +; finally drops the BSY signal and relinquishes the SCSI bus. +; -scsi_cd_busy: lda #IFU_INT_DAT_IN + IFU_INT_MSG_IN - trb IFU_IRQ_MSK ; Disable SCSI phase interrupts. +scsi_abort: lda #IFU_INT_DAT_IN + IFU_INT_MSG_IN + trb IFU_IRQ_MSK ; Just in case previously set. - sta IFU_SCSI_CTL ; Set control bits. + sta IFU_SCSI_CTL ; Signal SCSI_RST to stop cmd. ldy #30 * 2 ; Wait 30ms. bsr scsi_delay - lda #$FF - sta IFU_SCSI_DAT + lda #$FF ; Send $FF on SCSI bus in case + sta IFU_SCSI_DAT ; the CD reads it as a command. + + tst #SCSI_REQ, IFU_SCSI_FLG ; Does the CD-ROM want the PCE + beq scsi_initiate ; to send/recv some data? + +.flush_data: jsr scsi_handshake ; Flush out stale data byte. - tst #SCSI_REQ, IFU_SCSI_FLG - beq scsi_initiate +.still_busy: tst #SCSI_REQ, IFU_SCSI_FLG ; Keep on flushing data while + bne .flush_data ; the CD-ROM requests it. -.flush_data: jsr scsi_handshake ; Flush out stale data. + tst #SCSI_BSY, IFU_SCSI_FLG ; Wait for the CD-ROM to drop + bne .still_busy ; the SCSI_BSY signal. -.still_busy: tst #SCSI_REQ, IFU_SCSI_FLG - bne .flush_data - tst #SCSI_BSY, IFU_SCSI_FLG - bne .still_busy + ; -scsi_initiate: bsr scsi_select_cd ; Acquire the SCSI bus. - bcs scsi_cd_busy +scsi_initiate: bsr scsi_select_cd ; Attempt to select the CD-ROM. + bcs scsi_abort ; Is the CD-ROM already busy? .test_scsi_bus: ldy #18 ; Wait for up to 20ms for the - clx ; CD-ROM to acknowledge. + clx ; CD-ROM to signal SCSI_BSY. .wait_scsi_bus: bsr scsi_get_phase and #SCSI_BSY bne .ready @@ -481,16 +510,17 @@ scsi_initiate: bsr scsi_select_cd ; Acquire the SCSI bus. scsi_select_cd: sec - lda #$81 ; Abridged SCSI device - sta IFU_SCSI_DAT ; selection phase. + lda #$81 ; Set the SCSI device ID bits + sta IFU_SCSI_DAT ; for the PCE and the CD-ROM. + + tst #SCSI_BSY, IFU_SCSI_FLG ; Is the CD-ROM already busy + bne .done ; doing something? - tst #SCSI_BSY, IFU_SCSI_FLG ; Is the SCSI bus already - bne .done ; busy? + sta IFU_SCSI_CTL ; Signal SCSI_SEL to CD-ROM. - sta IFU_SCSI_CTL ; Magic Number! clc -.done: rts ; Returns CS if busy. +.done: rts ; Returns CS if CD-ROM busy. @@ -629,8 +659,8 @@ scsi_get_status:cly ; In case no PHASE_STAT_IN! .read_mesg: lda IFU_SCSI_DAT ; Flush message byte. jsr scsi_handshake -.wait_exit: tst #SCSI_BSY, IFU_SCSI_FLG ; Wait for the CD-ROM to - bne .wait_exit ; release the SCSI bus. +.wait_exit: bit IFU_SCSI_FLG ; Wait for the CD-ROM to + bmi .wait_exit ; release SCSI_BSY. tya ; Return status & set flags. rts @@ -1438,11 +1468,11 @@ cdr_incdec_len: lda.l cplay_len_l ; Decrement the ADPCM length. cdr_cplay_next .proc - jsr scsi_select_cd ; Acquire the SCSI bus. - bcs .finished ; Signal that we've failed! + jsr scsi_select_cd ; Attempt to select the CD-ROM. + bcs .finished ; Is the CD-ROM already busy? ldy #19 ; Wait for up to 20ms for the - clx ; CD-ROM to acknowledge. + clx ; CD-ROM to signal SCSI_BSY. .wait_scsi_bus: jsr scsi_get_phase and #SCSI_BSY bne .proc_scsi_loop From b2ee452914cbf03d48cabccf0ec1b5ea65beaa05 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Wed, 17 Jan 2024 18:38:53 -0500 Subject: [PATCH 24/40] Update ipl-scd and core-kernel to get around the System Card cd_read bug. --- examples/asm/elmer/include/core-kernel.asm | 29 +++++++++++---------- examples/asm/elmer/include/ipl-scd.dat | Bin 4096 -> 4096 bytes examples/asm/elmer/ipl-scd/ipl-scd.asm | 28 +++++++++++--------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/examples/asm/elmer/include/core-kernel.asm b/examples/asm/elmer/include/core-kernel.asm index 71d75964..b2a975c6 100644 --- a/examples/asm/elmer/include/core-kernel.asm +++ b/examples/asm/elmer/include/core-kernel.asm @@ -490,9 +490,9 @@ get_file_info: sec ; Calculate file length. ; *************************************************************************** ; *************************************************************************** -exec_overlay: phx ; Preserve file number. +exec_overlay: jsr core_clr_hooks ; Reset default hooks. - jsr core_clr_hooks ; Reset default hooks. +.retry: phx ; Preserve file number. cla ; Page System Card into MPR7. tam7 @@ -505,16 +505,6 @@ exec_overlay: phx ; Preserve file number. sta <_bl stz <_bh - tam2 ; Reset bank mapping back to - inc a ; its "CORE(not TM)" default. - tam3 - inc a - tam4 - inc a - tam5 - inc a - tam6 - lda #6 ; Use MPR6 for loading. sta <_dh @@ -523,9 +513,20 @@ exec_overlay: phx ; Preserve file number. plx ; Restore file number. cmp #0 ; Was there an error? - bne exec_overlay ; Retry forever! + bne .retry ; Retry forever! + +.execute_game: lda ~sKuiEYtp7F8wW&s;F-Y1VncyIM;2?xCseOg)sWOG_AjC0Zq9C9gAaCHQ0U&AM Qsa=yC2_69*vpWgH2ITQ6&Hw-a delta 111 zcmV-#0FeKHAb=pSmIxkKAl~4xQX(mTBH#m%N~vg51R7Ha8dDG&Qy>~sK$EBlGB#zR zF@OwA06?t&HPFMUM4~ZB+8~+WAba2-f-$Lkh3u&^h3z21F=C=1pd27?;Hd#1Y2c|_ RlO72k0UNVD3Bv{g000%eDQf@# diff --git a/examples/asm/elmer/ipl-scd/ipl-scd.asm b/examples/asm/elmer/ipl-scd/ipl-scd.asm index 6c6a07fc..48bc18b9 100644 --- a/examples/asm/elmer/ipl-scd/ipl-scd.asm +++ b/examples/asm/elmer/ipl-scd/ipl-scd.asm @@ -6,7 +6,7 @@ ; A version of Hudson's IPL for the PC Engine SuperCD that is customized ; to load homebrew assembly-language CD games that are built using ISOlink. ; -; Copyright John Brandwood 2021. +; Copyright John Brandwood 2021-2024. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at @@ -15,7 +15,7 @@ ; *************************************************************************** ; *************************************************************************** ; -; NOTE: This is not compatible with the current version of HuC! +; NOTE: This is not compatible with the old 2021 version of HuC! ; ; *************************************************************************** ; *************************************************************************** @@ -178,7 +178,7 @@ ipl_scd: jsr ex_getver ; Get System Card version. jsr ex_memopen ; Is there any SCD memory? bcs .not_supercd - sax ; Remove internal-or-HuCard + sax ; Remove internal-or-HuCARD and #$7F ; flag bit. sax cpx #3 ; Are there at-least three @@ -186,15 +186,7 @@ ipl_scd: jsr ex_getver ; Get System Card version. lda #$68 ; PCEAS code assumes this! - tam2 ; Map SCD RAM banks to - inc a ; replace the CD RAM banks. - tam3 - inc a - tam4 - inc a - tam5 - inc a - tam6 + tam2 ; Map the 1st SCD RAM bank. ldy #1 ; Load & run the 1st file. @@ -231,7 +223,17 @@ ipl_scd: jsr ex_getver ; Get System Card version. bra .report_error -.execute_game: jmp $4000 ; Execute the game. +.execute_game: tma2 ; Setup the bank mapping into + inc a ; its "CORE(not TM)" default. + tam3 ; This is not done earlier to + inc a ; avoid the System Card error + tam4 ; handling bug which causes a + inc a ; cd_read to an MPRn to leave + tam5 ; the MPRn value corrupted. + inc a + tam6 + + jmp $4000 ; Execute the game. .not_supercd: ldy iso_cderr ; Is there an error file bne .load_file ; to run? From 7eaf168c76d0b753bbf37b3fc2c30733424c810a Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Wed, 17 Jan 2024 19:02:34 -0500 Subject: [PATCH 25/40] Add some suggested HuVIDEO dimensions for different frame rates. --- examples/asm/elmer/include/movie.asm | 121 +++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/examples/asm/elmer/include/movie.asm b/examples/asm/elmer/include/movie.asm index 4cdec10f..c8178a90 100644 --- a/examples/asm/elmer/include/movie.asm +++ b/examples/asm/elmer/include/movie.asm @@ -78,6 +78,127 @@ ; ; *************************************************************************** ; *************************************************************************** +; +; Suggested video sizes for new 8-frames-per-second HuVIDEO data streams ... +; +; +; 256x104 (approximately 2.39x1) +; 32x13 = 416 CHR +; $3400 tiles ($0770 bytes-per-tick * 7 -> $3410) +; $0200 palettes +; $00D0 tile palettes +; $03E8 adpcm (1000 bytes, 2000 samples) +; $0028 adpcm resync +; = +; $3AE0 -> $3B00 * 8 -> 118KB/s +; +; +; 224x120 (approximately 16x9) +; 28x15 = 420 CHR +; $3480 tiles ($0780 bytes-per-tick * 7 -> $3480) +; $0200 palettes +; $00D2 tile palettes +; $03E8 adpcm (1000 bytes, 2000 samples) +; $0028 adpcm resync +; = +; $3B62 -> $3C00 * 8 -> 120KB/s +; +; +; 192x144 (approximately 4x3) +; 24x18 = 432 CHR +; $3600 tiles ($07C0 bytes-per-tick * 7 -> $3640) +; $0200 palettes +; $00D8 tile palettes +; $03E8 adpcm (1000 bytes, 2000 samples) +; $0028 adpcm resync +; = +; $3CE8 -> $3D00 * 8 -> 122KB/s +; +; +; Suggested video sizes for new 10-frames-per-second HuVIDEO data streams ... +; +; +; 224x96 (approximately 2.39x1) +; 28x12 = 336 CHR +; $2A00 tiles ($0700 bytes-per-tick * 6 -> $2A00) +; $0200 palettes +; $00A8 tile palettes +; $0320 adpcm (800 bytes, 1600 samples) +; $0028 adpcm resync +; = +; $2FF0 -> $3000 * 10 -> 120KB/s +; +; +; 192x112 (approximately 16x9) +; 24x14 = 336 CHR +; $2A00 tiles ($0700 bytes-per-tick * 6 -> $2A00) +; $0200 palettes +; $00A8 tile palettes +; $0320 adpcm (800 bytes, 1600 samples) +; $0028 adpcm resync +; = +; $2FF0 -> $3000 * 10 -> 120KB/s +; +; +; 176x120 (approximately 4x3) +; 22x15 = 330 CHR +; $2940 tiles ($06E0 bytes-per-tick * 6 -> $2940) +; $0200 palettes +; $00A5 tile palettes +; $0320 adpcm (800 bytes, 1600 samples) +; $0028 adpcm resync +; = +; $2F2D -> $3000 * 10 -> 120KB/s +; +; +; Suggested video sizes for new 12-frames-per-second HuVIDEO data streams ... +; +; +; 208x88 (approximately 2.39x1) +; 26x11 = 286 CHR +; $23C0 tiles ($0730 bytes-per-tick * 5 -> $23F0) +; $01E0 palettes **NOTE** +; $008F tile palettes +; $02A0 adpcm (669 bytes, 1338 samples) +; $0028 adpcm resync +; = +; $28F7 -> $2900 * 12 -> 123KB/s +; +; +; 176x104 (approximately 16x9) +; 22x13 = 286 CHR +; $23C0 tiles ($0730 bytes-per-tick * 5 -> $23F0) +; $01E0 palettes **NOTE** +; $008F tile palettes +; $02A0 adpcm (669 bytes, 1338 samples) +; $0028 adpcm resync +; = +; $28F7 -> $2900 * 12 -> 123KB/s +; +; +; 184x96 (approximately 16x9) +; 23x12 = 276 CHR +; $2280 tiles ($06F0 bytes-per-tick * 5 -> $22B0) +; $0200 palettes +; $0090 tile palettes +; $02A0 adpcm (669 bytes, 1338 samples) +; $0028 adpcm resync +; = +; $27D8 -> $2800 * 12 -> 120KB/s +; +; +; 160x112 (approximately 4x3) +; 20x14 = 280 CHR +; $2300 tiles ($0700 bytes-per-tick * 5 -> $2300) +; $0200 palettes +; $008C tile palettes +; $02A0 adpcm (669 bytes, 1338 samples) +; $0028 adpcm resync +; = +; $2854 -> $2900 * 12 -> 123KB/s +; +; *************************************************************************** +; *************************************************************************** ; ; Configure Library ... From 40a717396ff7725bf3ee3fddb6c475c52fda5213 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Thu, 18 Jan 2024 10:34:20 -0500 Subject: [PATCH 26/40] Update System Card patches with a fix for Hudson's cd_read_to_MPR bug. --- .../elmer/ted2-bios-romcd/syscard3-ted2.inc | 48 ++++++++++++++----- .../asm/elmer/ted2-bios-usbcd/ted2usbcd.asm | 2 +- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/examples/asm/elmer/ted2-bios-romcd/syscard3-ted2.inc b/examples/asm/elmer/ted2-bios-romcd/syscard3-ted2.inc index c989b274..35fb2f71 100644 --- a/examples/asm/elmer/ted2-bios-romcd/syscard3-ted2.inc +++ b/examples/asm/elmer/ted2-bios-romcd/syscard3-ted2.inc @@ -3,7 +3,7 @@ ; ; TURBO-GRAFX / PC-ENGINE SUPER SYSTEM CARD 3.00 PATCH FOR TURBO EVERDRIVE 2 ; -; Copyright John Brandwood 2015-2019. +; Copyright John Brandwood 2015-2024. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at @@ -25,6 +25,8 @@ ; 2015-11-24 - Initial Release 3.01 ; 2019-01-07 - Release 3.02 ; Add patch for Dragon Slayer II crash. +; 2024-01-05 - Release 3.03 +; Add patch to fix System Card's CD_READ-to-MPR bug. ; ; *************************************************************************** ; *************************************************************************** @@ -82,12 +84,18 @@ JPN_SYSCARD = 1 .incbin "syscard3.jpn.pce" -SCRSIZ_PATCH = $e28e +SCRSIZ_OLDPATCH = $e28e ; Patch STA $0002 in TED2 v3.02 patch!!! +SCRSIZ_NEWPATCH = $e288 ; Patch STA $0000 in TED2 v3.03 patch!!! + MEMOPEN_ADR = $fe92 UNLOCK_TEST = $c86b MESSAGE_ADR = $c9d1 RUNBOOT_ADR = $e206 +CDREAD_PATCH = $ed8e +RESTORE_MPRx = $ed91 +RETRY_CDREAD = $ecf1 + SCRSIZ_FUNC = $e267 ; bank 0 ; repair this from TED2 v3.01 patch!!! HACKINIT_ADR = $c887 ; bank 1 ; repair this from TED2 v3.01 patch!!! DISPLAY_SUPER = $c950 @@ -98,12 +106,18 @@ DISPLAY_SUPER = $c950 .incbin "syscard3.usa.pce" -SCRSIZ_PATCH = $e2a7 +SCRSIZ_OLDPATCH = $e2a7 ; Patch STA $0002 in TED2 v3.02 patch!!! +SCRSIZ_NEWPATCH = $e2a1 ; Patch STA $0000 in TED2 v3.03 patch!!! + MEMOPEN_ADR = $feab UNLOCK_TEST = $c86b ; bank 1 MESSAGE_ADR = $c9c4 ; bank 1 RUNBOOT_ADR = $e21f +CDREAD_PATCH = $eda7 +RESTORE_MPRx = $edaa +RETRY_CDREAD = $ed0a + SCRSIZ_FUNC = $e280 ; bank 0 ; repair this from TED2 v3.01 patch!!! HACKINIT_ADR = $c887 ; bank 1 ; repair this from TED2 v3.01 patch!!! DISPLAY_SUPER = $c943 @@ -188,13 +202,14 @@ my_ex_memopen: lda #$68 ; Return that 192KB of external SCD RAM clc ; internal SCD RAM chip. rts -fix_ex_scrsiz: pha ; Repair "Gate of Thunder" damage. - lda #$80 +fix_ex_scrsiz: sta $0000 ; Exec ex_scrsiz instr that was patched. + lda #$80 ; Repair "Gate of Thunder" damage. sta $FFF5 - pla - sta $0002 ; Exec ex_scrsiz instr that was patched. rts +fix_ex_cdread: jsr RESTORE_MPRx ; Fix Hudson's bug by restoring the old + jmp RETRY_CDREAD ; MPRx value before the CD_READ retries. + scd_unlock_test:php ; Disable interrupts for the next bit. sei lda #$A5 ; Unlock TED2 registers. @@ -221,8 +236,11 @@ scd_unlock_test:php ; Disable interrupts for the next bit. .org $e069 jmp SCRSIZ_FUNC - .org SCRSIZ_PATCH - jmp fix_ex_scrsiz + .org SCRSIZ_OLDPATCH + sta $0002 + + .org SCRSIZ_NEWPATCH + jsr fix_ex_scrsiz ; ; If we're building for a TED2 or 1MB-RAM card then patch the @@ -248,9 +266,17 @@ scd_unlock_test:php ; Disable interrupts for the next bit. .bank 0 .org RUNBOOT_ADR - jsr fix_bootload ; Patch System Card's jump to boot loader. + ; + ; Fix Hudson's System Card bug where ex_cdread to an MPR fails + ; to restore the original bank before retrying after an error. + ; + + .bank 0 + .org CDREAD_PATCH + jmp fix_ex_cdread + ; ; Check if the IPL of the CD (loaded at $3000) matches ; any of the games that need to be fixed. @@ -358,4 +384,4 @@ fix_dslayer2: tii .patch,$2CD1,(.done - .patch) .bank 1 .org MESSAGE_ADR - .db "TED2 3.02" + .db "TED2 3.03" diff --git a/examples/asm/elmer/ted2-bios-usbcd/ted2usbcd.asm b/examples/asm/elmer/ted2-bios-usbcd/ted2usbcd.asm index 4f487820..689bed6a 100644 --- a/examples/asm/elmer/ted2-bios-usbcd/ted2usbcd.asm +++ b/examples/asm/elmer/ted2-bios-usbcd/ted2usbcd.asm @@ -35,7 +35,7 @@ ; *************************************************************************** ; *************************************************************************** ; -; Apply some extra patches to run an overlay from ROM instead of loading it +; Apply some extra patches to run an overlay from USB instead of loading it ; from the CD, but try to keep the environment as similar as possible. ; From b7191396ddb05939e84eff4afb931788ec9724b4 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 19 Jan 2024 19:31:45 -0500 Subject: [PATCH 27/40] Add ted2-test-sherlock example project to investigate how Sherlock's video works. --- .../asm/elmer/ted2-test-sherlock/Makefile | 20 + .../asm/elmer/ted2-test-sherlock/README.txt | 24 + .../asm/elmer/ted2-test-sherlock/SHERLOCK.DAT | Bin 0 -> 196608 bytes .../asm/elmer/ted2-test-sherlock/dat2csv.c | 431 +++++++++++++ .../ted2-test-sherlock/sherlock-hack.asm | 595 ++++++++++++++++++ .../ted2-test-sherlock/sherlock-ted2.asm | 502 +++++++++++++++ 6 files changed, 1572 insertions(+) create mode 100644 examples/asm/elmer/ted2-test-sherlock/Makefile create mode 100644 examples/asm/elmer/ted2-test-sherlock/README.txt create mode 100644 examples/asm/elmer/ted2-test-sherlock/SHERLOCK.DAT create mode 100644 examples/asm/elmer/ted2-test-sherlock/dat2csv.c create mode 100644 examples/asm/elmer/ted2-test-sherlock/sherlock-hack.asm create mode 100644 examples/asm/elmer/ted2-test-sherlock/sherlock-ted2.asm diff --git a/examples/asm/elmer/ted2-test-sherlock/Makefile b/examples/asm/elmer/ted2-test-sherlock/Makefile new file mode 100644 index 00000000..59597a72 --- /dev/null +++ b/examples/asm/elmer/ted2-test-sherlock/Makefile @@ -0,0 +1,20 @@ +all: sherlock-hack.ovl sherlock-ted2.ovl dat2csv + +include ../Make_ex.inc + +ifeq ($(OS),Windows_NT) +TARGETS += *.exe +else +TARGETS += dat2csv +endif + +sherlock-hack.ovl: sherlock-hack.asm sherlock-boot.bin + $(AS) -raw -m -l 2 -S -trim sherlock-hack.asm -o sherlock-hack.ovl + cp sherlock-hack.ovl /d/retrogamedev/tools/sherlock/ + +sherlock-ted2.ovl: sherlock-ted2.asm sherlock-hack.ovl + $(AS) -newproc -strip -m -l 2 -S -scd -overlay sherlock-ted2.asm + cp sherlock-ted2.ovl /d/retrogamedev/tools/sherlock/ + +dat2csv: dat2csv.c + cc dat2csv.c -o dat2csv diff --git a/examples/asm/elmer/ted2-test-sherlock/README.txt b/examples/asm/elmer/ted2-test-sherlock/README.txt new file mode 100644 index 00000000..2cd5a7ca --- /dev/null +++ b/examples/asm/elmer/ted2-test-sherlock/README.txt @@ -0,0 +1,24 @@ +***************************************************************************** +Turbo Everdrive V2 "Sherlock Holmes Consulting Detective" (USA) Logfile Hack +***************************************************************************** + +Note: To build this project, you must first put a copy of the CD's boot code + into this directory with the filename "sherlock-boot.bin". + + If you have the ability to save binary files from within your emulator + software, you can run the game's CD with a breakpoint at address $2A00 + and then save the memory from $2A00..$81FF as "sherlock-boot.bin". + + If you have the game's CD image dumped as a MODE1/2048 .iso file, then + you can extract the file directly from the image itself using "dd" ... + + dd bs=2048 skip=2 count=11 if= of=sherlock-boot.bin + + You can confirm the validity of your file by checking its MD5 value. + + sherlock-boot.bin - MD5: 60aebcac752ba7b39d133c5d45a44d85 + + Because you must provide your own file, this project is not built by + default, and you must navigate to this directory and "make". + +***************************************************************************** diff --git a/examples/asm/elmer/ted2-test-sherlock/SHERLOCK.DAT b/examples/asm/elmer/ted2-test-sherlock/SHERLOCK.DAT new file mode 100644 index 0000000000000000000000000000000000000000..0d619559f615d35af78144519dd5e322daafaa76 GIT binary patch literal 196608 zcmXWjb(~ez8piRxcD&3CFf(*_hlG>}(%s!5-6cpnfOK~w-5}jvG7A9}Pyq#0Py`hW z5Cy$(f7|!`_5D20K5HBIvfs7#nbCC|=PPj>6K|Za(T!+wxX!IGM>MZ{&h7AXReZ-awjJAVXTuh^hKPLEHr7nlRPw23MV$Sa7rf*FO*ZwTdAD*D94c+n8a~xE!EUd z?W7<}GvxEOxoSG3aZ-|{69)8MROydC_aBhfNriTxDx=XOzo(ip>73MLnTjs`bk)pB z@1!BiA~f_%RI?_7la?&&k^e1{M07*{<*&CpqmvHZJ*qjA$w?pPh~`>mCqtMcn(wnX z8N+o?vpNQ^y6RUEUqtjh?M;x)ars=3L>(KYnzY#+FT79Q9F8AaCZ`jG*2?8bXsg^# zlF(jxoT$)|d7bFc>G_|RF7(A)CLV1P)x=aVDaeuxpVFsMB_lrJ^O-7|lxTCRk`Mo& zFI>r_LR(aolK4CQTa`^}v=OCxB-g3zI={s_rhXOIpfto^V;$R4m3H`;>m91PF4}IY z^u|Z@1FE?m+ILhLhQE-7kR zugVtuf_2+liidWODhKd$`s1y{M|)D0b9jw>Ni~;SOF)*-ReQ5_66b1^WA3y`Vo<)p zD^ZU9QI%itGp_&IHi?V&sVdL$Q~Fo!l6Yv1DuSPo<5UyhK8a73)OeXbvubj5ND`1G zk7^5ah;l9^aij=dOv3Y^W0XN#MwRmTF@2S{qg=GrRH=;@$c>cBw{aux3c#QrI;>h{toVUs8jT z6%Tuk&8tcwJjC_F{gb+AOR7>957NIiAgPD8vMM$30Db*|Nqw{pRB4L)$?a6rVNlY5 zEL~OGYfv(0pX*3}-0Si@d?%SfJ5-fXxQBks;AAe^393xR-Sl&YB=gYDRb>(GBCk}< zns<}=WLbwh>9?z9_t0bkS@x>-@X+MW4&%rP{Lnb&%&_DJ?Rix$;&%FL!;`ycKUd`j zZX8Gjl5zH!*k8j*c~G zBT6RnMshCIWmcYFdqrHS1ow zrq{Swcil^4dyR{8*K+^$<UKhE~bCDB;H5+ zSd~9j^I~ZVvxqtSk17r>BuA+xW?2fCEXi>JeOgsA;e6(NrsXL-w7FEtkMrmYuSnse zEv8B-oJ(K+gA@VU3aZqI#s~{P#WQE=58xh+T$#)cUYCuMcYl4-Z+zf z!0MD9+QF&}#~Jiv)}-{&j#XtMPAAV)&78F<1F|es?eev$oM}lNS&dVZvcFlE%Ann> z%7-|Ge)sxRF53O79Kp%-XEvnr(4JMr;w18Cs=2l?l~0y0a3cL})!f^ZDj>^!)&9IG zwKIWz!f!a9`TultYJ>J~RsM(X(+68pyJ#ay40#+mm1@##P3@5-J&vW%u1X#p!}`42 zQu}CMQ>Bz@%4|;^gnhZ~X`BS+d?kF3eL<}c(-^c7r9OEyxw&du?MUO2r45dv@1jZ% z9Lf4#JJWb*2deTej-Vg8D~*r#Jyj;EX4>wwW;pXSqRb%=BQH_S@;zx?vV4F;={Kmd z72jpfZ{M5NL%TzAcIuEU@N)qf(j#o|m;dDM((qKROEUL+IBwaw3 zJgO~tB)!u&aHJ^q33#3zO>fY?p-Oq|O<(0$dKYbVRq9|baud}wJ)YhpOH1rY|F&wn zoJjAJr5pC3@28p}C({RHc~`X~PiAnsvws+a-I)IqPh~J@C#y08yOI~GX5r}!E?Jgg z7y7lT*?1;{N0v?4nSQ4#`>+%DJ$N>QkM_7KAF0Nk%V^$a&VH=Qr`VBvT{Sm8%IK2i z7IvWjK{XH0XY|PO6Sk-SLzTa<9drIgVn!eBD^+}KOCMu1256J3k~)$z+DuLx=53}6 znG8xcY|VVlr<%eaXL89B+KRrEDsN&-)|J1Q$wOO3m73UszW$|5KH7$=G{ffP_NwV{ zIa5HEZmR8lIkVG@dDH)s%m!ruHf6pHQ)M(Z;rf_QGrMRfsxlQD)6e-Vvxjz`DvPlZ zd6jC`T*>T{Wj!{e->#b7S2G7>*{7N_*D{-ioYT)-%VHYPCt`i>f9>-uZs@%)vUs6S zzs%x?2G_F$p=oYpkE+ved+?^bJ+h^yjPrSz4;L-Ot&aD$MOpSed!o<(F&* zZEsZuU?ut?kFvRFhp93eE0QOuX5!;)9$BVh1^RiaS@>%0*ZY;+9;lFeEXp5^-8jI7Hf1V>iTTzvokzDV2PNx`iuKtUh2Bk3;W!|+`O^1JSx?~A0 zLf=D`zW5sJ`v05LLpwy35m=ah%>Q!wXve8C2@8>Dt7gv2oB>%DsdmN7TuwpG+pAyY zGAL`X0O#o~s_el0T;J{Fa?u`CNpkyWi>OjsHD#i52Vq}6Dvy(u`C1vXFsExq=P_t&tI_~7lUt~!RZJe2ENw9p zeOFa_VMf;XN}9(*J4ltGn1Oy|vOGT81XU)hW?J&RCOz|QhAMM09eJs0mdECG$+8mD z(r;8{8>V4CZja0Bq1~s-AxuqwEIzM~_OvSJkr&|Ra*DhG+D}yZGLmnl$mgWwTzw~H zK7;Zdrr78l?oU`UnPBh7i|qy>S8o~(+v4Nv=OBhIf~p# zHC;01_sP;7lhF59&5%s_1F{TN?Wjxz9AUnW#ejJ{F>?WfcB(3~(5GLRrGSfekt)m4 zBd=4<#;gTAvTR0|ezz(I(6IhswgNud6RMn7jm=&lpm#51FX$NN(2X1giH;l5Jj_{; z+c|DT^E_8U{xa-Sa{u?atLasfDbIhO`zo{gBBCEL$gcg^9I7dtSMO6kUqSw{cwXf% z$Uipct^x)5*THkBV8JAzV+s|F3Y}B9V07r}*9yjj?k-X=Y3P}v1(V^0PgENkevfdi zSi$5dq2c!kU#s#hhTkLHD_$@*{2t+?f+5uT~?4~E|(c&Z6X6pRnQ zM~K4kdxR9KNmH_5itu}c^s3ETvXJwPb5<_=lk-~MQiTlKLaG$Qr}Sk?7jn_Qp~_qM zgj_>4HOmz8$Wj;opl_;5EBu}Ht==f)qkUU7y~-8}$P&@^FI(98jrlSde`Rirc(brU zJ4%(Y_?SFJHPgx!cF8gmAJH#TWjX%B`sL*dduZ3GvKfD--~Lu%AMGwx4yoo?h1bka zJfBafauy$wFRJEp#n)W2e1;F`Z>Vw`e`F5asq~tM_P#1V;eGnYm0$DGK2haw{DJ;e zmDd8ajw;cS99yM`^F7b$_^L$=N-Dg^b2*bL+3_ydvsWwPqRp?$Yxo^~@#;l9w53#e z6ThXeQlp5EwyG+%@EdX?)ikMDBp^#G)wZu$)VafRy$kY%sp(Rys6pFDm4V0?t7b^; zqAuFus=SAM0c$4KDe9q(DAUM%L2Ku$W?|i;K3SIFP5RZU*;ub=K$gv_{jgp!=LYAj zJ$RjS+`;bH2^T;lm0QJRo1lG~`JL(A7)vUJ3c>3gcu4=*qW z`nP)BL;J2OBhk{2Y5ls7_I*_*V

WHm?V0Bg*1P{-8|>=RD8p)on`{l(qN~&+DzK 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�RCg)ezz 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

xHc9 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 G1pW`*E)>iF literal 0 HcmV?d00001 diff --git a/examples/asm/elmer/ted2-test-sherlock/dat2csv.c b/examples/asm/elmer/ted2-test-sherlock/dat2csv.c new file mode 100644 index 00000000..76af3ef1 --- /dev/null +++ b/examples/asm/elmer/ted2-test-sherlock/dat2csv.c @@ -0,0 +1,431 @@ +// ************************************************************************** +// ************************************************************************** +// +// dat2csv.c +// +// Convert the Sherlock logfile from binary to a CSV spreadsheet. +// +// Copyright John Brandwood 2024. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt)y +// +// ************************************************************************** +// ************************************************************************** +// +// This is a simple PC/Linux/MacOS command-line program that takes the 192KB +// SHERLOCK.DAT data file and converts the event log into a .CSV spreadsheet +// so that the data is easier for a human to understand. +// +// Just run the "dat2csv" program in the same directory as the SHERLOCK.DAT +// file and it will output the human-readable SHERLOCK.CSV spreadsheet file. +// +// ************************************************************************** +// ************************************************************************** + +// +// Standard C99 includes, with Visual Studio workarounds. +// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined (_MSC_VER) + #include + #define SSIZE_MAX UINT_MAX + #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) + #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) + #define strcasecmp _stricmp + #define strncasecmp _strnicmp +#else + #include + #ifndef O_BINARY + #define O_BINARY 0 + #define O_TEXT 0 + #endif +#endif + +#if defined (_MSC_VER) && (_MSC_VER < 1600) // i.e. before VS2010 + #define __STDC_LIMIT_MACROS + #include "msinttypes/stdint.h" +#else + #include +#endif + +#if defined (_MSC_VER) && (_MSC_VER < 1800) // i.e. before VS2013 + #define static_assert( test, message ) + #include "msinttypes/inttypes.h" + #include "msinttypes/stdbool.h" +#else + #include + #include +#endif + + + +// ************************************************************************** +// ************************************************************************** +// +// ReadBinaryFile () +// +// Uses POSIX file functions rather than C file functions. +// +// Google "FIO19-C" for the reason why. +// +// N.B. Will return an error for files larger than 2GB on a 32-bit system. +// + +bool ReadBinaryFile (const char *pName, uint8_t **pBuffer, size_t *pLength) +{ + uint8_t * pData = NULL; + off_t uSize; + struct stat cStat; + + int hFile = open(pName, O_BINARY | O_RDONLY); + + if (hFile == -1) + goto errorExit; + + if ((fstat(hFile, &cStat) != 0) || (!S_ISREG(cStat.st_mode))) + goto errorExit; + + if (cStat.st_size > SSIZE_MAX) + goto errorExit; + + uSize = cStat.st_size; + + pData = (uint8_t *)malloc(uSize); + + if (pData == NULL) + goto errorExit; + + if (read(hFile, pData, uSize) != uSize) + goto errorExit; + + close(hFile); + + *pBuffer = pData; + *pLength = uSize; + + return (true); + + // Handle errors. + +errorExit: + + if (pData != NULL) free(pData); + if (hFile >= 0) close(hFile); + + *pBuffer = NULL; + *pLength = 0; + + return (false); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// main () +// + +// Data offsets for the information in each logged event. + +#define EVENT_TYPE 0 +#define EVENT_TIMER 1 +#define EVENT_BLANK 2 +#define EVENT_FRAME 3 +#define EVENT_TICK 4 +#define EVENT_LEFT 5 +#define EVENT_WRONG 6 +#define EVENT_DELAY 7 + +// Event types. + +#define INFO_VBLANK 1 + +#define INFO_WAITVBL 2 + +#define INFO_SCSIRMV 3 +#define INFO_SCSIIDLE 4 +#define INFO_SCSISEND 5 + +#define INFO_DATAEND 6 +#define INFO_FILLEND 7 + +#define INFO_SEEK_BEGIN 10 +#define INFO_SEEK_ENDED 11 + +#define INFO_NOTBUSY 12 + +#define INFO_SCSI80 0x80u // PHASE_DATA_OUT +#define INFO_SCSI88 0x88u // PHASE_DATA_IN +#define INFO_SCSI98 0x98u // PHASE_STAT_IN + +#define PHASE_COMMAND 0xD0u // (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + ........) +#define PHASE_DATA_OUT 0xC0u // (SCSI_BSY + SCSI_REQ + ........ + ........ + ........) +#define PHASE_DATA_IN 0xC8u // (SCSI_BSY + SCSI_REQ + ........ + ........ + SCSI_IXO) +#define PHASE_STAT_IN 0xD8u // (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + SCSI_IXO) +#define PHASE_MESG_OUT 0xF0u // (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + ........) +#define PHASE_MESG_IN 0xF8u // (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + SCSI_IXO) + +#define REQ_DATA_OUT (0xC0u+1) // (SCSI_BSY + SCSI_REQ + ........ + ........ + ........) +#define REQ_DATA_IN (0xC8u+1) // (SCSI_BSY + SCSI_REQ + ........ + ........ + SCSI_IXO) +#define REQ_STAT_IN (0xD8u+1) // (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + SCSI_IXO) + +#define RMV_COMMAND (0xD0u+2) // (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + ........) +#define RMV_DATA_OUT (0xC0u+2) // (SCSI_BSY + SCSI_REQ + ........ + ........ + ........) +#define RMV_DATA_IN (0xC8u+2) // (SCSI_BSY + SCSI_REQ + ........ + ........ + SCSI_IXO) +#define RMV_STAT_IN (0xD8u+2) // (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + SCSI_IXO) +#define RMV_MESG_OUT (0xF0u+2) // (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + ........) +#define RMV_MESG_IN (0xF8u+2) // (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + SCSI_IXO) + +#define INFO_EXIT 0x7Fu + +// + +int main ( int argc, char **argv ) + +{ + // + + uint8_t * pDatFile; + uint8_t * pDatStop; + size_t uDatSize; + + FILE * pCsvFile; + + uint8_t * pLog; + + unsigned uLastData; + unsigned uThisData; + + unsigned uTimerAdd; + unsigned uBlankAdd; + unsigned uFrameAdd; + + uint8_t uLastTimer; + uint8_t uLastBlank; + uint8_t uLastFrame; + + // Sign on. + + printf("dat2csv - Converting SHERLOCK.DAT to SHERLOCK.CSV\n\n"); + + // Read the Sherlock data file. + + pDatFile = NULL; + uDatSize = 0; + + if (!ReadBinaryFile( "SHERLOCK.DAT", &pDatFile, &uDatSize)) + { + printf("Cannot load SHERLOCK.DAT!\n"); + exit(1); + } + + pDatStop = pDatFile + uDatSize; + + // + + pCsvFile = NULL; + + if ((pCsvFile = fopen( "SHERLOCK.CSV", "w" )) == NULL) + { + printf( "Cannot open SHERLOCK.CSV!\n" ); + exit( 1 ); + } + + fprintf( pCsvFile, "Timer,Vblank,Frame,Tick,Sectors,Wrong,Delay,Reason\n" ); + + // + + uTimerAdd = 0; + uBlankAdd = 0; + uFrameAdd = 0; + + uLastTimer = 0; + uLastBlank = 0; + uLastFrame = 0; + + uLastData = 0; + uThisData = 0; + + pLog = pDatFile; + + for (pLog = pDatFile; pLog <= (pDatStop - 8); pLog += 8) + { + // Put a blank line between each frame of video. + + if (uLastFrame != pLog[EVENT_FRAME]) + { + fprintf( pCsvFile, "\n" ); + } + + // Did any of the byte values just wrap? + + if (uLastTimer > pLog[EVENT_TIMER]) uTimerAdd += 256; + if (uLastBlank > pLog[EVENT_BLANK]) uBlankAdd += 256; + if (uLastFrame > pLog[EVENT_FRAME]) uFrameAdd += 256; + + uLastTimer = pLog[EVENT_TIMER]; + uLastBlank = pLog[EVENT_BLANK]; + uLastFrame = pLog[EVENT_FRAME]; + + // Filter out irrelevent VBLANK events. + + if ((pLog[EVENT_TYPE] == INFO_VBLANK) && (pLog[EVENT_DELAY] == 0)) + { + continue; + } + + // Output the data values. + + fprintf( pCsvFile, "%d," , uTimerAdd + pLog[EVENT_TIMER] ); + fprintf( pCsvFile, "%d," , uBlankAdd + pLog[EVENT_BLANK] ); + fprintf( pCsvFile, "%d," , uFrameAdd + pLog[EVENT_FRAME] ); + fprintf( pCsvFile, "%d," , (unsigned) pLog[EVENT_TICK] ); + fprintf( pCsvFile, "%d," , (unsigned) pLog[EVENT_LEFT] ); + fprintf( pCsvFile, "%d," , (int) *((int8_t *) (pLog + EVENT_WRONG)) ); + fprintf( pCsvFile, "%d," , (unsigned) pLog[EVENT_DELAY] ); + + // Output the event type. + + switch (pLog[EVENT_TYPE]) + { + case INFO_VBLANK: + fprintf( pCsvFile, "VBLANK IRQ\n" ); + break; + + case INFO_WAITVBL: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Wait for VBLANK\n" ); + break; + + case INFO_SCSIRMV: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Flush byte from queue\n" ); + break; + + case INFO_SCSIIDLE: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Idle: Awaiting command\n" ); + break; + + case INFO_SCSISEND: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Sending command\n" ); + break; + + case INFO_DATAEND: + fprintf( pCsvFile, "Finished reading Sector\n" ); + break; + + case INFO_FILLEND: + fprintf( pCsvFile, "ADPCM Prefill Completed\n\n" ); + break; + + case INFO_SEEK_BEGIN: + fprintf( pCsvFile, "Movie finished; System Card cd_seek\n" ); + break; + + case INFO_SEEK_ENDED: + fprintf( pCsvFile, "Movie finished; System Card cd_seek completed\n" ); + break; + + case INFO_NOTBUSY: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Idle: Queue flushed\n" ); + break; + + case INFO_SCSI80: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Got SCSI $80 (PHASE_DATA_OUT without REQ)\n" ); + break; + + case INFO_SCSI88: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Got SCSI $88 (PHASE_DATA_IN without REQ)\n" ); + break; + + case INFO_SCSI98: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Got SCSI $98 (PHASE_STAT_IN without REQ)\n" ); + break; + + case REQ_DATA_OUT: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: REQ asserted beginning PHASE_DATA_OUT\n" ); + break; + + case REQ_DATA_IN: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: REQ asserted beginning PHASE_DATA_IN\n" ); + break; + + case REQ_STAT_IN: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: REQ asserted beginning PHASE_STAT_IN\n" ); + break; + + case RMV_COMMAND: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Flush byte from queue during PHASE_COMMAND\n" ); + break; + + case RMV_DATA_OUT: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Flush byte from queue during PHASE_DATA_OUT\n" ); + break; + + case RMV_DATA_IN: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Flush byte from queue during PHASE_DATA_IN\n" ); + break; + + case RMV_STAT_IN: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Flush byte from queue during PHASE_STAT_IN\n" ); + break; + + case RMV_MESG_OUT: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Flush byte from queue during PHASE_MESG_OUT\n" ); + break; + + case RMV_MESG_IN: + fprintf( pCsvFile, "Send SCSI command; CD-ROM Busy: Flush byte from queue during PHASE_MESG_IN\n" ); + break; + + case PHASE_DATA_IN: + uThisData = uTimerAdd + pLog[EVENT_TIMER]; + if ((uThisData - uLastData) > 18) + { + fprintf( pCsvFile, "Wait for next Sector; Got SCSI PHASE_DATA_IN; %dms since last sector!\n", uThisData - uLastData ); + } + else + { + fprintf( pCsvFile, "Wait for next Sector; Got SCSI PHASE_DATA_IN\n" ); + } + uLastData = uThisData; + break; + + case PHASE_STAT_IN: + fprintf( pCsvFile, "Wait for next Sector; Got SCSI PHASE_STAT_IN\n" ); + break; + + case INFO_EXIT: + fprintf( pCsvFile, "Movie finished!\n" ); + break; + + default: + printf( "Unknown event type 0x%2X in data!\n", pLog[EVENT_TYPE] ); + fprintf( pCsvFile, "Unknown event type 0x%2X in data!\n", pLog[EVENT_TYPE] ); + fclose( pCsvFile ); + exit( 1 ); + } + + if (pLog[EVENT_TYPE] == INFO_EXIT) break; + } + + fclose( pCsvFile ); + + // + + printf( "Finished writing SHERLOCK.CSV\n" ); + + return( 0 ); +} diff --git a/examples/asm/elmer/ted2-test-sherlock/sherlock-hack.asm b/examples/asm/elmer/ted2-test-sherlock/sherlock-hack.asm new file mode 100644 index 00000000..43fd3661 --- /dev/null +++ b/examples/asm/elmer/ted2-test-sherlock/sherlock-hack.asm @@ -0,0 +1,595 @@ +; *************************************************************************** +; *************************************************************************** +; +; sherlock-hack.asm +; +; This patches the "Sherlock Holmes Consulting Detective" executable code to +; add a 1ms timer and a lot of event-logging to unused SuperCD memory banks. +; +; A copy of Sherlock's executable code must exist in this directory with the +; filename "sherlock-boot.bin". (Size = 22,528 bytes with CRC32 = $35AE4D73) +; +; Copyright John Brandwood 2024. +; +; Distributed under the Boost Software License, Version 1.0. +; (See accompanying file LICENSE_1_0.txt or copy at +; http://www.boost.org/LICENSE_1_0.txt) +; +; *************************************************************************** +; *************************************************************************** +; +; If you are using an emulator, then you can run the game normally but put a +; breakpoint at address $2A00. +; +; When the breakpoint is hit, just load "sherlock-hack.ovl" at $2A00 and run +; the code. +; +; Sherlock's introductory movie should immediately play, with a log of event +; information being written to banks $6A..$81. +; +; When the movie has completed, the game will hang on the next screen, which +; allows you to enter your emulator's debugger and save the contents of bank +; $6A..$81 ($0D4000..$103FFF physical) as the SHERLOCK.DAT file. +; +; Then run the "dat2csv" program in the same directory as the SHERLOCK.DAT +; file and it will output the human-readable SHERLOCK.CSV spreadsheet file. +; +; *************************************************************************** +; *************************************************************************** + + + +; *************************************************************************** +; *************************************************************************** +; +; Use the original Sherlock Holmes boot program as a base for testing. +; + + include "pcengine.inc" + + .bank 0 ; Bank $F8 at $2000-$3FFF. + .page 1 + + .bank 1 ; Bank $82 at $4000-$5FFF. + .page 2 + + .bank 2 ; Bank $83 at $6000-$7FFF. + .page 3 + + .bank 3 ; Bank $84 at $8000-$9FFF. + .page 4 + + .org $8000 ; Sherlock loads $2A00-$81FF. + ds $0A00 ; Clear space to $89FF. + + .bank 0 + .org $2A00 + incbin "sherlock-boot.bin" ; Load 11 sector boot code. + + ; + ; Sherlock's internal playback variables. + ; + +which_screen = $F8:2008 ; $00=VRAM $0800, $FF=VRAM $1CA0 +sector_counter = $F8:2009 ; #vram sectors left to read +sectors_remain = $F8:200A ; #sectors left to read in SCSI read command, send new SCSI when 0. + ; +video_wrong = $F8:200D ; cumulative #ticks playback is out +frame_wrong = $F8:200E ; #ticks taken this frame - 6 (nominal playback #ticks taken) +delay_ticks = $F8:200F ; #ticks to delay (if video is running too fast) + +tick_count = $F8:2026 ; reset at the start of every video frame + +frame_num_lo = $F8:2047 ; current movie frame number +frame_num_hi = $F8:2048 ; + +movie_lba_l = $F8:26CA +movie_lba_m = $F8:26CB +movie_lba_h = $F8:26CC + + ; + ; Use previously-unused locations in Sherlock for instrumentation. + ; + +timer_val = $F8:20C0 +info_addr = $F8:20C1 + + ; + ; + ; + +INFO_VBLANK = 1 + +INFO_WAITVBL = 2 + +INFO_SCSIRMV = 3 +INFO_SCSIIDLE = 4 +INFO_SCSISEND = 5 + +INFO_DATAEND = 6 +INFO_FILLEND = 7 + +INFO_SEEK_BEGIN = 10 +INFO_SEEK_ENDED = 11 + +INFO_NOTBUSY = 12 + +INFO_SCSI80 = $80 ; PHASE_DATA_OUT (CD-ROM uses this after a command is received) +INFO_SCSI88 = $88 ; PHASE_DATA_IN +INFO_SCSI98 = $98 ; PHASE_STAT_IN + +;PHASE_COMMAND = $D0 ; (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + ........) +;PHASE_DATA_OUT = $C0 ; (SCSI_BSY + SCSI_REQ + ........ + ........ + ........) +;PHASE_DATA_IN = $C8 ; (SCSI_BSY + SCSI_REQ + ........ + ........ + SCSI_IXO) +;PHASE_STAT_IN = $D8 ; (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + SCSI_IXO) +;PHASE_MESG_OUT = $F0 ; (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + ........) +;PHASE_MESG_IN = $F8 ; (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + SCSI_IXO) + +;REQ_DATA_OUT = ($C0+1) ; (SCSI_BSY + SCSI_REQ + ........ + ........ + ........) +;REQ_DATA_IN = ($C8+1) ; (SCSI_BSY + SCSI_REQ + ........ + ........ + SCSI_IXO) +;REQ_STAT_IN = ($D8+1) ; (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + SCSI_IXO) + +;RMV_COMMAND = ($D0+2) ; (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + ........) +;RMV_DATA_OUT = ($C0+2) ; (SCSI_BSY + SCSI_REQ + ........ + ........ + ........) +;RMV_DATA_IN = ($C8+2) ; (SCSI_BSY + SCSI_REQ + ........ + ........ + SCSI_IXO) +;RMV_STAT_IN = ($D8+2) ; (SCSI_BSY + SCSI_REQ + ........ + SCSI_CXD + SCSI_IXO) +;RMV_MESG_OUT = ($F0+2) ; (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + ........) +;RMV_MESG_IN = ($F8+2) ; (SCSI_BSY + SCSI_REQ + SCSI_MSG + SCSI_CXD + SCSI_IXO) + +INFO_EXIT = $7F + + ; + ; Hack Sherlock's code to add the instrumentation. + ; + + .list + .mlist + + .bank 0 + + .org $2A29 ; Disable System Card joypad. + ora #$30 + + .org $2A54 ; Replace joypad read with tracking. + jsr track_vbl + + .org $2B8B ; Start intro video and recording + jmp hijack_start ; when the game code executes. + + .bank 2 + + .org $7A10 ; Stop recording when the video ends. + jmp hijack_end + + ; + ; play_movie() routine, after movie has completed + ; + + .bank 0 + .org $328D + jsr begin_seek + + .org $3290 + jsr ended_seek + + ; + ; finish_movie() + ; + + .bank 0 + .org $31CE + jsr start_recording + + ; + ; scsi_send_cmd() and scsi_next_cmd() + ; + + .bank 0 + .org $3ECE + jsr delay_next_read + + .org $3EF6 + jsr scsi_flush_byte + + .org $3F07 + jsr scsi_is_idle + + .org $3F1D + jsr scsi_cmd_out + + .org $3F4D + jsr scsi_cmd_end + + .org $3F6F + jsr scsi_got_req + + ; + ; play_movie_loop() + ; + + .bank 0 + .org $3FCE + jsr end_then_wait + + .org $3FF7 + jsr end_then_wait + + .bank 1 + .org $40C9 + jsr end_of_frame + + .org $41BF + jsr end_then_wait + + .org $42A2 + jmp scsi_got_phase + + ; + ; prefill_adpcm() + ; + + .bank 1 + .org $43F9 + jsr end_then_wait + + .org $43FE + jmp prefill_end + + ; + ; There is LOTS of unused space at the end of the Sherlock code. + ; + + .bank 2 + .org $7C80 + + ; + ; 1ms timer interrupt. + ; + +timer_irq: stz IRQ_ACK ; Clear timer interrupt. + inc 7144 cycles -> 1.00124457ms + sta TIMER_DR + + jmp $2DA0 ; Play intro video at boot. + + ; + ; + ; + +start_recording:stz irq_cnt ; Reset IRQ count. + lda.h #$C000 ; Start recording now. + sta.h (CHR_0x10 * 16) + sta <_di + 1 + + lda #$FF ; Put font in colors 4-7, + sta <_al ; so bitplane 2 = $FF and + stz <_ah ; bitplane 3 = $00. + + lda #16 + 96 ; 16 box graphics + 96 ASCII. + sta <_bl + + lda #my_font + sta <_bp + 1 + ldy #^my_font + + call dropfnt8x8_vdc ; Upload font to VRAM. + + ; Upload the palette data to the VCE. + + stz <_al ; Start at palette 0 (BG). + lda #1 ; Copy 1 palette of 16 colors. + sta <_ah + lda #cpc464_colors + sta <_bp + 1 + ldy #^cpc464_colors + call load_palettes ; Add to the palette queue. + + call xfer_palettes ; Transfer queue to VCE now. + + ; Turn on the BG & SPR layers, then wait for a soft-reset. + + call set_dspon ; Enable background. + + call wait_vsync ; Wait for vsync. + + ; Reset the CD-ROM, because Sherlock leaves it in a mess! + + system cd_reset + + ; Save Sherlock's log data to the SDcard. + + jsr save_data ; + + ; Wait for user to reboot. + + lda ted2_unlocked ; If running on a TED2, then + bne .hang_usb ; allow for a USB upload. + +.hang: call wait_vsync ; Wait for vsync, or xfer. + + bra .hang ; Wait for user to reboot. + +.hang_usb: call wait_vsync_usb ; Wait for vsync, or xfer. + + bra .hang_usb ; Wait for user to reboot. + + + +; *************************************************************************** +; *************************************************************************** +; +; save_data - Save the data in memory to /TBED/SHERLOCK.DAT on the SDcard +; + +LOG_BANK = $6A +LOG_SIZE = ($82 - $6A) * $2000 + +save_data: PRINTF "\x1B<\eP0\eXL1\nInitializing Turbo Everdrive v2.\n" + + ; Unlock the TED2 hardware, which takes over bank $00. + + call unlock_ted2 ; Unlock the TED2 hardware. + beq .ted2_ok + +.ted2_fail: PRINTF "Turbo Everdrive v2 not found!\n" + jmp .finished + +.ted2_ok: PRINTF "Turbo Everdrive v2 found and unlocked!\n" + + ; Checksum the log data. + + PRINTF "Calculating CRC32 of data.\n" + + call init_crc32 ; Initialize the CRC32 tables. + + lda.l #LOG_SIZE + sta.l <_ax ; L-byte of #bytes to checksum. + lda.h #LOG_SIZE + sta.h <_ax ; M-byte of #bytes to checksum. + lda #LOG_SIZE >> 16 + sta <_ax + 2 ; H-byte of #bytes to checksum. + stz.l <_bp ; Checksum the log data. + lda #$60 + sta.h <_bp + ldy #LOG_BANK + call calc_crc32 ; Calculate the CRC32. + + tii _cx, crc_value, 4 + + PRINTF "The CRC32 of the data is $%0lx.\n\n", crc_value + + ; Initialize and mount the SDcard. + + PRINTF "Initializing SD card.\n" + + call f32_mount_vol ; Mount the SDcard FAT-32. + beq .fat32_ok + +.fat32_fail: PRINTF "FAT32 parition not found!\n" + jmp .finished + +.fat32_ok: PRINTF "FAT32 parition mounted.\n\n" + + ; Select "/TBED/" directory. + + PRINTF "Selecting SDcard /TBED/ directory.\n" + + call f32_select_root ; Start back at the root. + bne .cd_fail + + lda #<.dirname ; Locate the 'TBED' folder. + sta.l <_bp + lda #>.dirname + sta.h <_bp + ldy #^.dirname + call f32_find_name + bne .cd_fail + + call f32_change_dir ; Select the directory. + beq .cd_ok + +.cd_fail: PRINTF "Directory change failed!\n" + jmp .finished + +.dirname: db "TBED",0 + +.cd_ok: PRINTF "Directory selected.\n" + + ; Open the "SHERLOCK.DAT" file. + + PRINTF "Writing file /TBED/SHERLOCK.DAT from memory.\n" + + lda #<.filename ; Locate the file. + sta.l <_bp + lda #>.filename + sta.h <_bp + ldy #^.filename + call f32_find_name ; Locate the named file in + bne .write_fail ; the current directory. + + call f32_open_file ; Open the file, after which + bne .write_fail ; f32_cache_buf free for use! + + ; Overwrite the "SHERLOCK.DAT" file from memory. + + lda.l #LOG_SIZE / 512 ; Or look at f32_file_length! + sta.l <_ax ; Lo-byte of # blocks in file. + lda.h #LOG_SIZE / 512 + sta.h <_ax ; Hi-byte of # blocks in file. + + lda #LOG_BANK ; First unused bank, reported + tam3 ; by PCEAS! + stz.l <_bx ; Save from MPR3, which will + lda.h #$6000 ; autoincrement the bank# as + sta.h <_bx ; necessary. + + call f32_file_write ; Write the file from memory. + bne .write_fail + + call f32_close_file ; Close the file & set N & Z. + beq .write_ok + +.write_fail: pha ; Preserve error code. + call f32_close_file ; Close the file & set N & Z. + + PRINTF "File write failed!\n" + pla ; Restore error code. + jmp .finished + +.write_ok: PRINTF "File write complete.\n" + + ; Open the "SHERLOCK.DAT" file. + + PRINTF "Reading file /TBED/SHERLOCK.DAT into memory.\n" + + lda #<.filename ; Locate the file. + sta.l <_bp + lda #>.filename + sta.h <_bp + ldy #^.filename + call f32_find_name ; Locate the named file in + bne .read_fail ; the current directory. + + call f32_open_file ; Open the file, after which + bne .read_fail ; f32_cache_buf free for use! + + ; Read the "SHERLOCK.DAT" file into memory. + + lda.l #LOG_SIZE / 512 ; Or look at f32_file_length! + sta.l <_ax ; Lo-byte of # blocks in file. + lda.h #LOG_SIZE / 512 + sta.h <_ax ; Hi-byte of # blocks in file. + + lda #$40 ; Read file into TED2 memory. + tam3 + stz.l <_bx ; Load into MPR3, which will + lda #$60 ; autoincrement the bank# as + sta.h <_bx ; necessary. + + call f32_file_read ; Read the file into memory. + bne .read_fail + + call f32_close_file ; Close the file & set N & Z. + beq .read_ok + +.read_fail: pha ; Preserve error code. + call f32_close_file ; Close the file & set N & Z. + + PRINTF "File read failed!\n" + pla ; Restore error code. + jmp .finished + +.filename: db "SHERLOCK.DAT",0 + +.read_ok: PRINTF "File read complete.\n" + + ; Checksum the log file. + + PRINTF "Calculating CRC32 of file.\n" + + call init_crc32 ; Initialize the CRC32 tables. + + lda.l #LOG_SIZE + sta.l <_ax ; L-byte of #bytes to checksum. + lda.h #LOG_SIZE + sta.h <_ax ; M-byte of #bytes to checksum. + lda #LOG_SIZE >> 16 + sta <_ax + 2 ; H-byte of #bytes to checksum. + stz.l <_bp ; Checksum 32KB from $6000. + lda #$60 + sta.h <_bp + ldy #$40 ; Test file from TED2 memory. + call calc_crc32 ; Calculate the CRC32. + + tii _cx, crc_value, 4 + + PRINTF "The CRC32 of the file is $%0lx.\n\n", crc_value + + ; All Done! + +.finished: rts + + + +; *************************************************************************** +; *************************************************************************** +; +; The "CORE(not TM)" library initializes .DATA, so we don't do it again! +; +; *************************************************************************** +; *************************************************************************** + + .data + + + +; *************************************************************************** +; *************************************************************************** +; +; cpc464_colors - Palette data (a blast-from-the-past!) +; +; Note: DEFPAL palette data is in RGB format, 4-bits per value. +; Note: Packed palette data is in GRB format, 3-bits per value. +; +; $0 = transparent +; $1 = dark blue shadow +; $2 = white font +; +; $4 = dark blue background +; $5 = light blue shadow +; $6 = yellow font +; + +none = $000 + + align 2 + +cpc464_colors: defpal $000,$001,$662,none,$002,$114,$551,none + defpal none,none,none,none,none,none,none,none + + + +; *************************************************************************** +; *************************************************************************** +; +; It's the font data, nothing exciting to see here! +; + +my_font: incbin "font8x8-ascii-bold-short.dat" + + + +; *************************************************************************** +; *************************************************************************** +; +; Finally, include the hacked Sherlock CD boot-program image in the correct +; bank/address so that it can be executed just by paging it in to memory. +; + + .bank $81 - $68 ; Sherlock runs in bank $81. + + .org $2A00 + incbin "sherlock-hack.ovl" + + ds $2000 - (* & $1FFF) ; Clear to end of bank. From ac6753fa179531bb8023d8243d8f4572673b9d97 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Tue, 23 Jan 2024 10:08:50 -0500 Subject: [PATCH 28/40] Allow .BANK/.PAGE/.ORG within a .PROC if not in a .CODE section. .PROC already allows changing (temporarily) to .DATA/.BSS, this adds more flexibility for defining data within a procedure. --- src/mkit/as/command.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mkit/as/command.c b/src/mkit/as/command.c index 0e5abf6b..6a4365f2 100644 --- a/src/mkit/as/command.c +++ b/src/mkit/as/command.c @@ -747,8 +747,8 @@ void do_page(int *ip) { /* not allowed in procs */ - if (proc_ptr) { - fatal_error("PAGE can not be changed in procs!"); + if (proc_ptr && (section == S_CODE)) { + fatal_error("Code PAGE can not be changed within a .proc!"); return; } @@ -812,8 +812,8 @@ do_org(int *ip) case S_CODE: case S_DATA: /* not allowed in procs */ - if (proc_ptr) { - fatal_error("ORG can not be changed in procs!"); + if (proc_ptr && (section == S_CODE)) { + fatal_error("Code ORG can not be changed within a .proc!"); return; } @@ -855,8 +855,8 @@ do_bank(int *ip) char name[128]; /* not allowed in procs */ - if (proc_ptr) { - fatal_error("Bank can not be changed in procs!"); + if (proc_ptr && (section == S_CODE)) { + fatal_error("Code BANK can not be changed within a .proc!"); return; } From b8edc7481cf0533932c928674c686e6c73d41345 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 26 Jan 2024 18:57:36 -0500 Subject: [PATCH 29/40] Fix missing dependencies in a couple of Makefiles. --- examples/asm/elmer/cd-core-2stage/Makefile | 2 +- examples/asm/elmer/scd-core-2stage-error/Makefile | 4 ++-- examples/asm/elmer/scd-core-2stage/Makefile | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/asm/elmer/cd-core-2stage/Makefile b/examples/asm/elmer/cd-core-2stage/Makefile index 2b16c454..a673db58 100644 --- a/examples/asm/elmer/cd-core-2stage/Makefile +++ b/examples/asm/elmer/cd-core-2stage/Makefile @@ -7,7 +7,7 @@ AFLAGS ?= -newproc -strip -m -l 2 -S -overlay SRC1_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm SRC1_OVL = core-stage1.asm core-config.inc -SRC2_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm +SRC2_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm unpack-zx0.asm SRC2_OVL = stage2.asm core-config.inc SRC_ISO = core-stage1.bin stage2.ovl diff --git a/examples/asm/elmer/scd-core-2stage-error/Makefile b/examples/asm/elmer/scd-core-2stage-error/Makefile index b718d3f1..b1b8aab0 100644 --- a/examples/asm/elmer/scd-core-2stage-error/Makefile +++ b/examples/asm/elmer/scd-core-2stage-error/Makefile @@ -7,10 +7,10 @@ AFLAGS ?= -newproc -strip -m -l 2 -S -overlay SRC1_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm SRC1_OVL = core-stage1.asm core-config.inc -SRC2_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm +SRC2_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm unpack-zx0.asm SRC2_OVL = stage2.asm core-config.inc -ERR_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm +ERR_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm unpack-zx0.asm ERR_OVL = error.asm core-config.inc saz_vdc.zx0 saz_vce.zx0 SRC_ISO = core-stage1.bin stage2.ovl diff --git a/examples/asm/elmer/scd-core-2stage/Makefile b/examples/asm/elmer/scd-core-2stage/Makefile index 3d0b8339..8fa52eaa 100644 --- a/examples/asm/elmer/scd-core-2stage/Makefile +++ b/examples/asm/elmer/scd-core-2stage/Makefile @@ -7,7 +7,7 @@ AFLAGS ?= -newproc -strip -m -l 2 -S -overlay SRC1_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm SRC1_OVL = core-stage1.asm core-config.inc -SRC2_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm +SRC2_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm unpack-zx0.asm SRC2_OVL = stage2.asm core-config.inc SRC_ISO = core-stage1.bin stage2.ovl From 5b48461a93ca82b43fc2e000bd073f49e67e9dbd Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 26 Jan 2024 18:58:36 -0500 Subject: [PATCH 30/40] More tiny cleanups in cdrom.asm --- examples/asm/elmer/include/cdrom.asm | 43 +++++----------------------- 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/examples/asm/elmer/include/cdrom.asm b/examples/asm/elmer/include/cdrom.asm index c7696648..57225aa3 100644 --- a/examples/asm/elmer/include/cdrom.asm +++ b/examples/asm/elmer/include/cdrom.asm @@ -325,8 +325,7 @@ cdr_cplay_irq2: pha .got_int_stop: lda #IFU_INT_END ; Disable IFU_INT_END. trb IFU_IRQ_MSK - lda #$01 ; Disable IRQ2 vector. - trb Date: Fri, 26 Jan 2024 18:59:24 -0500 Subject: [PATCH 31/40] Add some more comments on register usage in common.asm --- examples/asm/elmer/include/common.asm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/asm/elmer/include/common.asm b/examples/asm/elmer/include/common.asm index 1ffe527b..37107c02 100644 --- a/examples/asm/elmer/include/common.asm +++ b/examples/asm/elmer/include/common.asm @@ -63,6 +63,8 @@ wait_nvsync: bsr wait_vsync ; # of VBLANK IRQs to wait in ; banks, with the 2nd bank having no specific relation to the 1st, there ; is no way to deal with a bank-increment, so do not map that region. ; +; N.B. Library code relies on this preserving X! +; set_bp_to_mpr3: lda.h <_bp ; Do not remap a ptr to RAM, cmp #$60 ; which is $2000-$5FFF. @@ -93,6 +95,8 @@ set_bp_to_mpr34:lda.h <_bp ; Do not remap a ptr to RAM, ; ; Increment the hi-byte of _bp and change TMA3 if necessary. ; +; N.B. Library code relies on this preserving A,X,Y! +; inc.h_bp_mpr3: inc.h <_bp ; Increment hi-byte of _bp. bpl !+ ; OK if within MPR0-MPR3. @@ -111,6 +115,8 @@ inc.h_bp_mpr3: inc.h <_bp ; Increment hi-byte of _bp. ; *************************************************************************** ; ; Increment the hi-byte of _bp and change TMA3 and TMA4 if necessary. +; +; N.B. Library code relies on this preserving A,X,Y! ; .if 1 ; Save memory, for now. From 04e6e422bf639e61189857a3755c718dceae2fe2 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 26 Jan 2024 19:00:34 -0500 Subject: [PATCH 32/40] Change tty.asm to use the MPR3 banking subroutine for consistency. --- examples/asm/elmer/include/tty.asm | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/asm/elmer/include/tty.asm b/examples/asm/elmer/include/tty.asm index c9cd2af1..052dea50 100644 --- a/examples/asm/elmer/include/tty.asm +++ b/examples/asm/elmer/include/tty.asm @@ -297,13 +297,9 @@ tty_printf_huc .proc ; HuC entry point. tma3 ; Preserve MPR3. pha - tya ; Map farptr to MPR3. - beq !+ - tam3 - inc a - tam4 + jsr set_bp_to_mpr34 ; Map farptr to MPR3 & MPR4. -!: stz tty_xyok ; Make sure VRAM addr is set! + stz tty_xyok ; Make sure VRAM addr is set! lda [_bp] ; Get string length from the inc a ; PASCAL-format string, the From 1ae2c91ffbe6e4c5d22aad4796507ca5a9c36c57 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 26 Jan 2024 19:01:50 -0500 Subject: [PATCH 33/40] Fix stupid typo in pcengine.inc --- examples/asm/elmer/include/pcengine.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/asm/elmer/include/pcengine.inc b/examples/asm/elmer/include/pcengine.inc index 37029011..a8b030e2 100644 --- a/examples/asm/elmer/include/pcengine.inc +++ b/examples/asm/elmer/include/pcengine.inc @@ -198,7 +198,7 @@ IFU_AUDIO_FADE = $FF:180F ; RW : IFU audio fade control/status. IFU_BRAM_LOCK = $FF:1803 ; _W : IFU_BRAM_UNLOCK = $FF:1807 ; _W : -; IFU interrupt bits in IFU_IRQ_CTL/IFU_IRQ_FLG +; IFU interrupt bits in IFU_IRQ_MSK/IFU_IRQ_FLG IFU_INT_HALF = $04 ; Set if ADPCM LENGTH <= $7FFF. IFU_INT_END = $08 ; Set if ADPCM LENGTH == 0 (when ADPCM_AUTO). From 8e63ff1d4bdfc256ecb08400eaf5f3489f39639f Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Fri, 26 Jan 2024 19:07:52 -0500 Subject: [PATCH 34/40] Add cd-core-scsitest example project investigating how the CD-ROM works. --- examples/asm/elmer/Makefile | 3 +- examples/asm/elmer/README.md | 3 + examples/asm/elmer/cd-core-scsitest/Makefile | 19 + .../elmer/cd-core-scsitest/core-scsitest.cue | 3 + .../asm/elmer/cd-core-scsitest/scsitest.asm | 1636 +++++++++++++++++ 5 files changed, 1663 insertions(+), 1 deletion(-) create mode 100644 examples/asm/elmer/cd-core-scsitest/Makefile create mode 100644 examples/asm/elmer/cd-core-scsitest/core-scsitest.cue create mode 100644 examples/asm/elmer/cd-core-scsitest/scsitest.asm diff --git a/examples/asm/elmer/Makefile b/examples/asm/elmer/Makefile index 73e40439..43726863 100644 --- a/examples/asm/elmer/Makefile +++ b/examples/asm/elmer/Makefile @@ -5,7 +5,8 @@ PREREQS = ipl-scd SUBDIRS = rom-bare-buftest rom-bare-mwrtest rom-bare-rcrtest rom-bare-tiatest \ rom-core-hello rom-core-okitest \ scd-bios-hello scd-bios-hello-error \ - cd-core-1stage cd-core-2stage scd-core-1stage scd-core-1stage-error \ + cd-core-1stage cd-core-2stage cd-core-scsitest \ + scd-core-1stage scd-core-1stage-error \ scd-core-2stage scd-core-2stage-error scd-core-fastcd \ rom-kickc-hello rom-kickc-shmup ted2-core-hwdetect ted2-core-sdcard \ ted2-core-gulliver diff --git a/examples/asm/elmer/README.md b/examples/asm/elmer/README.md index b8e1424d..5b1b163a 100644 --- a/examples/asm/elmer/README.md +++ b/examples/asm/elmer/README.md @@ -53,6 +53,9 @@ The "CORE(not TM)" PC Engine library is a small and configurable set of library * cd-core-2stage - A simple example of creating an ISO for a CD-ROM, and loading the "CORE(not TM)" kernel code in a startup overlay. +* cd-core-scsitest + - A CD-ROM and a HuCARD for testing some low-level details of the PC Engine's SCSI CD-ROM behavior. + * scd-core-1stage - A simple example of creating an ISO for a Super CD-ROM, and loading the "CORE(not TM)" kernel code from the overlay program. diff --git a/examples/asm/elmer/cd-core-scsitest/Makefile b/examples/asm/elmer/cd-core-scsitest/Makefile new file mode 100644 index 00000000..f24643fe --- /dev/null +++ b/examples/asm/elmer/cd-core-scsitest/Makefile @@ -0,0 +1,19 @@ +all: core-scsitest.iso scsitest.pce + +include ../Make_ex.inc + +AFLAGS ?= -newproc -strip -m -l 2 -S + +SRC_INC = pceas.inc pcengine.inc core.inc core-startup.asm core-kernel.asm joypad.asm adpcm.asm cdrom.asm +SRC_OVL = scsitest.asm core-config.inc + +SRC_ISO = scsitest.ovl ../data/alice.vdc ../data/alice.ram ../data/umbrella-16k.vox + +core-scsitest.iso: $(SRC_ISO) + $(IL) core-scsitest.iso -ipl="test-CD CD-ROM2",0x4000,0x4000,0,1,2,3,4 -asm $(SRC_ISO) + +scsitest.ovl: $(SRC_OVL) $(SRC_INC) scsitest.pce + $(AS) $(AFLAGS) -cd -overlay scsitest.asm + +scsitest.pce: $(SRC_OVL) $(SRC_INC) + $(AS) $(AFLAGS) scsitest.asm diff --git a/examples/asm/elmer/cd-core-scsitest/core-scsitest.cue b/examples/asm/elmer/cd-core-scsitest/core-scsitest.cue new file mode 100644 index 00000000..5652adee --- /dev/null +++ b/examples/asm/elmer/cd-core-scsitest/core-scsitest.cue @@ -0,0 +1,3 @@ +FILE core-scsitest.iso BINARY + TRACK 01 MODE1/2048 + INDEX 01 00:00:00 diff --git a/examples/asm/elmer/cd-core-scsitest/scsitest.asm b/examples/asm/elmer/cd-core-scsitest/scsitest.asm new file mode 100644 index 00000000..28981cd1 --- /dev/null +++ b/examples/asm/elmer/cd-core-scsitest/scsitest.asm @@ -0,0 +1,1636 @@ +; *************************************************************************** +; *************************************************************************** +; +; scsitest.asm +; +; Testing program to investigate the PC Engine's SCSI CD-ROM behavior. +; +; This could be used by emulator authors to compare a real PCE vs emulation. +; +; Copyright John Brandwood 2024. +; +; Distributed under the Boost Software License, Version 1.0. +; (See accompanying file LICENSE_1_0.txt or copy at +; http://www.boost.org/LICENSE_1_0.txt) +; +; *************************************************************************** +; *************************************************************************** +; +; When running on a HuCARD the setup is standard for the CORE library. +; +; The PC Engine's memory map is set to ... +; +; MPR0 = bank $FF : PCE hardware +; MPR1 = bank $F8 : PCE RAM with Stack & ZP +; MPR2 = bank $00 : HuCard ROM +; MPR3 = bank $01 : HuCard ROM +; MPR4 = bank $02 : HuCard ROM +; MPR5 = bank $03 : HuCard ROM +; MPR6 = bank $xx : HuCard ROM banked ASM library procedures. +; MPR7 = bank $00 : HuCard ROM with "CORE(not TM)" kernel and IRQ vectors. +; +; If it is running on an old CD-ROM2 System the overlay is loaded from the +; ISO into banks $80-$87 (64KB max). +; +; The PC Engine's memory map is set to ... +; +; MPR0 = bank $FF : PCE hardware +; MPR1 = bank $F8 : PCE RAM with Stack & ZP & "CORE(not TM)" kernel +; MPR2 = bank $80 : CD RAM +; MPR3 = bank $81 : CD RAM +; MPR4 = bank $82 : CD RAM +; MPR5 = bank $83 : CD RAM +; MPR6 = bank $84 : CD RAM +; MPR6 = bank $xx : CD RAM banked ASM library procedures. +; MPR7 = bank $80 : CD RAM or System Card's bank $00 +; +; *************************************************************************** +; *************************************************************************** + + + ; + ; Create some equates for a very generic VRAM layout, with a + ; 64*32 BAT, followed by the SAT, then followed by the tiles + ; for the ASCII character set. + ; + ; This uses the first 8KBytes of VRAM ($0000-$0FFF). + ; + +BAT_LINE = 64 +BAT_SIZE = 64 * 32 +SAT_ADDR = BAT_SIZE ; SAT takes 16 tiles of VRAM. +CHR_ZERO = BAT_SIZE / 16 ; 1st tile # after the BAT. +CHR_0x10 = CHR_ZERO + 16 ; 1st tile # after the SAT. +CHR_0x20 = CHR_ZERO + 32 ; ASCII ' ' CHR tile #. + + ; + ; Include the library, reading the project's configuration + ; settings from the local "core-config.inc", if it exists. + ; + + include "core.inc" ; Basic includes. + + .list + .mlist + + include "common.asm" ; Common helpers. + include "vdc.asm" ; Useful VDC routines. + include "font.asm" ; Useful font routines. + include "tty.asm" ; Useful TTY print routines. + include "cdrom.asm" ; Fast CD-ROM routines. + + + +; *************************************************************************** +; *************************************************************************** +; +; Constants and Variables. +; + + .zp + +timer_val: ds 2 +data_relative: ds 1 ; $80=DATA_IN clears timer. + + .bss + + ; Display variables. + +count_was: ds 2 +timer_was: ds 2 +text_message: ds 80 + +retry_count: ds 2 + +lba_top: ds 1 +lba_add: ds 1 + + ; Hacks to the CD-ROM test_read() to create bad behavior. + ; + ; Neither the System Card nor Hudson's "fast" code ever do + ; stupid things like these, but all the Sherlock Holmes CD + ; games rely on this behavior. + ; + +exit_on_data: ds 1 ; Exit cd_read early? +exit_on_stat: ds 1 ; Exit cd_read early? +exit_on_mesg: ds 1 ; Exit cd_read early? + +delay_data: ds 1 ; Add delay after DATA_IN? + + .code + +; *************************************************************************** +; *************************************************************************** + + + +; *************************************************************************** +; *************************************************************************** +; +; core_main - This is executed after "CORE(not TM)" library initialization. +; +; This is the first code assembled after the library includes, so we're still +; in the CORE_BANK, usually ".bank 0"; and because this is assembled with the +; default configuration from "include/core-config.inc", which sets the option +; "USING_MPR7", then we're running in MPR7 ($E000-$FFFF). +; + + .code + +core_main: ; Turn the display off and initialize the screen mode. + + call init_512x224 ; Initialize VDC & VRAM. + + ; Upload the font to VRAM. + + stz <_di + 0 ; Destination VRAM address. + lda #>(CHR_0x10 * 16) + sta <_di + 1 + + lda #$FF ; Put font in colors 4-7, + sta <_al ; so bitplane 2 = $FF and + stz <_ah ; bitplane 3 = $00. + + lda #16 + 96 ; 16 box graphics + 96 ASCII. + sta <_bl + + lda #my_font + sta <_bp + 1 + ldy #^my_font + + call dropfntbox_vdc ; Upload font to VRAM. + + ; Upload the 8x8 font to VRAM in colors 0-3, with box tiles + ; using colors 4-7. + ; + ; Thus we call dropfntbox_vdc() instead of dropfnt8x8_vdc()! + + stz <_di + 0 ; Destination VRAM address. + lda #>(CHR_0x10 * 16) + sta <_di + 1 + + lda #$FF ; Put font in colors 4-7, + sta <_al ; so bitplane 2 = $FF and + stz <_ah ; bitplane 3 = $00. + + lda #16 + 96 ; 16 graphics + 96 ASCII. + sta <_bl + + lda #my_font + sta <_bp + 1 + ldy #^my_font + + call dropfnt8x8_vdc ; Upload font to VRAM. + + ; Upload the palette data to the VCE. + + stz <_al ; Start at palette 0 (BG). + lda #3 ; Copy 3 palettes of 16 colors. + sta <_ah + lda #cpc464_colors + sta.h <_bp + ldy #^cpc464_colors + call load_palettes ; Add to the palette queue. + + ; Turn on the BG & SPR layers, then wait for a soft-reset. + + setvec vsync_hook, .vblank_irq ; Enable vsync_hook. + smb4 1024 cycles -> 143.034939us + + lda.l #timer_irq ; Install timer_irq vector. + sta.l timer_hook + lda.h #timer_irq + sta.h timer_hook + smb2 = MAX_TAGS) + .fail Too many TAGs, increase MAX_TAGS! + .endif + .data +!string: db (!end+ - !string-) ; PASCAL-style string. + db \1 +!end: db 0 +SAVED_BANK .set bank(*) - _bank_base ; Remember data bank. +SAVED_ADDR .set * ; Remember data addr. + .bank CORE_BANK + .org tagtbl_lo + tag_number + db !string- + .org tagtbl_bk + tag_number + db ^!string- + .bank SAVED_BANK ; Restore data bank. + .org SAVED_ADDR ; Restore data addr. + .code + .endm + +TAG_FLG .macro + TAG \1 + ldy #tag_number + jsr log_flg + .endm + +TAG_A .macro + TAG \1 + ldy #tag_number + jsr log_tag + .endm + + ; + ; Clear the current log entries. + ; + +clear_log: php + sei + nop + stz Date: Mon, 5 Feb 2024 14:31:54 -0500 Subject: [PATCH 35/40] Add a symbol's bank information to sym2inc now that PCEAS supports it. --- examples/asm/elmer/cd-core-scsitest/scsitest.asm | 2 +- src/tools/sym2inc/sym2inc.c | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/asm/elmer/cd-core-scsitest/scsitest.asm b/examples/asm/elmer/cd-core-scsitest/scsitest.asm index 28981cd1..ccd3fd55 100644 --- a/examples/asm/elmer/cd-core-scsitest/scsitest.asm +++ b/examples/asm/elmer/cd-core-scsitest/scsitest.asm @@ -449,7 +449,7 @@ print_log .proc lda.l count_was ; Only repeated once? ora.h count_was beq .unique - + .repeated: inc.l count_was ; Increment the count bne !+ ; for display. inc.h count_was diff --git a/src/tools/sym2inc/sym2inc.c b/src/tools/sym2inc/sym2inc.c index bc8dbb26..e5a46441 100644 --- a/src/tools/sym2inc/sym2inc.c +++ b/src/tools/sym2inc/sym2inc.c @@ -95,6 +95,7 @@ char * strupr ( char *pStr ) // document titled "A Painless Guide to CRC Error Detection Algorithms" // by Ross Williams (ross@guest.adelaide.edu.au.). This document is // likely to be in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". +// static uint32_t s_aCRC32Table [256] = { @@ -172,6 +173,7 @@ static uint32_t s_aCRC32Table [256] = // CalculateCRC32 () // // Basically, it is the CRC32 calculation as used in Ethernet, PKZIP, etc. +// uint32_t CalculateCRC32 ( const void * pData, size_t iSize ) { @@ -203,6 +205,7 @@ uint32_t CalculateCRC32 ( const void * pData, size_t iSize ) // Google "FIO19-C" for the reason why. // // N.B. Will return an error for files larger than 2GB on a 32-bit system. +// bool ReadBinaryFile ( const char * pName, void ** pBuffer, size_t * pLength ) { @@ -259,6 +262,7 @@ bool ReadBinaryFile ( const char * pName, void ** pBuffer, size_t * pLength ) // // Uses POSIX file functions rather than C file functions, just because // it might save some space since ReadBinaryFile() uses POSIX functions. +// bool WriteBinaryFile ( const char * pName, void * pBuffer, size_t iLength ) { @@ -501,6 +505,13 @@ void OutputSymbols ( char * pFileName ) fputs( "=\t$", pFile ); + strupr( pSymbol->pBankString ); + + if (( pSymbol->pBankString[0] != 'F' ) || ( pSymbol->pBankString[1] != '0' )) { + fputs( pSymbol->pBankString, pFile ); + fputs( ":", pFile ); + } + fputs( strupr( pSymbol->pAddrString ), pFile ); fputs( "\n", pFile ); From 8f8b1487acc5b55446c1eefd7e47fab6166cb409 Mon Sep 17 00:00:00 2001 From: John Brandwood Date: Mon, 5 Feb 2024 14:41:36 -0500 Subject: [PATCH 36/40] Add "hulz" tool for compressing/decompressing some of Hudson's LZSS variants. --- src/tools/Makefile | 2 +- src/tools/hulz/Makefile | 42 + src/tools/hulz/elmer.h | 82 ++ src/tools/hulz/hulz.c | 2083 ++++++++++++++++++++++++++++++++++++ src/tools/hulz/targetver.h | 10 + 5 files changed, 2218 insertions(+), 1 deletion(-) create mode 100644 src/tools/hulz/Makefile create mode 100644 src/tools/hulz/elmer.h create mode 100644 src/tools/hulz/hulz.c create mode 100644 src/tools/hulz/targetver.h diff --git a/src/tools/Makefile b/src/tools/Makefile index 305c4175..9b6bdf5f 100644 --- a/src/tools/Makefile +++ b/src/tools/Makefile @@ -2,7 +2,7 @@ # Makefile for HuC tools sources # -SUBDIRS = pcxtool mod2mml mml wav2vox sym2inc pce2png +SUBDIRS = pcxtool mod2mml mml wav2vox sym2inc pce2png hulz all clean: @$(MAKE) $(SUBDIRS) "COMMAND=$@" diff --git a/src/tools/hulz/Makefile b/src/tools/hulz/Makefile new file mode 100644 index 00000000..9e585cae --- /dev/null +++ b/src/tools/hulz/Makefile @@ -0,0 +1,42 @@ +# Makefile for hulz +# +# Written for Linux development version, September 27, 2001 by Dave Shadoff +# + +# +# Defines +# + +BASEDIR=..d ..d .. +include ../../Make_src.inc + +HDRS = elmer.h targetver.h +OBJS = hulz.o + +LIBS = + +EXE = hulz$(EXESUFFIX) + +# +# Subdirectories +# + +all:: + $(MAKE) $(EXE) + +# +# Executable +# + +$(EXE): $(OBJS) $(LIBS) $(HDRS) + $(CC) $(LDFLAGS) $(OBJS) $(LIBS) -o $@ + $(CP) $(EXE) $(BINDIR) + +# +# Targets +# + +include $(MAKETARG) + +clean:: + rm -f $(EXE) $(BINDIR)/$(EXE) $(OBJS) diff --git a/src/tools/hulz/elmer.h b/src/tools/hulz/elmer.h new file mode 100644 index 00000000..811ca43e --- /dev/null +++ b/src/tools/hulz/elmer.h @@ -0,0 +1,82 @@ +// ************************************************************************** +// ************************************************************************** +// +// elmer.h +// +// General type definitions. +// +// Copyright John Brandwood 1994-2021. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// ************************************************************************** +// ************************************************************************** + +#ifndef __ELMER_h +#define __ELMER_h + +#ifdef _WIN32 + #include "targetver.h" + #define WIN32_LEAN_AND_MEAN + #include +#endif + +// +// Standard C99 includes, with Visual Studio workarounds. +// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined (_MSC_VER) + #include + #define SSIZE_MAX UINT_MAX + #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) + #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) + #define strcasecmp _stricmp + #define strncasecmp _strnicmp +#else + #include + #ifndef O_BINARY + #define O_BINARY 0 + #define O_TEXT 0 + #endif +#endif + +#if defined (_MSC_VER) && (_MSC_VER < 1600) // i.e. before VS2010 + #define __STDC_LIMIT_MACROS + #include "msinttypes/stdint.h" +#else + #include +#endif + +#if defined (_MSC_VER) && (_MSC_VER < 1800) // i.e. before VS2013 + #define static_assert( test, message ) + #include "msinttypes/inttypes.h" + #include "msinttypes/stdbool.h" +#else + #include + #include +#endif + +// DETERMINE TARGET MACHINE BYTE ORDERING ... +// +// either BYTE_ORDER_LO_HI for 80x86 ordering, +// or BYTE_ORDER_HI_LO for 680x0 ordering. + +#ifdef PC + #define BYTE_ORDER_LO_HI 1 + #define BYTE_ORDER_HI_LO 0 +#endif + +#endif // __ELMER_h diff --git a/src/tools/hulz/hulz.c b/src/tools/hulz/hulz.c new file mode 100644 index 00000000..068020be --- /dev/null +++ b/src/tools/hulz/hulz.c @@ -0,0 +1,2083 @@ +// ************************************************************************** +// ************************************************************************** +// +// HULZ.C +// +// Simple compressor for some LZSS-derived schemes found on the PC Engine. +// +// Copyright John Brandwood 2014-2024. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// ************************************************************************** +// ************************************************************************** + +#include "elmer.h" + +// +// DEFINITIONS +// + +#ifndef GIT_VERSION + #define GIT_VERSION "unknown" +#endif + +#ifndef GIT_DATE + #define GIT_DATE __DATE__ +#endif + +#define VERSION_STR "hulz (" GIT_VERSION ", " GIT_DATE ")" + +// Which compressor has been chosen? + +typedef uint8_t * (*compressor) ( + uint8_t * pSrcBuffer, + unsigned uSrcLength, + uint8_t * pDstBuffer, + unsigned uDstLength ); + +compressor g_pCompressorFunc = NULL; +compressor g_pDecompressFunc = NULL; + +// + +bool g_fDecompress = false; +bool g_fLazyMatch = true; + +uint8_t * g_pOutBuffer; +unsigned g_uOutLength; + +int g_iWindowFix; + + + +// ************************************************************************** +// ************************************************************************** +// +// BitInit () +// BitWriteHiLo () +// BitWriteLoHi () +// +// Bit-oriented buffered output to global g_pOutBuffer. +// + +unsigned g_uOutMask; +uint8_t * g_pOutBits = NULL; + +void BitInit ( void ) +{ + g_pOutBits = NULL; +} + +void BitWriteHiLo ( unsigned uBitValue ) +{ + if (g_pOutBits == NULL) { + g_uOutMask = 0x80; + g_pOutBits = g_pOutBuffer; + *g_pOutBuffer++ = 0; + } + + if (uBitValue & 1) { + *g_pOutBits |= g_uOutMask; + } + + g_uOutMask >>= 1; + + if (g_uOutMask == 0) { + g_pOutBits = NULL; + } +} + +void BitWriteLoHi ( unsigned uBitValue ) +{ + if (g_pOutBits == NULL) { + g_uOutMask = 0x01; + g_pOutBits = g_pOutBuffer; + *g_pOutBuffer++ = 0; + } + + if (uBitValue & 1) { + *g_pOutBits |= g_uOutMask; + } + + g_uOutMask <<= 1; + + if (g_uOutMask == 256) { + g_pOutBits = NULL; + } +} + + + +// ************************************************************************** +// ************************************************************************** +// +// NibbleInit () +// NibbleWriteHiLo () +// +// Nibble-oriented buffered output to global g_pOutBuffer. +// + +uint8_t * g_pNibble = NULL; + +void NibbleInit ( void ) +{ + g_pNibble = NULL; +} + +void NibbleWriteHiLo ( unsigned uValue ) +{ + if (g_pNibble == NULL) + { + g_pNibble = g_pOutBuffer++; + + *g_pNibble = (uValue & 15) << 4; + } else { + *g_pNibble = (uValue & 15) | *g_pNibble; + + g_pNibble = NULL; + } +} + + + +// ************************************************************************** +// ************************************************************************** +// +// InitStringMatch () +// +// Simple hash-based string search for Lempel-Ziv compression. +// +// HASH_WIN_SIZE is the size of the hash window used to track previous string +// occurrences, do not confuse it with the iMaxLzssDelta used to search for a +// current match! +// +// HASH_WIN_SIZE must be a power-of-two that is >= the largest iMaxLzssDelta. +// +// There is no harm to having a HASH_WIN_SIZE > iMaxLzssDelta, it just takes +// up more memory during compression. +// + +#define HASH_VAL_SIZE 16384 +#define CALC_HASH( iSrcOffset ) ((pSrcBuffer[ iSrcOffset + 0 ] & 0x7F) + 128 * (pSrcBuffer[ iSrcOffset + 1 ] & 0x7F)) + +#define HASH_WIN_SIZE 0x2000 // Use a default 8KB tracking window on the PC Engine. +#define HASH_WIN_MASK (HASH_WIN_SIZE - 1) + +// Array of the most recent source-offset for every possible hash value. + +int * g_pHashOffset; + +// Array of previous source-offset with the same hash value for every window location +// in the tracking window. + +int * g_pPrevOffset; + +// + +void InitStringMatch ( void ) +{ + int i; + + if (g_pHashOffset == NULL) { + g_pHashOffset = (int *) malloc( sizeof(int) * (HASH_VAL_SIZE + HASH_WIN_SIZE) ); + g_pPrevOffset = g_pHashOffset + HASH_VAL_SIZE; + } + + // Initialize the hash table. + + for (i = 0; i < HASH_VAL_SIZE; ++i) { + g_pHashOffset[i] = - 1; + } +} + + + +// ************************************************************************** +// ************************************************************************** +// +// FindStringMatch () +// +// Simple hash-based string search for Lempel-Ziv compression. +// +// HASH_WIN_SIZE is the size of the hash window used to track previous string +// occurrences, do not confuse it with the iMaxLzssDelta used to search for a +// current match! +// +// HASH_WIN_SIZE must be a power-of-two that is >= the largest iMaxLzssDelta. +// +// There is no harm to having a HASH_WIN_SIZE > iMaxLzssDelta, it just takes +// up more memory during compression. +// + +void __inline FindStringMatch ( + int * pFoundOffset, int * pFoundLength, + const uint8_t * pSrcBuffer, const int iSrcLength, + int iFromOffset, int iMaxLzssDelta, int iMaxLzssLength ) + +{ + // Local Variables. + + int iMinOffset = ((iFromOffset - iMaxLzssDelta) < 0) ? 0 : (iFromOffset - iMaxLzssDelta); + int iBestOffset = iMinOffset; + int iBestLength = 0; + int iTestOffset = g_pHashOffset[ CALC_HASH( iFromOffset ) ]; + int iTestLength; + + // Don't read beyond the pSrcBuffer. + + iMaxLzssLength = ((iSrcLength - iFromOffset) < iMaxLzssLength) ? (iSrcLength - iFromOffset) : iMaxLzssLength; + + // Stop searching when the test string is outside the iMaxLzssDelta window. + + while (iTestOffset >= iMinOffset) + { + // Quickly reject strings that won't be longer than the current-best. + + if (pSrcBuffer[ iFromOffset + iBestLength ] == pSrcBuffer[ iTestOffset + iBestLength ]) + { + iTestLength = 0; + + while ((iTestLength < iMaxLzssLength) && + (pSrcBuffer[ iFromOffset + iTestLength ] == pSrcBuffer[ iTestOffset + iTestLength ])) { + ++iTestLength; + } + + if (iTestLength > iBestLength) { + iBestOffset = iTestOffset; + iBestLength = iTestLength; + if (iBestLength == iMaxLzssLength) break; + } + } + + // Test the previous string with the same CALC_HASH() value. + + iTestOffset = g_pPrevOffset[ iTestOffset & HASH_WIN_MASK ]; + } + + *pFoundOffset = iBestOffset; + *pFoundLength = iBestLength; +} + + + +// ************************************************************************** +// ************************************************************************** +// +// CompressHLZ () +// +// Compress data in Hudson's 4-bit length, 8-bit offset LZSS. +// +// The window offset is stored as relative to the start of the data, and not +// as a delta from the current output, but some games start decompressing to +// their 256 byte ring-buffer at $xxEF intead of $xx00, and so an offset has +// to be added to the window address that is written. +// +// With a window start of $EF, this method is used by (at least) ... +// Tengai Makyo II - Manji Maru +// Dragon Slayer - The Legend of Heroes II +// Emerald Dragon +// Linda3 +// +// With a window start of $00, this method is used by (at least) ... +// Ys IV - The Dawn of Ys +// Gulliver Boy +// +// The bit buffer is stored hi-lo. +// The nibble buffer is stored hi-lo. +// +// Ys4 and Gulliver add a 1-byte header to signal whether the compressed data +// is encoded in byte (0) or word (1) format. +// +// This compressor does NOT handle Ys4's word format scheme! +// + +uint8_t * CompressHLZ ( + uint8_t * pSrcBuffer, unsigned uSrcLength, + uint8_t * pDstBuffer, unsigned uDstLength ) + +{ + // Local Variables. + + int iSrcOffset = 0; + int iCopyCount = 0; + int iSkipCount; + + int iHashValue; + int iSrcRemain; + int iBestOffset = 0; + int iBestLength; + int iNextOffset; + int iNextLength; + + // Set the search parameters for this particular LZSS-variant. + + const int iMinMatch = 2; + const int iMaxMatch = 17; + const int iMaxDelta = 256; + + // Initialize the hash table and window for string matching. + + InitStringMatch(); + + // Initialize the output. + + g_pOutBuffer = pDstBuffer; + g_uOutLength = uDstLength; + + BitInit(); + NibbleInit(); + + // Write a header to signal Ys4's BYTE compression if using Ys4's g_iWindowFix. + + if (g_iWindowFix == 0) { + *g_pOutBuffer++ = 0; + } + + // Loop around encoding strings until the buffer is empty. + + for (;;) + { + // Update the window and calc how much data is left to compress. + + iSrcRemain = uSrcLength - iSrcOffset; + + if (iSrcRemain < iMinMatch) { + iSrcOffset += iSrcRemain; + iCopyCount += iSrcRemain; + break; + } + + // Find the best match at the current position. + + iBestLength = 0; + + if (iSrcOffset != 0) { + FindStringMatch( &iBestOffset, &iBestLength, pSrcBuffer, uSrcLength, iSrcOffset, iMaxDelta, iMaxMatch ); + } + + // Move the window on by 1 byte. + + iHashValue = CALC_HASH( iSrcOffset ); + + g_pPrevOffset[ iSrcOffset & HASH_WIN_MASK ] = g_pHashOffset[ iHashValue ]; + g_pHashOffset[ iHashValue ] = iSrcOffset; + + // Lazy match (compresses up to 1% better on a good day, usually less). + // + // Basically, if you can compress 1 extra byte, then that is a better thing + // to do instead of encoding 2 literals after the current match. + + if (g_fLazyMatch) + { + if ((iBestLength >= iMinMatch) && (iBestLength < iMaxMatch) && (iSrcRemain != iMinMatch)) + { + FindStringMatch( &iNextOffset, &iNextLength, pSrcBuffer, uSrcLength, iSrcOffset + 1, iMaxDelta, iMaxMatch ); + + if (iNextLength > iBestLength) { + ++iCopyCount; + ++iSrcOffset; + iBestLength = iNextLength; + iBestOffset = iNextOffset; + } + } + } + + // Keep track of COPY bytes, but don't process them, yet. + + if (iBestLength < iMinMatch) { + ++iCopyCount; + ++iSrcOffset; + continue; + } + + // First, write any COPY bytes. + + if (iCopyCount) + { + uint8_t * pCopyFrom = pSrcBuffer + iSrcOffset - iCopyCount; + + do { + // Signal that COPY is next. + + BitWriteHiLo( 1 ); + + *g_pOutBuffer++ = *pCopyFrom++; + } while (--iCopyCount); + } + + // Then write the LZSS "match". + // + // Signal that LZSS is next. + + BitWriteHiLo( 0 ); + + // Save LZSS offset (as an absolute window offset). + + *g_pOutBuffer++ = (iBestOffset + g_iWindowFix) & 255; + + // Save LZSS length - 2. + + NibbleWriteHiLo( iBestLength - 2 ); + + // Skip passed "matched" bytes, updating the window contents. + + ++iSrcOffset; + + iSkipCount = iBestLength - 1; + + while (iSkipCount--) + { + iHashValue = CALC_HASH( iSrcOffset ); + + g_pPrevOffset[ iSrcOffset & HASH_WIN_MASK ] = g_pHashOffset[ iHashValue ]; + g_pHashOffset[ iHashValue ] = iSrcOffset; + + ++iSrcOffset; + } + + } // End of "for (;;)" + + // Encode the last COPY byte(s) (if there are any). + + if (iCopyCount) + { + uint8_t * pCopyFrom = pSrcBuffer + iSrcOffset - iCopyCount; + + do { + // Signal that COPY is next. + + BitWriteHiLo( 1 ); + + *g_pOutBuffer++ = *pCopyFrom++; + } while (--iCopyCount); + } + + // All done. + + return (g_pOutBuffer); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// DecompressHLZ () +// +// Decompress data in Hudson's 4-bit length, 8-bit offset LZSS. +// +// The window offset is stored as relative to the start of the data, and not +// as a delta from the current output, but some games start decompressing to +// their 256 byte ring-buffer at $xxEF intead of $xx00. +// +// With a window start of $EF, this method is used by (at least) ... +// Tengai Makyo II - Manji Maru +// Dragon Slayer - The Legend of Heroes II +// Emerald Dragon +// Linda3 +// +// With a window start of $00, this method is used by (at least) ... +// Ys IV - The Dawn of Ys +// Gulliver Boy +// +// The bit buffer is stored hi-lo. +// The nibble buffer is stored hi-lo. +// +// The compression has no end-of-file marker, and data is decompressed until +// all of the source file is used, whether the data makes sense or not. +// +// Ys4 and Gulliver add a 1-byte header to signal whether the compressed data +// is encoded in byte (0) or word (1) format. +// +// This decompressor does NOT handle Ys4's word format scheme! +// + +uint8_t * DecompressHLZ ( + uint8_t * pSrcBuffer, unsigned uSrcLength, + uint8_t * pDstBuffer, unsigned uDstLength ) + +{ + // Local Variables. + + int iSrcOffset = 0; + int iDstOffset = 0; + int iMatchLength = 0; + + int iNibbleOffset = 0; + + uint8_t uBitMask = 0; + uint8_t uBitFlag = 0; + + uint8_t uWinOffset; + uint8_t uMatchOffset; + + uint8_t aWinBuffer[ 256 ]; + + // Initialize the ring-buffer window, just in case Hudson really thought + // that a preload filled with $00 bytes was actually a sane idea. + // + // N.B. I *hate* LZSS decompressors using a ring-buffer, it isn't needed + // unless you are using a preload, and it just obfuscates the simplicity + // of the LZSS algorithm. + + memset( aWinBuffer, 0, 256 ); + + // Detect the Ys4 style compression-type. + // + // Since HLZ compression stores its LZSS flags hi-lo, and there absolutely + // must be a COPY (a 1-bit) in the first few bits, normally in the top bit + // if there is no preload, then the type can be easily determined. + + uWinOffset = 0xEF; + + if (pSrcBuffer[0] < 2) { + // This must be the Ys4 style compression-type. + + if (pSrcBuffer[0] == 1) { + printf( "hulz - Hudson's WORD compression is not supported!\n" ); + return (NULL); + } + + uWinOffset = 0x00; + ++iSrcOffset; + } + + // Loop around decompressing data bytes until something stops us. + + while ((iSrcOffset <= (int) uSrcLength) && (iDstOffset < (int) uDstLength)) + { + if (iMatchLength == 0) + { + // Before starting a new COPY or MATCH, is there any data left to decompress? + + if (iSrcOffset >= (int) uSrcLength) { + break; + } + + // Do we need to load another byte of flag bits? + + if (uBitMask == 0) { + uBitFlag = pSrcBuffer[ iSrcOffset++ ]; + uBitMask = 0x80; + } + + if (uBitFlag & uBitMask) { + // COPY 1 byte. + + pDstBuffer[ iDstOffset++ ] = + aWinBuffer[ uWinOffset++ ] = pSrcBuffer[ iSrcOffset++ ]; + } else { + // Read MATCH offset. + + uMatchOffset = pSrcBuffer[ iSrcOffset++ ]; + + // Read MATCH length. + + if (iNibbleOffset == 0) { + iNibbleOffset = iSrcOffset++; + iMatchLength = (pSrcBuffer[ iNibbleOffset ] >> 4) + 2; + } else { + iMatchLength = (pSrcBuffer[ iNibbleOffset ] & 15) + 2; + iNibbleOffset = 0; + } + } + + uBitMask >>= 1; + } else { + // MATCH 1 byte. + + pDstBuffer[ iDstOffset++ ] = + aWinBuffer[ uWinOffset++ ] = aWinBuffer[ uMatchOffset++ ]; + --iMatchLength; + } + } + + // Warn the user if something seems to be wrong. + + if ((iMatchLength != 0) || (iDstOffset == (int) uDstLength)) { + printf( "hulz - WARNING, too much decompessed data, output truncated!\n" ); + } else { + if (iSrcOffset != (int) uSrcLength) { + printf( "hulz - WARNING, read passed the end of compressed data!\n" ); + printf( "hulz - The file is either too short or a byte too long.\n" ); + } else { + if (uBitMask != 0) { + do { + if (uBitFlag & uBitMask) { + printf( "hulz - WARNING, flag bits indicate the input file is too short!\n" ); + } + uBitMask >>= 1; + } while (uBitMask != 0); + } + } + } + + return (pDstBuffer + iDstOffset); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// g_aHudsonPreload [] +// +// Hudson's runtime-generated 4KByte preload for Anearth Fantasy Stories. +// + +uint8_t g_aHudsonPreload [4096] = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01, // |................| + 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x02,0x02, // |................| + 0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03, // |................| + 0x03,0x03,0x03,0x03,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, // |................| + 0x04,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x06,0x06, // |................| + 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x07,0x07,0x07,0x07, // |................| + 0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08, // |................| + 0x08,0x08,0x08,0x08,0x08,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09, // |................| + 0x09,0x09,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0b, // |................| + 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0c,0x0c,0x0c,0x0c, // |................| + 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, // |................| + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e, // |................| + 0x0e,0x0e,0x0e,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, // |................| + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x11,0x11,0x11, // |................| + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x12,0x12,0x12,0x12,0x12,0x12, // |................| + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13, // |................| + 0x13,0x13,0x13,0x13,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14, // |................| + 0x14,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x16,0x16, // |................| + 0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x17,0x17,0x17,0x17,0x17, // |................| + 0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, // |................| + 0x18,0x18,0x18,0x18,0x18,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19, // |................| + 0x19,0x19,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x1b, // |................| + 0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1b,0x1c,0x1c,0x1c,0x1c, // |................| + 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1d, // |................| + 0x1d,0x1d,0x1d,0x1d,0x1d,0x1d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e, // |................| + 0x1e,0x1e,0x1e,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f, // |................| + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x21,0x21,0x21, // | !!!| + 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22, // |!!!!!!!!!!""""""| + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23, // |"""""""#########| + 0x23,0x23,0x23,0x23,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, // |####$$$$$$$$$$$$| + 0x24,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x25,0x26,0x26, // |$%%%%%%%%%%%%%&&| + 0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x26,0x27,0x27,0x27,0x27,0x27, // |&&&&&&&&&&&'''''| + 0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x28, // |''''''''((((((((| + 0x28,0x28,0x28,0x28,0x28,0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29,0x29, // |((((()))))))))))| + 0x29,0x29,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2b, // |))*************+| + 0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2c,0x2c,0x2c,0x2c, // |++++++++++++,,,,| + 0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2c,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2d, // |,,,,,,,,,-------| + 0x2d,0x2d,0x2d,0x2d,0x2d,0x2d,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e,0x2e, // |------..........| + 0x2e,0x2e,0x2e,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f,0x2f, // |.../////////////| + 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x31,0x31,0x31, // |0000000000000111| + 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x32,0x32,0x32,0x32,0x32,0x32, // |1111111111222222| + 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33, // |2222222333333333| + 0x33,0x33,0x33,0x33,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34, // |3333444444444444| + 0x34,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x36,0x36, // |4555555555555566| + 0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x37,0x37,0x37,0x37,0x37, // |6666666666677777| + 0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x37,0x38,0x38,0x38,0x38,0x38,0x38,0x38,0x38, // |7777777788888888| + 0x38,0x38,0x38,0x38,0x38,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39, // |8888899999999999| + 0x39,0x39,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3a,0x3b, // |99:::::::::::::;| + 0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3c,0x3c,0x3c,0x3c, // |;;;;;;;;;;;;<<<<| + 0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3d, // |<<<<<<<<<=======| + 0x3d,0x3d,0x3d,0x3d,0x3d,0x3d,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e,0x3e, // |======>>>>>>>>>>| + 0x3e,0x3e,0x3e,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f,0x3f, // |>>>?????????????| + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x41,0x41,0x41, // |@@@@@@@@@@@@@AAA| + 0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x42,0x42,0x42,0x42,0x42,0x42, // |AAAAAAAAAABBBBBB| + 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43, // |BBBBBBBCCCCCCCCC| + 0x43,0x43,0x43,0x43,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44, // |CCCCDDDDDDDDDDDD| + 0x44,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x45,0x46,0x46, // |DEEEEEEEEEEEEEFF| + 0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x47,0x47,0x47,0x47,0x47, // |FFFFFFFFFFFGGGGG| + 0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x47,0x48,0x48,0x48,0x48,0x48,0x48,0x48,0x48, // |GGGGGGGGHHHHHHHH| + 0x48,0x48,0x48,0x48,0x48,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49, // |HHHHHIIIIIIIIIII| + 0x49,0x49,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4b, // |IIJJJJJJJJJJJJJK| + 0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4b,0x4c,0x4c,0x4c,0x4c, // |KKKKKKKKKKKKLLLL| + 0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4d,0x4d,0x4d,0x4d,0x4d,0x4d,0x4d, // |LLLLLLLLLMMMMMMM| + 0x4d,0x4d,0x4d,0x4d,0x4d,0x4d,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e,0x4e, // |MMMMMMNNNNNNNNNN| + 0x4e,0x4e,0x4e,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f, // |NNNOOOOOOOOOOOOO| + 0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x50,0x51,0x51,0x51, // |PPPPPPPPPPPPPQQQ| + 0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x52,0x52,0x52,0x52,0x52,0x52, // |QQQQQQQQQQRRRRRR| + 0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x53, // |RRRRRRRSSSSSSSSS| + 0x53,0x53,0x53,0x53,0x54,0x54,0x54,0x54,0x54,0x54,0x54,0x54,0x54,0x54,0x54,0x54, // |SSSSTTTTTTTTTTTT| + 0x54,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x56,0x56, // |TUUUUUUUUUUUUUVV| + 0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x57,0x57,0x57,0x57,0x57, // |VVVVVVVVVVVWWWWW| + 0x57,0x57,0x57,0x57,0x57,0x57,0x57,0x57,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58, // |WWWWWWWWXXXXXXXX| + 0x58,0x58,0x58,0x58,0x58,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59, // |XXXXXYYYYYYYYYYY| + 0x59,0x59,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5a,0x5b, // |YYZZZZZZZZZZZZZ[| + 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5c,0x5c,0x5c,0x5c, // |[[[[[[[[[[[[\\\\| + 0x5c,0x5c,0x5c,0x5c,0x5c,0x5c,0x5c,0x5c,0x5c,0x5d,0x5d,0x5d,0x5d,0x5d,0x5d,0x5d, // |\\\\\\\\\]]]]]]]| + 0x5d,0x5d,0x5d,0x5d,0x5d,0x5d,0x5e,0x5e,0x5e,0x5e,0x5e,0x5e,0x5e,0x5e,0x5e,0x5e, // |]]]]]]^^^^^^^^^^| + 0x5e,0x5e,0x5e,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f, // |^^^_____________| + 0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x61,0x61,0x61, // |`````````````aaa| + 0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x62,0x62,0x62,0x62,0x62,0x62, // |aaaaaaaaaabbbbbb| + 0x62,0x62,0x62,0x62,0x62,0x62,0x62,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63, // |bbbbbbbccccccccc| + 0x63,0x63,0x63,0x63,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64, // |ccccdddddddddddd| + 0x64,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x65,0x66,0x66, // |deeeeeeeeeeeeeff| + 0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x67,0x67,0x67,0x67,0x67, // |fffffffffffggggg| + 0x67,0x67,0x67,0x67,0x67,0x67,0x67,0x67,0x68,0x68,0x68,0x68,0x68,0x68,0x68,0x68, // |gggggggghhhhhhhh| + 0x68,0x68,0x68,0x68,0x68,0x69,0x69,0x69,0x69,0x69,0x69,0x69,0x69,0x69,0x69,0x69, // |hhhhhiiiiiiiiiii| + 0x69,0x69,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6a,0x6b, // |iijjjjjjjjjjjjjk| + 0x6b,0x6b,0x6b,0x6b,0x6b,0x6b,0x6b,0x6b,0x6b,0x6b,0x6b,0x6b,0x6c,0x6c,0x6c,0x6c, // |kkkkkkkkkkkkllll| + 0x6c,0x6c,0x6c,0x6c,0x6c,0x6c,0x6c,0x6c,0x6c,0x6d,0x6d,0x6d,0x6d,0x6d,0x6d,0x6d, // |lllllllllmmmmmmm| + 0x6d,0x6d,0x6d,0x6d,0x6d,0x6d,0x6e,0x6e,0x6e,0x6e,0x6e,0x6e,0x6e,0x6e,0x6e,0x6e, // |mmmmmmnnnnnnnnnn| + 0x6e,0x6e,0x6e,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f,0x6f, // |nnnooooooooooooo| + 0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x71,0x71,0x71, // |pppppppppppppqqq| + 0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x71,0x72,0x72,0x72,0x72,0x72,0x72, // |qqqqqqqqqqrrrrrr| + 0x72,0x72,0x72,0x72,0x72,0x72,0x72,0x73,0x73,0x73,0x73,0x73,0x73,0x73,0x73,0x73, // |rrrrrrrsssssssss| + 0x73,0x73,0x73,0x73,0x74,0x74,0x74,0x74,0x74,0x74,0x74,0x74,0x74,0x74,0x74,0x74, // |sssstttttttttttt| + 0x74,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x76,0x76, // |tuuuuuuuuuuuuuvv| + 0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x77,0x77,0x77,0x77,0x77, // |vvvvvvvvvvvwwwww| + 0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x78,0x78,0x78,0x78,0x78,0x78,0x78,0x78, // |wwwwwwwwxxxxxxxx| + 0x78,0x78,0x78,0x78,0x78,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79, // |xxxxxyyyyyyyyyyy| + 0x79,0x79,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7a,0x7b, // |yyzzzzzzzzzzzzz{| + 0x7b,0x7b,0x7b,0x7b,0x7b,0x7b,0x7b,0x7b,0x7b,0x7b,0x7b,0x7b,0x7c,0x7c,0x7c,0x7c, // |{{{{{{{{{{{{||||| + 0x7c,0x7c,0x7c,0x7c,0x7c,0x7c,0x7c,0x7c,0x7c,0x7d,0x7d,0x7d,0x7d,0x7d,0x7d,0x7d, // ||||||||||}}}}}}}| + 0x7d,0x7d,0x7d,0x7d,0x7d,0x7d,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e, // |}}}}}}~~~~~~~~~~| + 0x7e,0x7e,0x7e,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f, // |~~~.............| + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x81,0x81,0x81, // |................| + 0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x81,0x82,0x82,0x82,0x82,0x82,0x82, // |................| + 0x82,0x82,0x82,0x82,0x82,0x82,0x82,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83, // |................| + 0x83,0x83,0x83,0x83,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84,0x84, // |................| + 0x84,0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x85,0x86,0x86, // |................| + 0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x87,0x87,0x87,0x87,0x87, // |................| + 0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x87,0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88, // |................| + 0x88,0x88,0x88,0x88,0x88,0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x89, // |................| + 0x89,0x89,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8b, // |................| + 0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8b,0x8c,0x8c,0x8c,0x8c, // |................| + 0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8c,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8d, // |................| + 0x8d,0x8d,0x8d,0x8d,0x8d,0x8d,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e,0x8e, // |................| + 0x8e,0x8e,0x8e,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f,0x8f, // |................| + 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x91,0x91,0x91, // |................| + 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x92,0x92,0x92,0x92,0x92,0x92, // |................| + 0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93, // |................| + 0x93,0x93,0x93,0x93,0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94,0x94, // |................| + 0x94,0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x95,0x96,0x96, // |................| + 0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x96,0x97,0x97,0x97,0x97,0x97, // |................| + 0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x97,0x98,0x98,0x98,0x98,0x98,0x98,0x98,0x98, // |................| + 0x98,0x98,0x98,0x98,0x98,0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99,0x99, // |................| + 0x99,0x99,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9a,0x9b, // |................| + 0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9b,0x9c,0x9c,0x9c,0x9c, // |................| + 0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9c,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9d, // |................| + 0x9d,0x9d,0x9d,0x9d,0x9d,0x9d,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e, // |................| + 0x9e,0x9e,0x9e,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f, // |................| + 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1, // |................| + 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2, // |................| + 0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3,0xa3, // |................| + 0xa3,0xa3,0xa3,0xa3,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4,0xa4, // |................| + 0xa4,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa5,0xa6,0xa6, // |................| + 0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa6,0xa7,0xa7,0xa7,0xa7,0xa7, // |................| + 0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa7,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8, // |................| + 0xa8,0xa8,0xa8,0xa8,0xa8,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9,0xa9, // |................| + 0xa9,0xa9,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xab, // |................| + 0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xab,0xac,0xac,0xac,0xac, // |................| + 0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xac,0xad,0xad,0xad,0xad,0xad,0xad,0xad, // |................| + 0xad,0xad,0xad,0xad,0xad,0xad,0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae,0xae, // |................| + 0xae,0xae,0xae,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf, // |................| + 0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb0,0xb1,0xb1,0xb1, // |................| + 0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2, // |................| + 0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb2,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3, // |................| + 0xb3,0xb3,0xb3,0xb3,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4,0xb4, // |................| + 0xb4,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb6,0xb6, // |................| + 0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb7,0xb7,0xb7,0xb7,0xb7, // |................| + 0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb7,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8,0xb8, // |................| + 0xb8,0xb8,0xb8,0xb8,0xb8,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9,0xb9, // |................| + 0xb9,0xb9,0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xba,0xbb, // |................| + 0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbb,0xbc,0xbc,0xbc,0xbc, // |................| + 0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd, // |................| + 0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe, // |................| + 0xbe,0xbe,0xbe,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf, // |................| + 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc1,0xc1,0xc1, // |................| + 0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc1,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2, // |................| + 0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc2,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3, // |................| + 0xc3,0xc3,0xc3,0xc3,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4,0xc4, // |................| + 0xc4,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc6,0xc6, // |................| + 0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc7,0xc7,0xc7,0xc7,0xc7, // |................| + 0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,0xc8, // |................| + 0xc8,0xc8,0xc8,0xc8,0xc8,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9,0xc9, // |................| + 0xc9,0xc9,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xcb, // |................| + 0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcb,0xcc,0xcc,0xcc,0xcc, // |................| + 0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xcd, // |................| + 0xcd,0xcd,0xcd,0xcd,0xcd,0xcd,0xce,0xce,0xce,0xce,0xce,0xce,0xce,0xce,0xce,0xce, // |................| + 0xce,0xce,0xce,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf, // |................| + 0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd1,0xd1,0xd1, // |................| + 0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2, // |................| + 0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd3,0xd3,0xd3,0xd3,0xd3,0xd3,0xd3,0xd3,0xd3, // |................| + 0xd3,0xd3,0xd3,0xd3,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4, // |................| + 0xd4,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd6,0xd6, // |................| + 0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd7,0xd7,0xd7,0xd7,0xd7, // |................| + 0xd7,0xd7,0xd7,0xd7,0xd7,0xd7,0xd7,0xd7,0xd8,0xd8,0xd8,0xd8,0xd8,0xd8,0xd8,0xd8, // |................| + 0xd8,0xd8,0xd8,0xd8,0xd8,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9, // |................| + 0xd9,0xd9,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xdb, // |................| + 0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdc,0xdc,0xdc,0xdc, // |................| + 0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdc,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd, // |................| + 0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xde,0xde,0xde,0xde,0xde,0xde,0xde,0xde,0xde,0xde, // |................| + 0xde,0xde,0xde,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf,0xdf, // |................| + 0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe0,0xe1,0xe1,0xe1, // |................| + 0xe1,0xe1,0xe1,0xe1,0xe1,0xe1,0xe1,0xe1,0xe1,0xe1,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2, // |................| + 0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe3,0xe3,0xe3,0xe3,0xe3,0xe3,0xe3,0xe3,0xe3, // |................| + 0xe3,0xe3,0xe3,0xe3,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4, // |................| + 0xe4,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe6,0xe6, // |................| + 0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe7,0xe7,0xe7,0xe7,0xe7, // |................| + 0xe7,0xe7,0xe7,0xe7,0xe7,0xe7,0xe7,0xe7,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8,0xe8, // |................| + 0xe8,0xe8,0xe8,0xe8,0xe8,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9,0xe9, // |................| + 0xe9,0xe9,0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xeb, // |................| + 0xeb,0xeb,0xeb,0xeb,0xeb,0xeb,0xeb,0xeb,0xeb,0xeb,0xeb,0xeb,0xec,0xec,0xec,0xec, // |................| + 0xec,0xec,0xec,0xec,0xec,0xec,0xec,0xec,0xec,0xed,0xed,0xed,0xed,0xed,0xed,0xed, // |................| + 0xed,0xed,0xed,0xed,0xed,0xed,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee, // |................| + 0xee,0xee,0xee,0xef,0xef,0xef,0xef,0xef,0xef,0xef,0xef,0xef,0xef,0xef,0xef,0xef, // |................| + 0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf1,0xf1,0xf1, // |................| + 0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf2,0xf2,0xf2,0xf2,0xf2,0xf2, // |................| + 0xf2,0xf2,0xf2,0xf2,0xf2,0xf2,0xf2,0xf3,0xf3,0xf3,0xf3,0xf3,0xf3,0xf3,0xf3,0xf3, // |................| + 0xf3,0xf3,0xf3,0xf3,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4,0xf4, // |................| + 0xf4,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf5,0xf6,0xf6, // |................| + 0xf6,0xf6,0xf6,0xf6,0xf6,0xf6,0xf6,0xf6,0xf6,0xf6,0xf6,0xf7,0xf7,0xf7,0xf7,0xf7, // |................| + 0xf7,0xf7,0xf7,0xf7,0xf7,0xf7,0xf7,0xf7,0xf8,0xf8,0xf8,0xf8,0xf8,0xf8,0xf8,0xf8, // |................| + 0xf8,0xf8,0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xf9,0xf9,0xf9,0xf9,0xf9,0xf9, // |................| + 0xf9,0xf9,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfa,0xfb, // |................| + 0xfb,0xfb,0xfb,0xfb,0xfb,0xfb,0xfb,0xfb,0xfb,0xfb,0xfb,0xfb,0xfc,0xfc,0xfc,0xfc, // |................| + 0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd, // |................| + 0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe, // |................| + 0xfe,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // |................| + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, // |................| + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f, // |................| + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f, // | !"#$%&'()*+,-./| + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f, // |0123456789:;<=>?| + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f, // |@ABCDEFGHIJKLMNO| + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f, // |PQRSTUVWXYZ[\]^_| + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f, // |`abcdefghijklmno| + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f, // |pqrstuvwxyz{|}~.| + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, // |................| + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, // |................| + 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, // |................| + 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, // |................| + 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, // |................| + 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf, // |................| + 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, // |................| + 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff, // |................| + 0xff,0xfe,0xfd,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,0xf6,0xf5,0xf4,0xf3,0xf2,0xf1,0xf0, // |................| + 0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe5,0xe4,0xe3,0xe2,0xe1,0xe0, // |................| + 0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd0, // |................| + 0xcf,0xce,0xcd,0xcc,0xcb,0xca,0xc9,0xc8,0xc7,0xc6,0xc5,0xc4,0xc3,0xc2,0xc1,0xc0, // |................| + 0xbf,0xbe,0xbd,0xbc,0xbb,0xba,0xb9,0xb8,0xb7,0xb6,0xb5,0xb4,0xb3,0xb2,0xb1,0xb0, // |................| + 0xaf,0xae,0xad,0xac,0xab,0xaa,0xa9,0xa8,0xa7,0xa6,0xa5,0xa4,0xa3,0xa2,0xa1,0xa0, // |................| + 0x9f,0x9e,0x9d,0x9c,0x9b,0x9a,0x99,0x98,0x97,0x96,0x95,0x94,0x93,0x92,0x91,0x90, // |................| + 0x8f,0x8e,0x8d,0x8c,0x8b,0x8a,0x89,0x88,0x87,0x86,0x85,0x84,0x83,0x82,0x81,0x80, // |................| + 0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x75,0x74,0x73,0x72,0x71,0x70, // |.~}|{zyxwvutsrqp| + 0x6f,0x6e,0x6d,0x6c,0x6b,0x6a,0x69,0x68,0x67,0x66,0x65,0x64,0x63,0x62,0x61,0x60, // |onmlkjihgfedcba`| + 0x5f,0x5e,0x5d,0x5c,0x5b,0x5a,0x59,0x58,0x57,0x56,0x55,0x54,0x53,0x52,0x51,0x50, // |_^]\[ZYXWVUTSRQP| + 0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x45,0x44,0x43,0x42,0x41,0x40, // |ONMLKJIHGFEDCBA@| + 0x3f,0x3e,0x3d,0x3c,0x3b,0x3a,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30, // |?>=<;:9876543210| + 0x2f,0x2e,0x2d,0x2c,0x2b,0x2a,0x29,0x28,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20, // |/.-,+*)('&%$#"! | + 0x1f,0x1e,0x1d,0x1c,0x1b,0x1a,0x19,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10, // |................| + 0x0f,0x0e,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // |................| + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // | | + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // | | + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // | | + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // | | + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // | | + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // | | + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // | | + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20 // | | +}; + + + +// ************************************************************************** +// ************************************************************************** +// +// CompressAFS () +// +// Compress data in Hudson's 4-bit length, 12-bit offset LZSS, with preload. +// +// The window offset is stored as relative to the start of the preload data, +// and not as a delta from the current output, and some games do not use all +// of the 4096-byte preload and start compressing at offset $xFEE instead of +// $x000. +// +// With a window start of $0FEE, this method is used by (at least) ... +// Anearth Fantasy Stories +// +// The bit buffer is stored lo-hi. +// +// The compressed data is preceded by an 8-byte header containing the sizes +// of both the compressed and uncompressed data. +// + +uint8_t * CompressAFS ( + uint8_t * pSrcBuffer, unsigned uSrcLength, + uint8_t * pDstBuffer, unsigned uDstLength ) + +{ + // Local Variables. + + int iSrcOffset = 0; + int iCopyCount = 0; + int iSkipCount; + + int iHashValue; + int iSrcRemain; + int iBestOffset; + int iBestLength; + int iNextOffset; + int iNextLength; + + uint8_t * pTmpBuffer = NULL; + + bool usePreload = true; + + // Set the search parameters for this particular LZSS-variant. + + const int iMinMatch = 3; + const int iMaxMatch = 18; + const int iMaxDelta = 0x1000; + + // Initialize the hash table and window for string matching. + + InitStringMatch(); + + // Initialize the output, which begins with an 8-byte chunk header. + + g_pOutBuffer = pDstBuffer + 8; + g_uOutLength = uDstLength - 8; + + BitInit(); + + // Write the uncompressed length to the chunk header. + + pDstBuffer[4] = (uint8_t) (uSrcLength & 255); + pDstBuffer[5] = (uint8_t) (uSrcLength / 256); + pDstBuffer[6] = 0; + pDstBuffer[7] = 0; + + // Handle preload by creating a copy of the pSrcBuffer. + // + // Hudson's AFS only uses 0x0FEE bytes out of the 0x1000 byte preload! + + pTmpBuffer = (uint8_t *) malloc( uSrcLength + 0x0FEE ); + + memcpy( pTmpBuffer + 0x0000, g_aHudsonPreload, 0x0FEE ); + memcpy( pTmpBuffer + 0x0FEE, pSrcBuffer, uSrcLength ); + + pSrcBuffer = pTmpBuffer; + uSrcLength = uSrcLength + 0x0FEE; + + if (usePreload) + { + // Add preload data to the window of known strings. + + while (iSrcOffset != 0x0FEE) + { + iHashValue = CALC_HASH( iSrcOffset ); + + g_pPrevOffset[ iSrcOffset & HASH_WIN_MASK ] = g_pHashOffset[ iHashValue ]; + g_pHashOffset[ iHashValue ] = iSrcOffset; + + ++iSrcOffset; + } + } + else + { + // Ignore the preload data, but still start at offset 0x0FEE to keep + // Hudson's decompressor happy. + + iSrcOffset = 0x0FEE; + } + + // Loop around encoding strings until the buffer is empty. + + for (;;) + { + // Update the window and calc how much data is left to compress. + + iSrcRemain = uSrcLength - iSrcOffset; + + if (iSrcRemain < iMinMatch) { + iSrcOffset += iSrcRemain; + iCopyCount += iSrcRemain; + break; + } + + // Find the best match at the current position. + + iBestLength = 0; + + if (iSrcOffset != 0) { + FindStringMatch( &iBestOffset, &iBestLength, pSrcBuffer, uSrcLength, iSrcOffset, iMaxDelta, iMaxMatch ); + } + + // Move the window on by 1 byte. + + iHashValue = CALC_HASH( iSrcOffset ); + + g_pPrevOffset[ iSrcOffset & HASH_WIN_MASK ] = g_pHashOffset[ iHashValue ]; + g_pHashOffset[ iHashValue ] = iSrcOffset; + + // Lazy match (compresses up to 1% better on a good day, usually less). + // + // Basically, if you can compress 1 extra byte, then that is a better thing + // to do instead of encoding 2 literals after the current match. + + if (g_fLazyMatch) + { + if ((iBestLength >= iMinMatch) && (iBestLength < iMaxMatch) && (iSrcRemain != iMinMatch)) + { + FindStringMatch( &iNextOffset, &iNextLength, pSrcBuffer, uSrcLength, iSrcOffset + 1, iMaxDelta, iMaxMatch ); + + if (iNextLength > iBestLength) { + ++iCopyCount; + ++iSrcOffset; + iBestLength = iNextLength; + iBestOffset = iNextOffset; + } + } + } + + // Keep track of COPY bytes, but don't process them, yet. + + if (iBestLength < iMinMatch) { + ++iCopyCount; + ++iSrcOffset; + continue; + } + + // First, write any COPY bytes. + + if (iCopyCount) + { + uint8_t * pCopyFrom = pSrcBuffer + iSrcOffset - iCopyCount; + + do { + // Signal that COPY is next. + + BitWriteLoHi( 1 ); + + *g_pOutBuffer++ = *pCopyFrom++; + } while (--iCopyCount); + } + + // Then write the LZSS "match". + // + // Mark this as an LZSS and not a COPY. + + BitWriteLoHi( 0 ); + + // In Hudson's compression, the offset is not relative to the output, but + // is actually the index into the 4KB window[] array! + + g_pOutBuffer[0] = (uint8_t) (iBestOffset & 255); + g_pOutBuffer[1] = (uint8_t) ((0xF0u & (iBestOffset >> 4)) + (iBestLength - 3)); + + g_pOutBuffer += 2; + + // Skip passed "matched" bytes, updating the window contents. + + ++iSrcOffset; + + iSkipCount = iBestLength - 1; + + while (iSkipCount--) + { + iHashValue = CALC_HASH( iSrcOffset ); + + g_pPrevOffset[ iSrcOffset & HASH_WIN_MASK ] = g_pHashOffset[ iHashValue ]; + g_pHashOffset[ iHashValue ] = iSrcOffset; + + ++iSrcOffset; + } + + } // End of "for (;;)" + + // Encode the last COPY byte(s) (if there are any). + + if (iCopyCount) + { + uint8_t * pCopyFrom = pSrcBuffer + iSrcOffset - iCopyCount; + + do { + // Signal that COPY is next. + + BitWriteLoHi( 1 ); + + *g_pOutBuffer++ = *pCopyFrom++; + } while (--iCopyCount); + } + + // Write the compressed length to the chunk header. + + uDstLength = g_pOutBuffer - (pDstBuffer + 8); + + pDstBuffer[0] = (uint8_t) (uDstLength & 255); + pDstBuffer[1] = (uint8_t) (uDstLength / 256); + pDstBuffer[2] = 0; + pDstBuffer[3] = 0; + + // Free up the preload buffer. + + if (pTmpBuffer != NULL) + free( pTmpBuffer ); + + // All done. + + return (g_pOutBuffer); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// DecompressAFS () +// +// Decompress data in Hudson's 4-bit length, 12-bit offset LZSS, with buffer +// preload. +// +// The window offset is stored as relative to the start of the preload data, +// and not as a delta from the current output, and some games do not use all +// of the 4096-byte preload and start compressing at offset $xFEE instead of +// $x000. +// +// With a window start of $0FEE, this method is used by (at least) ... +// Anearth Fantasy Stories +// +// The bit buffer is stored lo-hi. +// +// The compressed data is preceded by an 8-byte header containing the sizes +// of both the compressed and uncompressed data. +// + +uint8_t * DecompressAFS ( + uint8_t * pSrcBuffer, unsigned uSrcLength, + uint8_t * pDstBuffer, unsigned uDstLength ) + +{ + // Local Variables. + + int iSrcLength; + int iDstLength; + + int iSrcOffset = 0; + int iDstOffset = 0; + int iMatchLength = 0; + + uint8_t uBitMask = 0; + uint8_t uBitFlag = 0; + + int iWinOffset; + int iMatchOffset; + + uint8_t aWinBuffer[ 4096 ]; + + // Initialize the ring-buffer window. + + memcpy( aWinBuffer, g_aHudsonPreload, 4096 ); + + iWinOffset = 0x0FEE; + + // Read the compressed and decompressed sizes from the header. + + iSrcLength = pSrcBuffer[0] + (0x00000100 * pSrcBuffer[1]) + (0x00010000 * pSrcBuffer[2]) + (0x01000000 * pSrcBuffer[3]); + iDstLength = pSrcBuffer[4] + (0x00000100 * pSrcBuffer[5]) + (0x00010000 * pSrcBuffer[6]) + (0x01000000 * pSrcBuffer[7]); + + if ((iSrcLength < 0) || (uSrcLength < (unsigned) (iSrcLength + 8))) { + printf( "hulz - ERROR, header indicates some compressed data is missing!\n" ); + return (NULL); + } + + iSrcLength += 8; + + if ((iDstLength < 0) || (uDstLength < (unsigned) (iDstLength))) { + printf( "hulz - ERROR, header says the decompressed data is more than %u bytes!\n", uDstLength ); + return (NULL); + } + + iSrcOffset = 8; + + // Loop around decompressing data bytes until something stops us. + + while ((iSrcOffset <= iSrcLength) && (iDstOffset < iDstLength)) + { + if (iMatchLength == 0) + { + // Before starting a new COPY or MATCH, is there any data left to decompress? + + if (iSrcOffset >= (int) uSrcLength) { + break; + } + + // Do we need to load another byte of flag bits? + + if (uBitMask == 0) { + uBitFlag = pSrcBuffer[ iSrcOffset++ ]; + uBitMask = 0x01; + } + + if (uBitFlag & uBitMask) { + // COPY 1 byte. + + pDstBuffer[ iDstOffset++ ] = + aWinBuffer[ iWinOffset++ ] = pSrcBuffer[ iSrcOffset++ ]; + iWinOffset &= 0x0FFF; + } else { + // Read MATCH offset. + + iMatchOffset = pSrcBuffer[ iSrcOffset++ ]; + iMatchOffset = iMatchOffset + 256 * (pSrcBuffer[ iSrcOffset ] >> 4); + + // Read MATCH length. + + iMatchLength = (pSrcBuffer[ iSrcOffset++ ] & 15) + 3; + } + + uBitMask <<= 1; + } else { + // MATCH 1 byte. + + pDstBuffer[ iDstOffset++ ] = + aWinBuffer[ iWinOffset++ ] = aWinBuffer[ iMatchOffset++ ]; + iWinOffset &= 0x0FFF; + iMatchOffset &= 0x0FFF; + --iMatchLength; + } + } + + // Warn the user if something seems to be wrong. + + if ((iMatchLength != 0) || (iDstOffset != iDstLength)) { + printf( "hulz - WARNING, decompessed data length does not match length in header!\n" ); + } else { + if (iSrcOffset != iSrcLength) { + printf( "hulz - WARNING, read passed the end of compressed data!\n" ); + } else { + if (uBitMask != 0) { + do { + if (uBitFlag & uBitMask) { + printf( "hulz - WARNING, flag bits indicate the input file is too short!\n" ); + } + uBitMask <<= 1; + } while (uBitMask != 0); + } + } + } + + return (pDstBuffer + iDstOffset); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// CompressLZ8 () +// +// Compress data in Elmer's 16-bit length, 8-bit offset LZ4-variant. +// +// The window offset is stored as relative to the start of the data, and not +// as a delta from the current output, which is quicker for decompression to +// a page-aligned output (or ring-buffer). +// +// Both COPY and MATCH counts are encoded as variable-length formats, either +// nibble or nibble+byte or nibble+byte+word. +// +// The nibble buffer is stored hi-lo. +// +// N.B. This is a pretty useless format, please use ZX0 instead! +// + +uint8_t * CompressLZ8 ( + uint8_t * pSrcBuffer, unsigned uSrcLength, + uint8_t * pDstBuffer, unsigned uDstLength ) + +{ + // Local Variables. + + int iSrcOffset = 0; + int iCopyCount = 0; + int iSkipCount; + + int iHashValue; + int iSrcRemain; + int iBestOffset; + int iBestLength; + int iNextOffset; + int iNextLength; + int iPrevOffset = 0; + int iPrevLength = 0; + + // Set the search parameters for this particular LZSS-variant. + + const int iMinMatch = 2; + const int iMaxMatch = 65536; + const int iMaxDelta = 256; + + // Initialize the hash table and window for string matching. + + InitStringMatch(); + + // Initialize the output. + + g_pOutBuffer = pDstBuffer; + g_uOutLength = uDstLength; + + BitInit(); + NibbleInit(); + + // Loop around encoding strings until the buffer is empty. + + for (;;) + { + // Update the window and calc how much data is left to compress. + + iSrcRemain = uSrcLength - iSrcOffset; + + if (iSrcRemain < iMinMatch) { + iSrcOffset += iSrcRemain; + iCopyCount += iSrcRemain; + break; + } + + // Find the best match at the current position. + + iBestLength = 0; + + if (iSrcOffset != 0) { + FindStringMatch( &iBestOffset, &iBestLength, pSrcBuffer, uSrcLength, iSrcOffset, iMaxDelta, iMaxMatch ); + } + + // Move the window on by 1 byte. + + iHashValue = CALC_HASH( iSrcOffset ); + + g_pPrevOffset[ iSrcOffset & HASH_WIN_MASK ] = g_pHashOffset[ iHashValue ]; + g_pHashOffset[ iHashValue ] = iSrcOffset; + + // Lazy match (compresses up to 1% better on a good day, usually less). + // + // With variable-length encoding, the cost of ignoring the current match is + // rather ugly to calculate. + + if (g_fLazyMatch) + { + if ((iBestLength >= iMinMatch) && (iBestLength < iMaxMatch) && (iSrcRemain != iMinMatch)) + { + int iLazyCost = 8; + + FindStringMatch( &iNextOffset, &iNextLength, pSrcBuffer, uSrcLength, iSrcOffset + 1, iMaxDelta, iMaxMatch ); + + if (iCopyCount == 0) { + iLazyCost += 4; + } else + if (iCopyCount == 15) { + iLazyCost += 8; + } else + if (iCopyCount == 255) { + iLazyCost += 16; + } + + if (iBestLength <= 16) { + if (iNextLength > 256) { + iLazyCost += 16; + } else + if (iNextLength > 16) { + iLazyCost += 8; + } + } else + if (iBestLength <= 256) { + if (iNextLength > 256) { + iLazyCost += 16; + } + } + + iLazyCost = (iLazyCost + 7) / 8; + + if (iNextLength >= (iBestLength + iLazyCost)) { + ++iCopyCount; + ++iSrcOffset; + iBestLength = iNextLength; + iBestOffset = iNextOffset; + } + } + } + + // Keep track of COPY bytes, but don't process them, yet. + + if (iBestLength < iMinMatch) { + ++iCopyCount; + ++iSrcOffset; + continue; + } + + // Got a new LZSS match! + // + // First, write the previous match now that we know what follows it. + + if (iPrevLength) { + // Save LZSS length with "followed-by-copy-or-lzss" flag in the bottom bit. + + int iTempLength = ((iPrevLength - 1) * 2) + ((iCopyCount == 0) ? 1 : 0); + + if (iTempLength < 16) { + NibbleWriteHiLo( iTempLength ); + } else { + NibbleWriteHiLo( 0 ); + if (iTempLength < 256) { + *g_pOutBuffer++ = iTempLength; + } else { + *g_pOutBuffer++ = 0; + *g_pOutBuffer++ = (iTempLength >> 8); + *g_pOutBuffer++ = (iTempLength & 255); + } + } + + // Save LZSS offset. + + *g_pOutBuffer++ = iPrevOffset & 255; + } + + // Then write any COPY bytes. + + if (iCopyCount) { + uint8_t * pCopyFrom; + + // Save COPY length. + + if (iCopyCount < 16) { + NibbleWriteHiLo( iCopyCount ); + } else { + NibbleWriteHiLo( 0 ); + if (iCopyCount < 256) { + *g_pOutBuffer++ = iCopyCount; + } else { + *g_pOutBuffer++ = 0; + *g_pOutBuffer++ = (iCopyCount >> 8) - (((iCopyCount & 255) == 0) ? 1 : 0); + *g_pOutBuffer++ = (iCopyCount & 255); + } + } + + // Save each individual COPY byte. + + pCopyFrom = pSrcBuffer + iSrcOffset - iCopyCount; + + do { + *g_pOutBuffer++ = *pCopyFrom++; + } while (--iCopyCount); + } + + // Remember the LZSS "match", but don't write it until we know what follows it. + + iPrevLength = iBestLength; + iPrevOffset = iBestOffset; + + // Skip passed "matched" bytes, updating the window contents. + + ++iSrcOffset; + + iSkipCount = iBestLength - 1; + + while (iSkipCount--) + { + iHashValue = CALC_HASH( iSrcOffset ); + + g_pPrevOffset[ iSrcOffset & HASH_WIN_MASK ] = g_pHashOffset[ iHashValue ]; + g_pHashOffset[ iHashValue ] = iSrcOffset; + + ++iSrcOffset; + } + + } // End of "for (;;)" + + // We have now reached the end of the data to compress! + // + // First, write the previous match now that we know what follows it. + + if (iPrevLength) { + // Save LZSS length with "what-comes-next" flag in the bottom bit. + + int iTempLength = ((iPrevLength - 1) * 2) + ((iCopyCount == 0) ? 1 : 0); + + if (iTempLength < 16) { + NibbleWriteHiLo( iTempLength ); + } else { + NibbleWriteHiLo( 0 ); + if (iTempLength < 256) { + *g_pOutBuffer++ = iTempLength; + } else { + *g_pOutBuffer++ = 0; + *g_pOutBuffer++ = (iTempLength >> 8); + *g_pOutBuffer++ = (iTempLength & 255); + } + } + + // Save LZSS offset. + + *g_pOutBuffer++ = iPrevOffset & 255; + } + + // Write the last COPY byte(s) (if there are any). + + if (iCopyCount) { + uint8_t * pCopyFrom; + + // Save COPY length. + + if (iCopyCount < 16) { + NibbleWriteHiLo( iCopyCount ); + } else { + NibbleWriteHiLo( 0 ); + if (iCopyCount < 256) { + *g_pOutBuffer++ = iCopyCount; + } else { + *g_pOutBuffer++ = 0; + *g_pOutBuffer++ = (iCopyCount >> 8) - (((iCopyCount & 255) == 0) ? 1 : 0); + *g_pOutBuffer++ = (iCopyCount & 255); + } + } + + // Save each individual COPY byte. + + pCopyFrom = pSrcBuffer + iSrcOffset - iCopyCount; + + do { + *g_pOutBuffer++ = *pCopyFrom++; + } while (--iCopyCount); + } + + // Write the end-of-file token. + + NibbleWriteHiLo( 0 ); + + *g_pOutBuffer++ = 1; + + // All done. + + return (g_pOutBuffer); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// DecompressLZ8 () +// +// Compress data in Elmer's 16-bit length, 8-bit offset LZ4-variant. +// +// The window offset is stored as relative to the start of the data, and not +// as a delta from the current output, which is quicker for decompression to +// a page-aligned output (or ring-buffer). +// +// Both COPY and MATCH counts are encoded as variable-length formats, either +// nibble or nibble+byte or nibble+byte+word. +// +// The nibble buffer is stored hi-lo. +// +// N.B. This is a pretty useless format, please use ZX0 instead! +// + +uint8_t * DecompressLZ8 ( + uint8_t * pSrcBuffer, unsigned uSrcLength, + uint8_t * pDstBuffer, unsigned uDstLength ) + +{ + // Local Variables. + + int iSrcOffset = 0; + int iDstOffset = 0; + + int iMatchIsNext = 0; + + int iCopyLength = 0; + int iMatchLength = 0; + + uint8_t * pNibbleOffset = NULL; + + uint8_t uWinOffset = 0; + uint8_t uMatchOffset; + + uint8_t aWinBuffer[ 256 ]; + + // Initialize the ring-buffer window. + // + // N.B. I *hate* LZSS decompressors using a ring-buffer, it isn't needed + // unless you are using a preload, and it just obfuscates the simplicity + // of the LZSS algorithm ... but I guess that it's a simple way to avoid + // reading outside the buffer area if we're given bad data to decompress. + + memset( aWinBuffer, 0, 256 ); + + // Loop around decompressing data bytes until something stops us. + + while ((iSrcOffset <= (int) uSrcLength) && (iDstOffset < (int) uDstLength)) + { + if ((iCopyLength == 0) && (iMatchLength == 0)) + { + int iLength; + + // Before starting a new COPY or MATCH, is there any data left to decompress? + // + // This shouldn't be necessary with an EOF marker, but it helps if given bad + // data. + + if (iSrcOffset >= (int) uSrcLength) { + break; + } + + // Read the next length. + + if (pNibbleOffset == NULL) { + pNibbleOffset = pSrcBuffer + iSrcOffset++; + iLength = (*pNibbleOffset >> 4); + } else { + iLength = (*pNibbleOffset & 15); + pNibbleOffset = NULL; + } + + if (iLength == 0) { + // Read 8-bit length. + + iLength = pSrcBuffer[ iSrcOffset++ ]; + + if (iLength == 1) { + // Got EOF marker. + + break; + } + + if (iLength == 0) { + // Read 16-bit length. + + iLength = 256 * pSrcBuffer[ iSrcOffset++ ]; + iLength = iLength + pSrcBuffer[ iSrcOffset++ ]; + } + } + + // Now that we've got the length, what to do with it? + + if (iMatchIsNext == 0) { + iMatchIsNext = 1; + + // Set COPY length. + + iCopyLength = iLength; + if ((iLength & 255) == 0) iCopyLength += 256; + } else { + iMatchIsNext = iLength & 1; + + // Set MATCH length. + + iMatchLength = (iLength >> 1) + 1; + + // Get MATCH offset. + + uMatchOffset = pSrcBuffer[ iSrcOffset++ ]; + } + } else { + // There's still an incomplete COPY or MATCH to process. + + if (iMatchLength == 0) { + // COPY 1 byte. + + pDstBuffer[ iDstOffset++ ] = + aWinBuffer[ uWinOffset++ ] = pSrcBuffer[ iSrcOffset++ ]; + --iCopyLength; + } else { + // MATCH 1 byte. + + pDstBuffer[ iDstOffset++ ] = + aWinBuffer[ uWinOffset++ ] = aWinBuffer[ uMatchOffset++ ]; + --iMatchLength; + } + } + } + + // Warn the user if something seems to be wrong. + + if (iDstOffset == (int) uDstLength) { + printf( "hulz - WARNING, too much decompessed data, output truncated!\n" ); + } else + if ((iCopyLength != 0) || (iMatchLength != 0) || (iSrcOffset != (int) uSrcLength)) { + printf( "hulz - WARNING, read passed the end of compressed data!\n" ); + printf( "hulz - The file is probably too short.\n" ); + } + + return (pDstBuffer + iDstOffset); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// ReadBinaryFile () +// +// Uses POSIX file functions rather than C file functions. +// +// Google "FIO19-C" for the reason why. +// +// N.B. Will return an error for files larger than 2GB on a 32-bit system. +// +// N.B. Pads allocated buffer size to next 16-byte boundary. +// + +bool ReadBinaryFile ( const char *pName, uint8_t **pBuffer, size_t *pLength ) +{ + uint8_t * pData = NULL; + off_t uSize; + struct stat cStat; + + int hFile = open( pName, O_BINARY | O_RDONLY ); + + if (hFile == -1) + goto errorExit; + + if ((fstat( hFile, &cStat ) != 0) || (!S_ISREG( cStat.st_mode ))) + goto errorExit; + + if (cStat.st_size > SSIZE_MAX) + goto errorExit; + + uSize = cStat.st_size; + + pData = (uint8_t *) calloc( (uSize + 15) >> 4, 16 ); + + if (pData == NULL) + goto errorExit; + + if (read( hFile, pData, uSize ) != uSize) + goto errorExit; + + close( hFile ); + + *pBuffer = pData; + *pLength = uSize; + + return (true); + + // Handle errors. + +errorExit: + + if (pData != NULL) free( pData ); + if (hFile >= 0) close( hFile ); + + *pBuffer = NULL; + *pLength = 0; + + return (false); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// WriteBinaryFile () +// +// Uses POSIX file functions rather than C file functions, just because +// it might save some space since ReadBinaryFile() uses POSIX functions. +// + +bool WriteBinaryFile ( const char *pName, uint8_t *pBuffer, size_t iLength ) +{ + int hFile = open( pName, O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE ); + + if (hFile == -1) + goto errorExit; + + if (write( hFile, pBuffer, iLength) != iLength ) + goto errorExit; + + close( hFile ); + + return (true); + + // Handle errors. + +errorExit: + + if (hFile >= 0) close( hFile ); + + return (false); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// ProcessFile () +// + +int ProcessFile ( const char *pSrcName, const char *pDstName ) + +{ + // Local variables. + + uint8_t * pSrcBuffer = NULL; + size_t uSrcLength = 0; + + uint8_t * pDstBuffer = NULL; + size_t uDstLength = 0; + + uint8_t * pDstFinish = NULL; + + // Read the original data from the source file. + + if (!ReadBinaryFile( pSrcName, &pSrcBuffer, &uSrcLength )) + { + printf( "hulz - Unable to read \"%s\" into memory!\n", pSrcName ); + return (1); + } + + // Some of Hudson's compression schemes are limited to a 16-bit length. + + if (uSrcLength > 65536) + { + printf( "hulz - File is too big, PC Engine compression is limited to 64KB!\n" ); + return (1); + } + + // Allocate 128KB of memory for the compresed data. + + uDstLength = 128 * 1024; + pDstBuffer = malloc( uDstLength ); + + // Compress or decompress the data using the desired method. + + if (g_fDecompress) { + if (g_pDecompressFunc == NULL) { + printf( "hulz - Decompression method not specified!\n" ); + return (1); + } + pDstFinish = (*g_pDecompressFunc)( pSrcBuffer, (int) uSrcLength, pDstBuffer, (int) uDstLength ); + } else { + if (g_pCompressorFunc == NULL) { + printf( "hulz - Compression method not specified!\n" ); + return (1); + } + pDstFinish = (*g_pCompressorFunc)( pSrcBuffer, (int) uSrcLength, pDstBuffer, (int) uDstLength ); + } + + if (pDstFinish == NULL) + { + return (1); + } + + // Write the compressed data to the output file. + + if (!WriteBinaryFile( pDstName, pDstBuffer, pDstFinish - pDstBuffer )) + { + printf( "hulz - Unable to write output file \"%s\"!\n", pDstName ); + return (1); + } + + // Free up the buffers before finishing. + + free( pDstBuffer ); + free( pSrcBuffer ); + + return (0); +} + + + +// ************************************************************************** +// ************************************************************************** +// +// ProcessOption () +// + +int ProcessOption ( char * pOption ) + +{ + // Process option string. + + switch( pOption[1] ) + { + // Display help. + + case '?': + case 'h': + { + printf + ( + "Purpose : Compress data with various methods used on the PC Engine\n" + "\n" + "Usage : hulz [