Add large-file (>1MB) OPL4 loading and fix track-end hang
- One-shot cold loader (asm/opl4_loader.asm, overlaid into plugin page 5) uploads embedded PCM sample blocks into ZXM-MoonSound SRAM and compacts the VGM stream in place, letting files over the normal 64-page megabuffer budget play back through the existing pipeline. - instant_abort() was filling only one of the two ISR command buffers with the shutdown chain; the chain (SILENCE_OPL + WAVE_OFF/TL + CMD_ISR_DONE) doesn't fit in one buffer, so after the first CMD_END_BUF the ISR swapped into stale data and never reached CMD_ISR_DONE, hanging at the end of long tracks instead of advancing to the next one. Now fills both buffers while frozen and syncs update_buffer()'s own ready-tracking state. - Switch to 14MHz before the SRAM upload (previously ran at whatever speed WC's file I/O left active) and show "Loading..." for .vgz and files >= 1MB, since the busy-waited upload takes several seconds. - Confirmed on real hardware: long OPL4 tracks (previously stuck looping at the end) now finish and advance normally, ESC exits cleanly.
This commit is contained in:
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
; Matching continuation for opl4_loader.asm's page-switch trampoline.
|
||||||
|
; After OUT (#12AF),D at #BFFD switches Win2 from plugin page 5 back to page
|
||||||
|
; 0, the CPU fetches this RET at the same window's next address (#BFFF).
|
||||||
|
.module cold_return
|
||||||
|
.area _CABS (ABS)
|
||||||
|
.org 0xBFFF
|
||||||
|
_opl4_page_return_gate::
|
||||||
|
.db 0xC9
|
||||||
@@ -0,0 +1,708 @@
|
|||||||
|
; One-shot large OPL4 loader, overlaid into unused space of plugin-relative
|
||||||
|
; page 5 (the command-block page, not WC physical page #05). Logical address
|
||||||
|
; #9800 means offset #1800, above WC's #1000..#1447 screen scratch range.
|
||||||
|
DEVICE NOSLOT64K
|
||||||
|
ORG #9800
|
||||||
|
|
||||||
|
PORT_W0 EQU #10AF
|
||||||
|
PORT_W2 EQU #12AF
|
||||||
|
PORT_W3 EQU #13AF
|
||||||
|
OPL_STAT EQU #C4
|
||||||
|
OPL_FM1_REG EQU #C6
|
||||||
|
OPL_FM1_DAT EQU #C7
|
||||||
|
OPL_WAVE_REG EQU #7E
|
||||||
|
OPL_WAVE_DAT EQU #7F
|
||||||
|
PORT_SYSCONF EQU #20AF ; System Config (turbo bits [1:0]), see isr.s
|
||||||
|
TURBO_14MHZ EQU #02
|
||||||
|
|
||||||
|
; Return: A bit 0..6 = compacted 16K page count (0 = reject),
|
||||||
|
; A bit 7 = ROM-image flag,
|
||||||
|
; H = first VPL page for the unread tail,
|
||||||
|
; L = high byte of its destination address (#C0..#FF).
|
||||||
|
opl4_large_entry:
|
||||||
|
; Page 5 replaces Win2, which normally contains the resident plugin
|
||||||
|
; ISR. The resident wrapper keeps interrupts disabled until #6028
|
||||||
|
; has restored page 0; never execute EI on this page.
|
||||||
|
push ix
|
||||||
|
push iy
|
||||||
|
|
||||||
|
; Loading a large file spends most of its time in the busy-waited
|
||||||
|
; SRAM upload below (upload_block/wave_reg/wave_dat) -- run it at
|
||||||
|
; full speed instead of whatever rate WC's own file I/O left active.
|
||||||
|
ld bc,PORT_SYSCONF
|
||||||
|
ld a,TURBO_14MHZ
|
||||||
|
out (c),a
|
||||||
|
|
||||||
|
xor a
|
||||||
|
ld (rom_flag),a
|
||||||
|
ld (block_count),a
|
||||||
|
call map_page0
|
||||||
|
|
||||||
|
; Basic VGM + OPL4 header validation.
|
||||||
|
ld hl,#C000
|
||||||
|
ld de,sig_vgm
|
||||||
|
ld b,4
|
||||||
|
.sig: ld a,(de)
|
||||||
|
cp (hl)
|
||||||
|
jp nz,loader_fail
|
||||||
|
inc de
|
||||||
|
inc hl
|
||||||
|
djnz .sig
|
||||||
|
ld hl,(#C060)
|
||||||
|
ld de,(#C062)
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
or d
|
||||||
|
or e
|
||||||
|
jp z,loader_fail
|
||||||
|
|
||||||
|
ld hl,#0040
|
||||||
|
ld de,(#C008)
|
||||||
|
ld a,d
|
||||||
|
cp #01
|
||||||
|
jr c,.data_ready
|
||||||
|
jr nz,.data_new
|
||||||
|
ld a,e
|
||||||
|
cp #50
|
||||||
|
jr c,.data_ready
|
||||||
|
.data_new:
|
||||||
|
ld a,(#C036)
|
||||||
|
or a
|
||||||
|
jp nz,loader_fail
|
||||||
|
ld a,(#C037)
|
||||||
|
or a
|
||||||
|
jp nz,loader_fail
|
||||||
|
ld hl,(#C034)
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
jr z,.data_zero
|
||||||
|
ld de,#0034
|
||||||
|
add hl,de
|
||||||
|
jr .data_ready
|
||||||
|
.data_zero:
|
||||||
|
ld hl,#0040
|
||||||
|
.data_ready:
|
||||||
|
ld a,h
|
||||||
|
cp #04 ; initial blocks must begin in first 1K
|
||||||
|
jp nc,loader_fail
|
||||||
|
ld de,#C000
|
||||||
|
add hl,de
|
||||||
|
ld (src_ptr),hl
|
||||||
|
xor a
|
||||||
|
ld (src_page),a
|
||||||
|
|
||||||
|
; NEW2 is required before any wave-memory access.
|
||||||
|
call chip_wait
|
||||||
|
ld a,#05
|
||||||
|
out (OPL_FM1_REG),a
|
||||||
|
call chip_wait
|
||||||
|
ld a,#03
|
||||||
|
out (OPL_FM1_DAT),a
|
||||||
|
|
||||||
|
.next_block:
|
||||||
|
ld hl,(src_ptr)
|
||||||
|
ld (block_end_ptr),hl
|
||||||
|
ld a,(src_page)
|
||||||
|
ld (block_end_page),a
|
||||||
|
call read_byte
|
||||||
|
cp #67
|
||||||
|
jr nz,.blocks_done
|
||||||
|
call read_byte
|
||||||
|
cp #66
|
||||||
|
jp nz,loader_fail
|
||||||
|
call read_byte
|
||||||
|
ld (block_type),a
|
||||||
|
cp #84
|
||||||
|
jr z,.read_size
|
||||||
|
cp #87
|
||||||
|
jr nz,.blocks_done
|
||||||
|
.read_size:
|
||||||
|
call read_byte
|
||||||
|
ld (block_len),a
|
||||||
|
call read_byte
|
||||||
|
ld (block_len+1),a
|
||||||
|
call read_byte
|
||||||
|
ld (block_len+2),a
|
||||||
|
call read_byte
|
||||||
|
ld (block_len+3),a
|
||||||
|
call block_fits_loaded
|
||||||
|
jp c,loader_fail
|
||||||
|
|
||||||
|
; ROM/RAM size is informational.
|
||||||
|
call read_byte
|
||||||
|
call read_byte
|
||||||
|
call read_byte
|
||||||
|
call read_byte
|
||||||
|
; Start address, little endian.
|
||||||
|
call read_byte
|
||||||
|
ld (start_lo),a
|
||||||
|
call read_byte
|
||||||
|
ld (start_mid),a
|
||||||
|
call read_byte
|
||||||
|
ld (start_hi),a
|
||||||
|
call read_byte
|
||||||
|
or a
|
||||||
|
jp nz,loader_fail
|
||||||
|
|
||||||
|
; payload length = block length - 8.
|
||||||
|
ld hl,(block_len)
|
||||||
|
ld de,8
|
||||||
|
or a
|
||||||
|
sbc hl,de
|
||||||
|
ld (block_len),hl
|
||||||
|
jr nc,.len_ready
|
||||||
|
ld hl,(block_len+2)
|
||||||
|
dec hl
|
||||||
|
ld (block_len+2),hl
|
||||||
|
.len_ready:
|
||||||
|
call block_fits_sram
|
||||||
|
jp c,loader_fail
|
||||||
|
call upload_block
|
||||||
|
ld hl,block_count
|
||||||
|
inc (hl)
|
||||||
|
jp .next_block
|
||||||
|
|
||||||
|
.blocks_done:
|
||||||
|
ld a,(block_count)
|
||||||
|
or a
|
||||||
|
jp z,loader_fail
|
||||||
|
call compact_and_patch
|
||||||
|
or a
|
||||||
|
jp z,loader_fail
|
||||||
|
ld c,a ; page count, keep returned HL intact
|
||||||
|
ld a,(rom_flag)
|
||||||
|
or a
|
||||||
|
ld a,c
|
||||||
|
jr z,.no_rom_flag
|
||||||
|
or #80
|
||||||
|
.no_rom_flag:
|
||||||
|
jr loader_return
|
||||||
|
|
||||||
|
loader_fail:
|
||||||
|
xor a
|
||||||
|
ld h,a
|
||||||
|
ld l,a
|
||||||
|
loader_return:
|
||||||
|
pop iy
|
||||||
|
pop ix
|
||||||
|
jp page0_return
|
||||||
|
|
||||||
|
; CF=1 if current src position + block_len exceeds the loaded 1 MiB.
|
||||||
|
block_fits_loaded:
|
||||||
|
ld hl,(src_ptr)
|
||||||
|
ld a,h
|
||||||
|
and #3F
|
||||||
|
ld h,a
|
||||||
|
ld a,(src_page)
|
||||||
|
ld d,a
|
||||||
|
and 3
|
||||||
|
rrca
|
||||||
|
rrca
|
||||||
|
and #C0
|
||||||
|
or h
|
||||||
|
ld h,a
|
||||||
|
ld a,d
|
||||||
|
srl a
|
||||||
|
srl a
|
||||||
|
ld e,a
|
||||||
|
ld d,0
|
||||||
|
ld bc,(block_len)
|
||||||
|
add hl,bc
|
||||||
|
ex de,hl
|
||||||
|
ld bc,(block_len+2)
|
||||||
|
adc hl,bc
|
||||||
|
ld a,h
|
||||||
|
or a
|
||||||
|
scf
|
||||||
|
ret nz
|
||||||
|
ld a,l
|
||||||
|
cp #10
|
||||||
|
jr nc,.too_far
|
||||||
|
or a
|
||||||
|
ret
|
||||||
|
.too_far:
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; CF=1 if start address + payload length exceeds 1 MiB SRAM.
|
||||||
|
block_fits_sram:
|
||||||
|
ld hl,(block_len)
|
||||||
|
ld a,(start_lo)
|
||||||
|
ld e,a
|
||||||
|
ld a,(start_mid)
|
||||||
|
ld d,a
|
||||||
|
add hl,de
|
||||||
|
ld c,l
|
||||||
|
ld b,h
|
||||||
|
ld hl,(block_len+2)
|
||||||
|
ld a,(start_hi)
|
||||||
|
ld e,a
|
||||||
|
ld d,0
|
||||||
|
adc hl,de
|
||||||
|
ld a,h
|
||||||
|
or a
|
||||||
|
scf
|
||||||
|
ret nz
|
||||||
|
ld a,l
|
||||||
|
cp #10
|
||||||
|
jr nc,.at_limit
|
||||||
|
or a
|
||||||
|
ret
|
||||||
|
.at_limit:
|
||||||
|
jr nz,.bad
|
||||||
|
ld a,b
|
||||||
|
or c
|
||||||
|
ret z
|
||||||
|
.bad: scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
upload_block:
|
||||||
|
; Address high/mid/low plus SRAM base #200000.
|
||||||
|
ld a,#03
|
||||||
|
call wave_reg
|
||||||
|
ld a,(start_hi)
|
||||||
|
add a,#20
|
||||||
|
call wave_dat
|
||||||
|
ld a,#04
|
||||||
|
call wave_reg
|
||||||
|
ld a,(start_mid)
|
||||||
|
call wave_dat
|
||||||
|
ld a,#05
|
||||||
|
call wave_reg
|
||||||
|
ld a,(start_lo)
|
||||||
|
call wave_dat
|
||||||
|
ld a,#02
|
||||||
|
call wave_reg
|
||||||
|
ld a,#11
|
||||||
|
call wave_dat
|
||||||
|
ld a,#06
|
||||||
|
call wave_reg
|
||||||
|
|
||||||
|
xor a
|
||||||
|
ld (hdr_pos),a
|
||||||
|
ld (hdr_left),a
|
||||||
|
ld a,(block_type)
|
||||||
|
cp #84
|
||||||
|
jr nz,.fast_test
|
||||||
|
ld a,(start_lo)
|
||||||
|
ld hl,start_mid
|
||||||
|
or (hl)
|
||||||
|
inc hl
|
||||||
|
or (hl)
|
||||||
|
jr nz,.fast_test
|
||||||
|
ld a,#80
|
||||||
|
ld (hdr_left),a
|
||||||
|
ld (rom_flag),a
|
||||||
|
|
||||||
|
.patch_test:
|
||||||
|
ld a,(hdr_left)
|
||||||
|
or a
|
||||||
|
jr z,.fast_test
|
||||||
|
call len_is_zero
|
||||||
|
jr z,.finish
|
||||||
|
call read_byte
|
||||||
|
ld e,a
|
||||||
|
ld a,(hdr_pos)
|
||||||
|
or a
|
||||||
|
jr nz,.patch_count
|
||||||
|
ld a,e
|
||||||
|
or #20
|
||||||
|
ld e,a
|
||||||
|
.patch_count:
|
||||||
|
ld a,(hdr_pos)
|
||||||
|
inc a
|
||||||
|
cp 12
|
||||||
|
jr nz,.store_pos
|
||||||
|
xor a
|
||||||
|
ld (hdr_pos),a
|
||||||
|
ld hl,hdr_left
|
||||||
|
dec (hl)
|
||||||
|
jr .patch_write
|
||||||
|
.store_pos:
|
||||||
|
ld (hdr_pos),a
|
||||||
|
.patch_write:
|
||||||
|
ld a,e
|
||||||
|
call wave_dat
|
||||||
|
call dec_len
|
||||||
|
jr .patch_test
|
||||||
|
|
||||||
|
.fast_test:
|
||||||
|
call len_is_zero
|
||||||
|
jr z,.finish
|
||||||
|
ld hl,(src_ptr)
|
||||||
|
xor a
|
||||||
|
sub l
|
||||||
|
ld c,a
|
||||||
|
ld a,0
|
||||||
|
sbc a,h
|
||||||
|
ld b,a ; BC = bytes to page boundary
|
||||||
|
ld hl,(block_len+2)
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
jr nz,.chunk_ready
|
||||||
|
ld hl,(block_len)
|
||||||
|
or a
|
||||||
|
sbc hl,bc
|
||||||
|
jr nc,.chunk_ready
|
||||||
|
ld bc,(block_len)
|
||||||
|
.chunk_ready:
|
||||||
|
ld hl,(src_ptr)
|
||||||
|
push bc
|
||||||
|
.byte_loop:
|
||||||
|
in a,(OPL_STAT)
|
||||||
|
rrca
|
||||||
|
jr c,.byte_loop
|
||||||
|
ld a,(hl)
|
||||||
|
out (OPL_WAVE_DAT),a
|
||||||
|
inc hl
|
||||||
|
dec bc
|
||||||
|
ld a,b
|
||||||
|
or c
|
||||||
|
jr nz,.byte_loop
|
||||||
|
ld (src_ptr),hl
|
||||||
|
pop bc
|
||||||
|
call sub_len_bc
|
||||||
|
ld hl,(src_ptr)
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
jr nz,.fast_test
|
||||||
|
call next_src_page
|
||||||
|
jr .fast_test
|
||||||
|
|
||||||
|
.finish:
|
||||||
|
ld a,#02
|
||||||
|
call wave_reg
|
||||||
|
ld a,#10 ; sound generation, WT base 200000h
|
||||||
|
jp wave_dat
|
||||||
|
|
||||||
|
; Compacts loaded data and patches offsets. It deliberately performs no WC
|
||||||
|
; calls: the resident wrapper reads the unread file tail after page 5 returns.
|
||||||
|
compact_and_patch:
|
||||||
|
; delta sectors = block_end/512 - 1
|
||||||
|
ld a,(block_end_page)
|
||||||
|
ld l,a
|
||||||
|
ld h,0
|
||||||
|
add hl,hl
|
||||||
|
add hl,hl
|
||||||
|
add hl,hl
|
||||||
|
add hl,hl
|
||||||
|
add hl,hl
|
||||||
|
ld de,(block_end_ptr)
|
||||||
|
ld a,d
|
||||||
|
and #3E
|
||||||
|
rrca
|
||||||
|
ld e,a
|
||||||
|
ld d,0
|
||||||
|
add hl,de
|
||||||
|
dec hl
|
||||||
|
ld a,l
|
||||||
|
add a,a
|
||||||
|
ld (delta+1),a
|
||||||
|
xor a
|
||||||
|
ld (delta),a
|
||||||
|
ld a,h
|
||||||
|
add a,a
|
||||||
|
ld b,a
|
||||||
|
ld a,l
|
||||||
|
rlca
|
||||||
|
and 1
|
||||||
|
or b
|
||||||
|
ld (delta+2),a
|
||||||
|
xor a
|
||||||
|
ld (delta+3),a
|
||||||
|
|
||||||
|
ld de,(block_end_ptr)
|
||||||
|
ld a,d
|
||||||
|
and 1
|
||||||
|
add a,2
|
||||||
|
ld d,a
|
||||||
|
ld (dst_ptr),de
|
||||||
|
call map_page0
|
||||||
|
ld hl,#C004
|
||||||
|
call sub_header32
|
||||||
|
ld hl,#C014
|
||||||
|
call sub_header32
|
||||||
|
ld hl,#C01C
|
||||||
|
call sub_header32
|
||||||
|
ld hl,(dst_ptr)
|
||||||
|
ld de,#0034
|
||||||
|
or a
|
||||||
|
sbc hl,de
|
||||||
|
ld (#C034),hl
|
||||||
|
xor a
|
||||||
|
ld (#C036),a
|
||||||
|
ld (#C037),a
|
||||||
|
|
||||||
|
; page count from compacted EOF+4, reject >1 MiB.
|
||||||
|
ld hl,(#C004)
|
||||||
|
ld bc,4
|
||||||
|
add hl,bc
|
||||||
|
ld de,(#C006)
|
||||||
|
jr nc,.size_ok
|
||||||
|
inc de
|
||||||
|
.size_ok:
|
||||||
|
ld a,d
|
||||||
|
or a
|
||||||
|
jp nz,.compact_fail
|
||||||
|
ld a,e
|
||||||
|
cp #10
|
||||||
|
jr c,.count_pages
|
||||||
|
jp nz,.compact_fail
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
jp nz,.compact_fail
|
||||||
|
.count_pages:
|
||||||
|
ld a,e
|
||||||
|
add a,a
|
||||||
|
add a,a
|
||||||
|
ld b,a
|
||||||
|
ld a,h
|
||||||
|
rlca
|
||||||
|
rlca
|
||||||
|
and 3
|
||||||
|
add a,b
|
||||||
|
ld b,a
|
||||||
|
ld a,h
|
||||||
|
and #3F
|
||||||
|
or l
|
||||||
|
jr z,.pages_ready
|
||||||
|
inc b
|
||||||
|
.pages_ready:
|
||||||
|
ld a,b
|
||||||
|
ld (result_pages),a
|
||||||
|
|
||||||
|
; Copy [block_end,1MiB) forward using Win3 -> Win0.
|
||||||
|
ld bc,PORT_W0
|
||||||
|
in a,(c)
|
||||||
|
push af
|
||||||
|
ld bc,PORT_W3
|
||||||
|
in a,(c)
|
||||||
|
push af
|
||||||
|
xor a
|
||||||
|
ld (dst_page),a
|
||||||
|
ld hl,(block_end_ptr)
|
||||||
|
ld de,(dst_ptr)
|
||||||
|
ld a,(block_end_page)
|
||||||
|
ld (copy_src_page),a
|
||||||
|
add a,#20
|
||||||
|
ld bc,PORT_W3
|
||||||
|
out (c),a
|
||||||
|
ld a,#20
|
||||||
|
ld bc,PORT_W0
|
||||||
|
out (c),a
|
||||||
|
.copy:
|
||||||
|
ld a,(hl)
|
||||||
|
ld (de),a
|
||||||
|
inc hl
|
||||||
|
inc de
|
||||||
|
ld a,d
|
||||||
|
cp #40
|
||||||
|
jr nz,.src_wrap
|
||||||
|
ld de,0
|
||||||
|
ld a,(dst_page)
|
||||||
|
inc a
|
||||||
|
ld (dst_page),a
|
||||||
|
add a,#20
|
||||||
|
ld bc,PORT_W0
|
||||||
|
out (c),a
|
||||||
|
.src_wrap:
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
jr nz,.copy
|
||||||
|
ld a,(copy_src_page)
|
||||||
|
inc a
|
||||||
|
ld (copy_src_page),a
|
||||||
|
cp 64
|
||||||
|
jr z,.copy_done
|
||||||
|
add a,#20
|
||||||
|
ld bc,PORT_W3
|
||||||
|
out (c),a
|
||||||
|
ld hl,#C000
|
||||||
|
jr .copy
|
||||||
|
.copy_done:
|
||||||
|
pop af
|
||||||
|
ld bc,PORT_W3
|
||||||
|
out (c),a
|
||||||
|
pop af
|
||||||
|
ld bc,PORT_W0
|
||||||
|
out (c),a
|
||||||
|
ld a,(dst_page)
|
||||||
|
ld h,a
|
||||||
|
ld a,d
|
||||||
|
or #C0
|
||||||
|
ld l,a
|
||||||
|
ld a,(result_pages)
|
||||||
|
ret
|
||||||
|
.compact_fail:
|
||||||
|
xor a
|
||||||
|
ret
|
||||||
|
|
||||||
|
sub_header32:
|
||||||
|
push hl
|
||||||
|
ld a,(hl)
|
||||||
|
inc hl
|
||||||
|
or (hl)
|
||||||
|
inc hl
|
||||||
|
or (hl)
|
||||||
|
inc hl
|
||||||
|
or (hl)
|
||||||
|
pop hl
|
||||||
|
ret z
|
||||||
|
push hl
|
||||||
|
ld e,(hl)
|
||||||
|
inc hl
|
||||||
|
ld d,(hl)
|
||||||
|
ex de,hl
|
||||||
|
ld bc,(delta)
|
||||||
|
or a
|
||||||
|
sbc hl,bc
|
||||||
|
ex de,hl
|
||||||
|
ld (hl),d
|
||||||
|
dec hl
|
||||||
|
ld (hl),e
|
||||||
|
pop hl
|
||||||
|
inc hl
|
||||||
|
inc hl
|
||||||
|
ld e,(hl)
|
||||||
|
inc hl
|
||||||
|
ld d,(hl)
|
||||||
|
ex de,hl
|
||||||
|
ld bc,(delta+2)
|
||||||
|
sbc hl,bc
|
||||||
|
ex de,hl
|
||||||
|
ld (hl),d
|
||||||
|
dec hl
|
||||||
|
ld (hl),e
|
||||||
|
ret
|
||||||
|
|
||||||
|
read_byte:
|
||||||
|
ld hl,(src_ptr)
|
||||||
|
ld a,(hl)
|
||||||
|
inc hl
|
||||||
|
ld (src_ptr),hl
|
||||||
|
push af
|
||||||
|
ld a,h
|
||||||
|
or l
|
||||||
|
jr nz,.no_wrap
|
||||||
|
call next_src_page
|
||||||
|
.no_wrap:
|
||||||
|
pop af
|
||||||
|
ret
|
||||||
|
|
||||||
|
next_src_page:
|
||||||
|
ld a,(src_page)
|
||||||
|
inc a
|
||||||
|
ld (src_page),a
|
||||||
|
call map_vpl
|
||||||
|
ld hl,#C000
|
||||||
|
ld (src_ptr),hl
|
||||||
|
ret
|
||||||
|
|
||||||
|
len_is_zero:
|
||||||
|
ld hl,block_len
|
||||||
|
ld a,(hl)
|
||||||
|
inc hl
|
||||||
|
or (hl)
|
||||||
|
inc hl
|
||||||
|
or (hl)
|
||||||
|
inc hl
|
||||||
|
or (hl)
|
||||||
|
ret
|
||||||
|
|
||||||
|
dec_len:
|
||||||
|
ld hl,block_len
|
||||||
|
ld a,(hl)
|
||||||
|
sub 1
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
ld a,(hl)
|
||||||
|
sbc a,0
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
ld a,(hl)
|
||||||
|
sbc a,0
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
ld a,(hl)
|
||||||
|
sbc a,0
|
||||||
|
ld (hl),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
sub_len_bc:
|
||||||
|
ld hl,(block_len)
|
||||||
|
or a
|
||||||
|
sbc hl,bc
|
||||||
|
ld (block_len),hl
|
||||||
|
ret nc
|
||||||
|
ld hl,(block_len+2)
|
||||||
|
dec hl
|
||||||
|
ld (block_len+2),hl
|
||||||
|
ret
|
||||||
|
|
||||||
|
chip_wait:
|
||||||
|
in a,(OPL_STAT)
|
||||||
|
rrca
|
||||||
|
jr c,chip_wait
|
||||||
|
ret
|
||||||
|
wave_reg:
|
||||||
|
push af
|
||||||
|
call chip_wait
|
||||||
|
pop af
|
||||||
|
out (OPL_WAVE_REG),a
|
||||||
|
ret
|
||||||
|
wave_dat:
|
||||||
|
push af
|
||||||
|
call chip_wait
|
||||||
|
pop af
|
||||||
|
out (OPL_WAVE_DAT),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
map_page0:
|
||||||
|
xor a
|
||||||
|
map_vpl:
|
||||||
|
add a,#20
|
||||||
|
ld bc,PORT_W3
|
||||||
|
out (c),a
|
||||||
|
ret
|
||||||
|
|
||||||
|
sig_vgm: DB "Vgm "
|
||||||
|
src_page: DB 0
|
||||||
|
src_ptr: DW #C000
|
||||||
|
block_end_page: DB 0
|
||||||
|
block_end_ptr: DW 0
|
||||||
|
block_type: DB 0
|
||||||
|
block_count: DB 0
|
||||||
|
block_len: DD 0
|
||||||
|
start_lo: DB 0
|
||||||
|
start_mid: DB 0
|
||||||
|
start_hi: DB 0
|
||||||
|
hdr_pos: DB 0
|
||||||
|
hdr_left: DB 0
|
||||||
|
rom_flag: DB 0
|
||||||
|
delta: DD 0
|
||||||
|
dst_page: DB 0
|
||||||
|
dst_ptr: DW 0
|
||||||
|
copy_src_page: DB 0
|
||||||
|
result_pages: DB 0
|
||||||
|
|
||||||
|
ASSERT $ <= #B800, "OPL4 cold loader exceeds reserved overlay"
|
||||||
|
|
||||||
|
; #6020 maps page 5 and jumps to the loader but deliberately does not restore
|
||||||
|
; Win2. Restore the physical page-0 mapping ourselves. OUT is placed at
|
||||||
|
; #BFFD; its following instruction is fetched from the newly mapped page 0,
|
||||||
|
; where cold_return.s puts a single RET at #BFFF. E preserves the loader's A
|
||||||
|
; result while D carries the physical page number to OUT (C),D, so returned A
|
||||||
|
; and HL remain intact.
|
||||||
|
ORG #BFEF
|
||||||
|
page0_return:
|
||||||
|
ld e,a
|
||||||
|
ld a,(#6002)
|
||||||
|
sub 5
|
||||||
|
ld (#6002),a
|
||||||
|
ld d,a
|
||||||
|
ld a,e
|
||||||
|
ld bc,PORT_W2 ; #12AF, Win2
|
||||||
|
out (c),d
|
||||||
|
ASSERT $ == #BFFF, "page0 return trampoline must end at #BFFE"
|
||||||
|
|
||||||
|
SAVEBIN "build/opl4_loader.bin", #9800, $-#9800
|
||||||
@@ -52,9 +52,15 @@ REM Паддинг inflate.bin до 16KB (чтобы cmdblocks.bin начина
|
|||||||
powershell -NoProfile -Command "$f=[IO.File]::ReadAllBytes('build\inflate.bin'); if($f.Length -lt 16384){$pad=New-Object byte[] (16384-$f.Length); [IO.File]::WriteAllBytes('build\inflate.bin',$f+$pad); Write-Host (' inflate.bin padded: '+$f.Length+' -> 16384')}"
|
powershell -NoProfile -Command "$f=[IO.File]::ReadAllBytes('build\inflate.bin'); if($f.Length -lt 16384){$pad=New-Object byte[] (16384-$f.Length); [IO.File]::WriteAllBytes('build\inflate.bin',$f+$pad); Write-Host (' inflate.bin padded: '+$f.Length+' -> 16384')}"
|
||||||
if errorlevel 1 ( echo FAIL pad inflate && goto :err )
|
if errorlevel 1 ( echo FAIL pad inflate && goto :err )
|
||||||
|
|
||||||
|
echo [3a/9] Сборка cold OPL4 loader...
|
||||||
|
..\tools\sjasm\sjasmplus.exe asm\opl4_loader.asm >nul 2>&1
|
||||||
|
if errorlevel 1 ( echo FAIL opl4_loader.asm && goto :err )
|
||||||
|
|
||||||
echo [3b/9] Сборка cmdblocks.asm (sjasmplus)...
|
echo [3b/9] Сборка cmdblocks.asm (sjasmplus)...
|
||||||
..\tools\sjasm\sjasmplus.exe asm\cmdblocks.asm >nul 2>&1
|
..\tools\sjasm\sjasmplus.exe asm\cmdblocks.asm >nul 2>&1
|
||||||
if errorlevel 1 ( echo FAIL cmdblocks.asm && goto :err )
|
if errorlevel 1 ( echo FAIL cmdblocks.asm && goto :err )
|
||||||
|
node scripts\overlay_bin.js build\cmdblocks.bin build\opl4_loader.bin 0x1800
|
||||||
|
if errorlevel 1 ( echo FAIL overlay OPL4 loader && goto :err )
|
||||||
REM cmdblocks.asm имеет SAVEBIN "build/cmdblocks.bin" внутри
|
REM cmdblocks.asm имеет SAVEBIN "build/cmdblocks.bin" внутри
|
||||||
|
|
||||||
echo [4/9] Компиляция C файлов...
|
echo [4/9] Компиляция C файлов...
|
||||||
@@ -91,6 +97,9 @@ if errorlevel 1 ( echo FAIL inflate_call.s && goto :err )
|
|||||||
sdasz80 %ASFLAGS% build\spectrum.rel asm\spectrum.s >nul
|
sdasz80 %ASFLAGS% build\spectrum.rel asm\spectrum.s >nul
|
||||||
if errorlevel 1 ( echo FAIL spectrum.s && goto :err )
|
if errorlevel 1 ( echo FAIL spectrum.s && goto :err )
|
||||||
|
|
||||||
|
sdasz80 %ASFLAGS% build\cold_return.rel asm\cold_return.s >nul
|
||||||
|
if errorlevel 1 ( echo FAIL cold_return.s && goto :err )
|
||||||
|
|
||||||
echo [6/9] Линковка...
|
echo [6/9] Линковка...
|
||||||
REM Порядок важен: crt0 первым (entry point), затем модули
|
REM Порядок важен: crt0 первым (entry point), затем модули
|
||||||
REM Layout: CODE #8000–#B8CF, DATA #B8D0–#BFFF
|
REM Layout: CODE #8000–#B8CF, DATA #B8D0–#BFFF
|
||||||
@@ -108,6 +117,7 @@ sdcc -mz80 --no-std-crt0 --out-fmt-ihx --code-loc 0x8000 --data-loc %DATA_LOC% ^
|
|||||||
build\txtlib.rel ^
|
build\txtlib.rel ^
|
||||||
build\inflate_call.rel ^
|
build\inflate_call.rel ^
|
||||||
build\spectrum.rel ^
|
build\spectrum.rel ^
|
||||||
|
build\cold_return.rel ^
|
||||||
-o build\vgmplay.ihx >nul
|
-o build\vgmplay.ihx >nul
|
||||||
if errorlevel 1 ( echo FAIL link && goto :err )
|
if errorlevel 1 ( echo FAIL link && goto :err )
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
/* Плотная раскладка: полная версия spectrum.s + загрузчик OPL4.
|
/* Плотная раскладка: полная версия spectrum.s + загрузчик OPL4.
|
||||||
* Проверяется build.bat: _CODE должен закончиться раньше _DATA,
|
* Проверяется build.bat: _CODE должен закончиться раньше _DATA,
|
||||||
* а последняя секция — раньше 0xC000. */
|
* а последняя секция — раньше 0xC000. */
|
||||||
#define VARIANT_DATA_LOC 0xB8D0
|
#define VARIANT_DATA_LOC 0xB8DC
|
||||||
|
|
||||||
/* ── Спектроанализатор: скорости затухания (в TV-кадрах) ────────── */
|
/* ── Спектроанализатор: скорости затухания (в TV-кадрах) ────────── */
|
||||||
/* Меньше = быстрее затухание. 1 = каждый кадр (~20 мс). */
|
/* Меньше = быстрее затухание. 1 = каждый кадр (~20 мс). */
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ extern uint8_t vgm_chip_type;
|
|||||||
|
|
||||||
/** Массив обнаруженных чипов (для вывода информации о файле) */
|
/** Массив обнаруженных чипов (для вывода информации о файле) */
|
||||||
extern uint8_t vgm_chip_count;
|
extern uint8_t vgm_chip_count;
|
||||||
|
extern uint8_t vgm_opl4_preloaded_rom;
|
||||||
extern vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS];
|
extern vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS];
|
||||||
|
|
||||||
/** Адрес точки петли (#C000–#FFFF); 0 = нет петли */
|
/** Адрес точки петли (#C000–#FFFF); 0 = нет петли */
|
||||||
|
|||||||
+249
-107
@@ -41,6 +41,7 @@ uint16_t vgm_end_addr; // смещение в окне #C000, на к
|
|||||||
uint16_t vgm_version; // версия VGM в BCD (например, 0x0151 для v1.51)
|
uint16_t vgm_version; // версия VGM в BCD (например, 0x0151 для v1.51)
|
||||||
uint8_t vgm_chip_type; // битовая маска типа чипов (VGM_CHIP_xxx), например VGM_CHIP_OPL2 | VGM_CHIP_AY
|
uint8_t vgm_chip_type; // битовая маска типа чипов (VGM_CHIP_xxx), например VGM_CHIP_OPL2 | VGM_CHIP_AY
|
||||||
uint8_t vgm_chip_count; // количество обнаруженных чипов (для вывода информации о файле)
|
uint8_t vgm_chip_count; // количество обнаруженных чипов (для вывода информации о файле)
|
||||||
|
uint8_t vgm_opl4_preloaded_rom; // cold loader already moved a 0x84 image
|
||||||
|
|
||||||
vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS];
|
vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS];
|
||||||
|
|
||||||
@@ -185,26 +186,63 @@ static const chip_scan_t chip_scan[] = {
|
|||||||
|
|
||||||
/* Пропустить n байт в VGM-потоке (арифметически, без чтения).
|
/* Пропустить n байт в VGM-потоке (арифметически, без чтения).
|
||||||
* Используется для data block 0x67, PCM RAM write 0x68 и др. */
|
* Используется для data block 0x67, PCM RAM write 0x68 и др. */
|
||||||
static void vgm_skip(uint32_t n)
|
static void vgm_skip(uint32_t n) __naked
|
||||||
{
|
{
|
||||||
uint16_t ptr = vgm_read_ptr;
|
(void)n;
|
||||||
uint32_t avail = 0x10000UL - (uint32_t)ptr;
|
__asm
|
||||||
|
; sdcccall(1): n = HL:DE (HL high word, DE low word).
|
||||||
|
; Convert the mapped address C000h..FFFFh to a 14-bit page offset,
|
||||||
|
; add it to n, then split the result back into page and address.
|
||||||
|
ld bc, (_vgm_read_ptr)
|
||||||
|
ld a, b
|
||||||
|
and a, #0x3F
|
||||||
|
ld b, a
|
||||||
|
|
||||||
if (n < avail) {
|
ex de, hl ; HL = n.low, DE = n.high
|
||||||
vgm_read_ptr = ptr + (uint16_t)n;
|
add hl, bc
|
||||||
return;
|
ex de, hl ; DE = total.low, HL = total.high
|
||||||
}
|
jr nc, vsk_no_carry
|
||||||
|
inc hl
|
||||||
|
vsk_no_carry:
|
||||||
|
|
||||||
n -= avail;
|
; Remember whether at least one 16K boundary was crossed.
|
||||||
vgm_cur_page++;
|
ld a, d
|
||||||
|
and a, #0xC0
|
||||||
|
or a, l
|
||||||
|
or a, h
|
||||||
|
ld b, a
|
||||||
|
|
||||||
while (n >= 0x4000UL) {
|
; page_delta = (total >> 14) & 0xFF.
|
||||||
n -= 0x4000UL;
|
ld a, d
|
||||||
vgm_cur_page++;
|
rlca
|
||||||
}
|
rlca
|
||||||
|
and a, #0x03
|
||||||
|
ld c, a
|
||||||
|
ld a, l
|
||||||
|
add a, a
|
||||||
|
add a, a
|
||||||
|
or a, c
|
||||||
|
ld c, a
|
||||||
|
|
||||||
vgm_read_ptr = 0xC000 + (uint16_t)n;
|
; Address within the selected page remains in total.low bits 0..13.
|
||||||
wc_mngcvpl(vgm_cur_page);
|
ld a, d
|
||||||
|
and a, #0x3F
|
||||||
|
or a, #0xC0
|
||||||
|
ld d, a
|
||||||
|
ld (_vgm_read_ptr), de
|
||||||
|
|
||||||
|
ld a, (_vgm_cur_page)
|
||||||
|
add a, c
|
||||||
|
ld (_vgm_cur_page), a
|
||||||
|
|
||||||
|
; The old C implementation remapped only after crossing a boundary.
|
||||||
|
ld a, b
|
||||||
|
or a
|
||||||
|
ret z
|
||||||
|
ld a, (_vgm_cur_page)
|
||||||
|
call _wc_mngcvpl
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Деление числа VGM-сэмплов на 44100 вызывается только при разборе
|
/* Деление числа VGM-сэмплов на 44100 вызывается только при разборе
|
||||||
@@ -251,7 +289,8 @@ uint8_t vgm_parse_header(void)
|
|||||||
vgm_paused = 0;
|
vgm_paused = 0;
|
||||||
vgm_chip_count = 0;
|
vgm_chip_count = 0;
|
||||||
vgm_chip_type = VGM_CHIP_NONE;
|
vgm_chip_type = VGM_CHIP_NONE;
|
||||||
opl4_rom_loaded = 0;
|
opl4_rom_loaded = vgm_opl4_preloaded_rom;
|
||||||
|
vgm_opl4_preloaded_rom = 0;
|
||||||
for (i = 0; i < 3; i++)
|
for (i = 0; i < 3; i++)
|
||||||
spec_wave_active[i] = 0;
|
spec_wave_active[i] = 0;
|
||||||
vgm_loop_addr = 0;
|
vgm_loop_addr = 0;
|
||||||
@@ -884,106 +923,209 @@ wuf_loop:
|
|||||||
* len = полный размер data-block (4-байтовое поле "size" из потока),
|
* len = полный размер data-block (4-байтовое поле "size" из потока),
|
||||||
* включает 8-байтовый под-заголовок выше.
|
* включает 8-байтовый под-заголовок выше.
|
||||||
* ───────────────────────────────────────────────────────────────────── */
|
* ───────────────────────────────────────────────────────────────────── */
|
||||||
static void wave_upload_block(void)
|
static void wave_upload_block(void) __naked
|
||||||
{
|
{
|
||||||
/* Длина payload — прямо через fb_t32 (уже посчитан вызывающим кодом),
|
|
||||||
* без промежуточной локальной переменной — короче код (без спилла
|
|
||||||
* в стек-фрейм IX; ничто другое fb_t32 в течение этой функции не
|
|
||||||
* трогает). */
|
|
||||||
/* Порядок записи регистров адреса — high, mid, low (как в референсном
|
|
||||||
* NedoOS-драйвере, opl4setmemoryaddress) — на реальном чипе порядок,
|
|
||||||
* похоже, важен, в отличие от того, что можно было бы предположить
|
|
||||||
* по документации эмулятора. Граница SRAM 0x200000 имеет нулевые
|
|
||||||
* младшие 2 байта, поэтому "+0x200000" — это "+0x20" к старшему
|
|
||||||
* байту адреса (предполагает startAddr < 0xE00000 — с большим
|
|
||||||
* запасом для реальных VGM). */
|
|
||||||
asm_read_2bytes(); asm_read_2bytes(); /* ROM/RAM Size (4 байта) — не используется */
|
|
||||||
|
|
||||||
/* Синхронно (не через cmd-buffer) включить NEW2 в FM Bank1 (рег.#05=3):
|
|
||||||
* без этого чип игнорирует ЛЮБЫЕ обращения к wave-части, включая
|
|
||||||
* доступ к памяти. CMDBLK_INIT_OPL4 делает то же самое через ISR-
|
|
||||||
* буфер — но применится только когда реально запустится ISR, а эта
|
|
||||||
* заливка идёт синхронно ДО этого момента. Заинлайнено — вызывается
|
|
||||||
* только отсюда. Регистр #02 переключается ниже непосредственно
|
|
||||||
* перед заливкой и сразу возвращается в sound-generation mode. */
|
|
||||||
__asm
|
__asm
|
||||||
|
; ROM/RAM Size (4 bytes) is informational only.
|
||||||
|
call _asm_read_2bytes
|
||||||
|
call _asm_read_2bytes
|
||||||
|
|
||||||
|
; NEW2: FM bank 1, register 05h = 03h. Wave registers ignore writes
|
||||||
|
; until this bit is enabled, and the command ISR has not started yet.
|
||||||
call _chip_wait
|
call _chip_wait
|
||||||
ld a, #0x05
|
ld a, #0x05
|
||||||
out (0xC6), a
|
out (0xC6), a
|
||||||
call _chip_wait
|
call _chip_wait
|
||||||
ld a, #0x03
|
ld a, #0x03
|
||||||
out (0xC7), a
|
out (0xC7), a
|
||||||
|
|
||||||
|
; VGM start address is little-endian; YMF278B wants high/mid/low.
|
||||||
|
call _asm_read_byte
|
||||||
|
ld (_fb_b2), a
|
||||||
|
call _asm_read_byte
|
||||||
|
ld (_fb_b1), a
|
||||||
|
call _asm_read_byte
|
||||||
|
ld (_fb_ch), a
|
||||||
|
call _asm_read_byte ; unused top byte of the 32-bit address
|
||||||
|
|
||||||
|
; Patch 128 twelve-byte headers only for a ROM image beginning at 0.
|
||||||
|
xor a
|
||||||
|
ld (_hdr_recs), a
|
||||||
|
ld (_hdr_sub), a
|
||||||
|
ld a, (_fb_reg)
|
||||||
|
cp a, #0x84
|
||||||
|
jr nz, wub_header_ready
|
||||||
|
ld a, (_fb_ch)
|
||||||
|
ld hl, #_fb_b1
|
||||||
|
or a, (hl)
|
||||||
|
inc hl
|
||||||
|
or a, (hl)
|
||||||
|
jr nz, wub_header_ready
|
||||||
|
ld a, #0x80
|
||||||
|
ld (_hdr_recs), a
|
||||||
|
wub_header_ready:
|
||||||
|
|
||||||
|
; Memory address = 200000h + start address. Register order matters.
|
||||||
|
ld a, #0x03
|
||||||
|
call _wave_out_reg
|
||||||
|
ld a, (_fb_ch)
|
||||||
|
add a, #0x20
|
||||||
|
call _wave_out_dat
|
||||||
|
ld a, #0x04
|
||||||
|
call _wave_out_reg
|
||||||
|
ld a, (_fb_b1)
|
||||||
|
call _wave_out_dat
|
||||||
|
ld a, #0x05
|
||||||
|
call _wave_out_reg
|
||||||
|
ld a, (_fb_b2)
|
||||||
|
call _wave_out_dat
|
||||||
|
|
||||||
|
; 11h = memory access mode, WT header base 200000h; 06h = data port.
|
||||||
|
ld a, #0x02
|
||||||
|
call _wave_out_reg
|
||||||
|
ld a, #0x11
|
||||||
|
call _wave_out_dat
|
||||||
|
ld a, #0x06
|
||||||
|
call _wave_out_reg
|
||||||
|
|
||||||
|
; fb_t32 contains the whole block payload, including its 8-byte header.
|
||||||
|
ld hl, (_fb_t32)
|
||||||
|
ld de, #0x0008
|
||||||
|
or a
|
||||||
|
sbc hl, de
|
||||||
|
ld (_fb_t32), hl
|
||||||
|
jr nc, wub_len_ready
|
||||||
|
ld hl, (_fb_t32 + 2)
|
||||||
|
dec hl
|
||||||
|
ld (_fb_t32 + 2), hl
|
||||||
|
wub_len_ready:
|
||||||
|
|
||||||
|
ld a, (_hdr_recs)
|
||||||
|
or a
|
||||||
|
jr z, wub_fast_test
|
||||||
|
ld a, #0x01
|
||||||
|
ld (_opl4_rom_loaded), a
|
||||||
|
|
||||||
|
; Patch address bit 21 in byte 0 of each of the first 128 headers.
|
||||||
|
wub_patch_test:
|
||||||
|
ld a, (_hdr_recs)
|
||||||
|
or a
|
||||||
|
jr z, wub_fast_test
|
||||||
|
ld hl, #_fb_t32
|
||||||
|
ld a, (hl)
|
||||||
|
inc hl
|
||||||
|
or a, (hl)
|
||||||
|
inc hl
|
||||||
|
or a, (hl)
|
||||||
|
inc hl
|
||||||
|
or a, (hl)
|
||||||
|
jp z, wub_finish
|
||||||
|
|
||||||
|
call _asm_read_byte
|
||||||
|
ld e, a
|
||||||
|
ld a, (_hdr_sub)
|
||||||
|
or a
|
||||||
|
jr nz, wub_patch_count
|
||||||
|
ld a, e
|
||||||
|
or a, #0x20
|
||||||
|
ld e, a
|
||||||
|
wub_patch_count:
|
||||||
|
ld a, (_hdr_sub)
|
||||||
|
inc a
|
||||||
|
cp a, #0x0C
|
||||||
|
jr nz, wub_store_sub
|
||||||
|
xor a
|
||||||
|
ld (_hdr_sub), a
|
||||||
|
ld hl, #_hdr_recs
|
||||||
|
dec (hl)
|
||||||
|
jr wub_patch_write
|
||||||
|
wub_store_sub:
|
||||||
|
ld (_hdr_sub), a
|
||||||
|
wub_patch_write:
|
||||||
|
ld a, e
|
||||||
|
call _wave_out_dat
|
||||||
|
|
||||||
|
; --fb_t32. The high word changes only when the low word wraps.
|
||||||
|
ld hl, (_fb_t32)
|
||||||
|
dec hl
|
||||||
|
ld (_fb_t32), hl
|
||||||
|
ld a, h
|
||||||
|
and a, l
|
||||||
|
inc a
|
||||||
|
jr nz, wub_patch_test
|
||||||
|
ld hl, (_fb_t32 + 2)
|
||||||
|
dec hl
|
||||||
|
ld (_fb_t32 + 2), hl
|
||||||
|
jr wub_patch_test
|
||||||
|
|
||||||
|
; Upload the rest in chunks that never cross a mapped 16K page.
|
||||||
|
wub_fast_test:
|
||||||
|
ld hl, #_fb_t32
|
||||||
|
ld a, (hl)
|
||||||
|
inc hl
|
||||||
|
or a, (hl)
|
||||||
|
inc hl
|
||||||
|
or a, (hl)
|
||||||
|
inc hl
|
||||||
|
or a, (hl)
|
||||||
|
jr z, wub_finish
|
||||||
|
|
||||||
|
; fb_w = 0 - fb_rp: number of bytes through FFFFh inclusive.
|
||||||
|
ld hl, (_fb_rp)
|
||||||
|
xor a
|
||||||
|
sub a, l
|
||||||
|
ld e, a
|
||||||
|
ld a, #0x00
|
||||||
|
sbc a, h
|
||||||
|
ld d, a
|
||||||
|
ld (_fb_w), de
|
||||||
|
|
||||||
|
; If the remaining 32-bit length is smaller, use its low word.
|
||||||
|
ld hl, (_fb_t32 + 2)
|
||||||
|
ld a, h
|
||||||
|
or a, l
|
||||||
|
jr nz, wub_fast_upload
|
||||||
|
ld hl, (_fb_t32)
|
||||||
|
or a
|
||||||
|
sbc hl, de
|
||||||
|
jr nc, wub_fast_upload
|
||||||
|
ld hl, (_fb_t32)
|
||||||
|
ld (_fb_w), hl
|
||||||
|
wub_fast_upload:
|
||||||
|
call _wave_upload_fast
|
||||||
|
|
||||||
|
; fb_t32 -= fb_w, propagating carry into the high word.
|
||||||
|
ld hl, (_fb_t32)
|
||||||
|
ld de, (_fb_w)
|
||||||
|
or a
|
||||||
|
sbc hl, de
|
||||||
|
ld (_fb_t32), hl
|
||||||
|
jr nc, wub_after_subtract
|
||||||
|
ld hl, (_fb_t32 + 2)
|
||||||
|
dec hl
|
||||||
|
ld (_fb_t32 + 2), hl
|
||||||
|
wub_after_subtract:
|
||||||
|
|
||||||
|
; wave_upload_fast leaves fb_rp at zero exactly on a page boundary.
|
||||||
|
ld hl, (_fb_rp)
|
||||||
|
ld a, h
|
||||||
|
or a, l
|
||||||
|
jr nz, wub_fast_test
|
||||||
|
ld hl, #_fb_cpg
|
||||||
|
inc (hl)
|
||||||
|
ld a, (hl)
|
||||||
|
call _wc_mngcvpl
|
||||||
|
ld hl, #0xC000
|
||||||
|
ld (_fb_rp), hl
|
||||||
|
jr wub_fast_test
|
||||||
|
|
||||||
|
wub_finish:
|
||||||
|
ld a, #0x02
|
||||||
|
call _wave_out_reg
|
||||||
|
ld a, #0x10 ; sound generation, WT base 200000h
|
||||||
|
call _wave_out_dat
|
||||||
|
ret
|
||||||
__endasm;
|
__endasm;
|
||||||
|
|
||||||
/* Переиспользуем существующие static (fb_ch/fb_b1/fb_b2) вместо
|
|
||||||
* локальных переменных — короче код (без спилла в стек-фрейм IX),
|
|
||||||
* ветки, где они обычно используются (AY/YM2203, чтение 2 байт),
|
|
||||||
* с этой не пересекаются. */
|
|
||||||
/* Start Address в VGM записан в Intel byte order (little-endian).
|
|
||||||
* Регистры YMF278B #03/#04/#05, наоборот, принимают адрес как
|
|
||||||
* high/mid/low. Поэтому читаем младший байт в fb_b2, а старший из
|
|
||||||
* используемых 24 бит — в fb_ch. Прежний порядок случайно работал
|
|
||||||
* для адресов вида 0x00xx00 (например, 0x001800), но перемешивал
|
|
||||||
* почти все последующие куски ROM/RAM image. */
|
|
||||||
fb_b2 = asm_read_byte(); /* младший байт адреса */
|
|
||||||
fb_b1 = asm_read_byte(); /* средний байт адреса */
|
|
||||||
fb_ch = asm_read_byte(); /* старший байт (до добавления базы SRAM) */
|
|
||||||
asm_read_byte(); /* старший байт 32-битного поля (>16 МБ) */
|
|
||||||
|
|
||||||
/* Блок покрывает таблицу заголовков, только если это 0x84 (ROM Image)
|
|
||||||
* И его начало — самый старт RAM-региона (адрес 0). Именно так
|
|
||||||
* выглядят все реальные файлы этого типа (заголовки всегда идут
|
|
||||||
* первыми). Более общий случай (старт не с нуля) не поддерживаем —
|
|
||||||
* не встречался ни в одном реальном VGM. */
|
|
||||||
hdr_recs = (fb_reg == 0x84 && (fb_ch | fb_b1 | fb_b2) == 0) ? 128 : 0;
|
|
||||||
hdr_sub = 0;
|
|
||||||
|
|
||||||
/* Порядок записи регистров #03/#04/#05 (hi/mid/lo) — как в референсном
|
|
||||||
* NedoOS-драйвере (opl4setmemoryaddress): high, mid, low. */
|
|
||||||
wave_out_reg(0x03); wave_out_dat(fb_ch + 0x20); /* + база SRAM (0x200000) */
|
|
||||||
wave_out_reg(0x04); wave_out_dat(fb_b1);
|
|
||||||
wave_out_reg(0x05); wave_out_dat(fb_b2);
|
|
||||||
|
|
||||||
/* Регистр #02: 0x11 = WT header base 0x200000 + memory access mode.
|
|
||||||
* Ранее адрес и значение были перепутаны (#11 <- #02), поэтому SRAM
|
|
||||||
* фактически не переходила в режим записи. */
|
|
||||||
wave_out_reg(0x02); wave_out_dat(0x11);
|
|
||||||
|
|
||||||
wave_out_reg(0x06); /* регистр данных памяти — дальше только данные,
|
|
||||||
* memaddr авто-инкрементируется самим чипом */
|
|
||||||
fb_t32 -= 8;
|
|
||||||
|
|
||||||
if (hdr_recs)
|
|
||||||
opl4_rom_loaded = 1;
|
|
||||||
|
|
||||||
/* Первые 128 заголовков ROM image требуют побайтного патча адреса. */
|
|
||||||
while (hdr_recs && fb_t32) {
|
|
||||||
uint8_t b = asm_read_byte();
|
|
||||||
if (hdr_sub == 0)
|
|
||||||
b |= 0x20; /* взвести адресный бит 0x200000 */
|
|
||||||
if (++hdr_sub == 12) {
|
|
||||||
hdr_sub = 0;
|
|
||||||
hdr_recs--;
|
|
||||||
}
|
|
||||||
wave_out_dat(b);
|
|
||||||
fb_t32--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Остаток блока — быстрыми кусками до границы VPL-страницы. */
|
|
||||||
while (fb_t32) {
|
|
||||||
fb_w = (uint16_t)(0u - (uint16_t)fb_rp); /* байт до wrap #FFFF->#0000 */
|
|
||||||
if (fb_t32 < fb_w)
|
|
||||||
fb_w = (uint16_t)fb_t32;
|
|
||||||
wave_upload_fast();
|
|
||||||
fb_t32 -= fb_w;
|
|
||||||
if (!(uint16_t)fb_rp) {
|
|
||||||
fb_cpg++;
|
|
||||||
wc_mngcvpl(fb_cpg);
|
|
||||||
fb_rp = (uint8_t *)0xC000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wave_out_reg(0x02); wave_out_dat(0x10); /* sound generation, WT base 0x200000 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─────────────────────────────────────────────────────────────────────
|
/* ─────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+248
-56
@@ -101,38 +101,79 @@ extern uint8_t cfg_saa_mode;
|
|||||||
static uint8_t s_saa_mode_user; /* user setting from INI (preserved across tracks) */
|
static uint8_t s_saa_mode_user; /* user setting from INI (preserved across tracks) */
|
||||||
static uint8_t s_board_dual_saa; /* 0=одночиповая(default), 1=двухчиповая (INI param 4) */
|
static uint8_t s_board_dual_saa; /* 0=одночиповая(default), 1=двухчиповая (INI param 4) */
|
||||||
|
|
||||||
static void detect_active_chips(void)
|
static void detect_active_chips(void) __naked
|
||||||
{
|
{
|
||||||
s_has_opl = s_has_ay = s_has_ay2 = 0;
|
__asm
|
||||||
s_has_saa = s_has_saa2 = 0;
|
; The seven cache bytes are declared consecutively above.
|
||||||
s_has_ym2203 = s_has_ym2203_2 = 0;
|
xor a
|
||||||
for (uint8_t i = 0; i < vgm_chip_count; i++) {
|
ld hl, #_s_has_opl
|
||||||
uint8_t id = vgm_chip_list[i].id;
|
ld b, #7
|
||||||
uint8_t dual = vgm_chip_list[i].flags & VGM_CF_DUAL;
|
dac_clear:
|
||||||
switch (id) {
|
ld (hl), a
|
||||||
case VGM_OFF_YM2203:
|
inc hl
|
||||||
s_has_ym2203 = 1;
|
djnz dac_clear
|
||||||
if (dual) s_has_ym2203_2 = 1;
|
|
||||||
/* fallthrough (проваливаемся) */
|
ld a, (_vgm_chip_count)
|
||||||
case VGM_OFF_AY8910:
|
or a
|
||||||
s_has_ay = 1;
|
ret z
|
||||||
if (dual) s_has_ay2 = 1;
|
ld b, a
|
||||||
break;
|
ld de, #_vgm_chip_list
|
||||||
case VGM_OFF_YM3526:
|
|
||||||
case VGM_OFF_YM3812:
|
dac_loop:
|
||||||
case VGM_OFF_Y8950:
|
ld a, (de) ; id
|
||||||
case VGM_OFF_YMF262:
|
ld l, a
|
||||||
case VGM_OFF_YMF278B:
|
inc de
|
||||||
s_has_opl = 1;
|
ld a, (de) ; flags (bit 0 = dual)
|
||||||
break;
|
ld c, a
|
||||||
case VGM_OFF_SAA1099:
|
inc de ; skip flags + clock_khz (2 bytes)
|
||||||
s_has_saa = 1;
|
inc de
|
||||||
if (dual) s_has_saa2 = 1;
|
inc de
|
||||||
break;
|
|
||||||
default:
|
ld a, l
|
||||||
break;
|
cp a, #0x44 ; YM2203
|
||||||
}
|
jr z, dac_ym2203
|
||||||
}
|
cp a, #0x74 ; AY8910
|
||||||
|
jr z, dac_ay
|
||||||
|
cp a, #0xC8 ; SAA1099
|
||||||
|
jr z, dac_saa
|
||||||
|
|
||||||
|
; OPL ids are 50h,54h,58h,5Ch,60h: one aligned range check.
|
||||||
|
cp a, #0x50
|
||||||
|
jr c, dac_next
|
||||||
|
cp a, #0x61
|
||||||
|
jr nc, dac_next
|
||||||
|
and a, #0x03
|
||||||
|
jr nz, dac_next
|
||||||
|
ld a, #1
|
||||||
|
ld (_s_has_opl), a
|
||||||
|
jr dac_next
|
||||||
|
|
||||||
|
dac_ym2203:
|
||||||
|
ld a, #1
|
||||||
|
ld (_s_has_ym2203), a
|
||||||
|
bit 0, c
|
||||||
|
jr z, dac_ay
|
||||||
|
ld (_s_has_ym2203_2), a
|
||||||
|
; YM2203 also contains an AY-compatible PSG, so fall through.
|
||||||
|
dac_ay:
|
||||||
|
ld a, #1
|
||||||
|
ld (_s_has_ay), a
|
||||||
|
bit 0, c
|
||||||
|
jr z, dac_next
|
||||||
|
ld (_s_has_ay2), a
|
||||||
|
jr dac_next
|
||||||
|
|
||||||
|
dac_saa:
|
||||||
|
ld a, #1
|
||||||
|
ld (_s_has_saa), a
|
||||||
|
bit 0, c
|
||||||
|
jr z, dac_next
|
||||||
|
ld (_s_has_saa2), a
|
||||||
|
|
||||||
|
dac_next:
|
||||||
|
djnz dac_loop
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* cfg_loop_rewinds, cfg_min_duration, cfg_max_duration определены в vgm.c */
|
/* cfg_loop_rewinds, cfg_min_duration, cfg_max_duration определены в vgm.c */
|
||||||
@@ -526,27 +567,144 @@ uint8_t drow_ui(void)
|
|||||||
/* render_static_info() удалена — заменена на drow_ui() */
|
/* render_static_info() удалена — заменена на drow_ui() */
|
||||||
|
|
||||||
/* Проверить расширение файла: .vgz / .VGZ */
|
/* Проверить расширение файла: .vgz / .VGZ */
|
||||||
static uint8_t is_vgz_filename(void)
|
static uint8_t is_vgz_filename(void) __naked
|
||||||
{
|
{
|
||||||
const char *p = wc_file_name;
|
__asm
|
||||||
const char *dot = 0;
|
ld hl, (_wc_file_name)
|
||||||
while (*p) {
|
ld de, #0 ; address of the last dot
|
||||||
if (*p == '.') dot = p;
|
ivf_scan:
|
||||||
p++;
|
ld a, (hl)
|
||||||
|
or a
|
||||||
|
jr z, ivf_check
|
||||||
|
cp a, #'.'
|
||||||
|
jr nz, ivf_next
|
||||||
|
ld d, h
|
||||||
|
ld e, l
|
||||||
|
ivf_next:
|
||||||
|
inc hl
|
||||||
|
jr ivf_scan
|
||||||
|
|
||||||
|
ivf_check:
|
||||||
|
ld a, d
|
||||||
|
or a, e
|
||||||
|
jr z, ivf_no
|
||||||
|
inc de
|
||||||
|
ex de, hl
|
||||||
|
|
||||||
|
; ASCII upper/lower case differ by bit 5.
|
||||||
|
ld a, (hl)
|
||||||
|
or a, #0x20
|
||||||
|
cp a, #'v'
|
||||||
|
jr nz, ivf_no
|
||||||
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
or a, #0x20
|
||||||
|
cp a, #'g'
|
||||||
|
jr nz, ivf_no
|
||||||
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
or a, #0x20
|
||||||
|
cp a, #'z'
|
||||||
|
jr nz, ivf_no
|
||||||
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
or a
|
||||||
|
jr nz, ivf_no
|
||||||
|
inc a ; return 1
|
||||||
|
ret
|
||||||
|
ivf_no:
|
||||||
|
xor a ; return 0
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
}
|
}
|
||||||
if (!dot) return 0;
|
|
||||||
dot++;
|
/* The unused tail of plugin-relative page 5 (not WC physical page #05)
|
||||||
if ((dot[0]=='v'||dot[0]=='V') &&
|
* contains the one-shot large OPL4 loader at logical address 9800h.
|
||||||
(dot[1]=='g'||dot[1]=='G') &&
|
* Offset 1800h is also above WC's 1000h..1447h screen scratch range. */
|
||||||
(dot[2]=='z'||dot[2]=='Z') &&
|
static uint8_t load_large_opl4(void) __naked
|
||||||
dot[3]==0)
|
{
|
||||||
return 1;
|
__asm
|
||||||
return 0;
|
ld a, i
|
||||||
|
push af ; P/V remembers incoming IFF2
|
||||||
|
di ; page 5 does not contain the resident ISR
|
||||||
|
ld a, #5
|
||||||
|
ld hl, #0x9800
|
||||||
|
ld de, #ll4_after_cold
|
||||||
|
push de ; #6020 is a jump, provide its return address
|
||||||
|
jp 0x6020 ; WC #6028 is position-dependent in some builds
|
||||||
|
ll4_after_cold:
|
||||||
|
ld c, a ; bit 7 = ROM, bits 0..6 = final pages
|
||||||
|
ld d, h ; keep cold-loader return across POP AF
|
||||||
|
ld e, l
|
||||||
|
pop af
|
||||||
|
jp po, ll4_irq_was_off
|
||||||
|
ei ; safe now: #6028 has restored page 0
|
||||||
|
ll4_irq_was_off:
|
||||||
|
ld a, c
|
||||||
|
and a, #0x7f
|
||||||
|
jr z, ll4_fail
|
||||||
|
ld a, c
|
||||||
|
and a, #0x80
|
||||||
|
ld (_vgm_opl4_preloaded_rom), a
|
||||||
|
ld a, c
|
||||||
|
and a, #0x7f
|
||||||
|
ld c, a ; final page count
|
||||||
|
; D/E already hold page/address high byte
|
||||||
|
|
||||||
|
ll4_tail_loop:
|
||||||
|
ld a, d
|
||||||
|
cp a, #64
|
||||||
|
jr nc, ll4_success
|
||||||
|
|
||||||
|
push bc
|
||||||
|
push de
|
||||||
|
ld a, d
|
||||||
|
call _wc_mngcvpl
|
||||||
|
pop de
|
||||||
|
pop bc
|
||||||
|
|
||||||
|
xor a ; blocks up to next 16K boundary
|
||||||
|
sub a, e
|
||||||
|
srl a
|
||||||
|
ld b, a
|
||||||
|
push bc
|
||||||
|
push de
|
||||||
|
push af ; one-byte stack argument for wc_load512
|
||||||
|
inc sp
|
||||||
|
ld h, e
|
||||||
|
ld l, #0
|
||||||
|
call _wc_load512
|
||||||
|
pop de
|
||||||
|
pop bc
|
||||||
|
cp a, #0x0f ; EOF: compacted stream is complete
|
||||||
|
jr z, ll4_success
|
||||||
|
or a
|
||||||
|
jr nz, ll4_fail
|
||||||
|
|
||||||
|
inc d
|
||||||
|
ld e, #0xc0
|
||||||
|
jr ll4_tail_loop
|
||||||
|
|
||||||
|
ll4_success:
|
||||||
|
push bc
|
||||||
|
xor a
|
||||||
|
call _wc_mngcvpl
|
||||||
|
pop bc
|
||||||
|
ld a, c
|
||||||
|
ret
|
||||||
|
|
||||||
|
ll4_fail:
|
||||||
|
xor a
|
||||||
|
call _wc_mngcvpl ; leave Win3 in a known state on reject
|
||||||
|
xor a
|
||||||
|
ret
|
||||||
|
__endasm;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t load_vgm(void)
|
uint8_t load_vgm(void)
|
||||||
{
|
{
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
uint8_t large_file = 0;
|
||||||
uint32_t fsize = wc_file_size;
|
uint32_t fsize = wc_file_size;
|
||||||
|
|
||||||
/* Минимальный размер — хотя бы 256 байт */
|
/* Минимальный размер — хотя бы 256 байт */
|
||||||
@@ -556,6 +714,10 @@ uint8_t load_vgm(void)
|
|||||||
s_pages = (uint8_t)((fsize + 16383UL) >> 14);
|
s_pages = (uint8_t)((fsize + 16383UL) >> 14);
|
||||||
if (!s_pages)
|
if (!s_pages)
|
||||||
s_pages = 1;
|
s_pages = 1;
|
||||||
|
if (s_pages > 64) {
|
||||||
|
s_pages = 64;
|
||||||
|
large_file = 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* vgm_end_page / vgm_end_addr устанавливаются в vgm_parse_header()
|
/* vgm_end_page / vgm_end_addr устанавливаются в vgm_parse_header()
|
||||||
из поля EOF offset заголовка VGM — здесь задавать не нужно. */
|
из поля EOF offset заголовка VGM — здесь задавать не нужно. */
|
||||||
@@ -642,6 +804,12 @@ uint8_t load_vgm(void)
|
|||||||
dbg_trace2(0xA5, s_pages, 0);
|
dbg_trace2(0xA5, s_pages, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (large_file) {
|
||||||
|
s_pages = load_large_opl4();
|
||||||
|
if (!s_pages)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return s_pages;
|
return s_pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,16 +973,31 @@ static void instant_abort(void)
|
|||||||
/* ── 3. Обнулить wait_ctr (отменить CMD_WAIT) ─────────── */
|
/* ── 3. Обнулить wait_ctr (отменить CMD_WAIT) ─────────── */
|
||||||
isr_wait_ctr = 0;
|
isr_wait_ctr = 0;
|
||||||
|
|
||||||
/* ── 4. Заполнить НЕАКТИВНЫЙ буфер shutdown-цепочкой ───── *
|
/* ── 4. Заполнить ОБА буфера shutdown-цепочкой ────────── *
|
||||||
* vgm_hl_pos уже установлен на vgm_hl_abort_pos → *
|
* vgm_hl_pos уже установлен на vgm_hl_abort_pos. *
|
||||||
* vgm_fill_buffer выдаст silence + CMD_ISR_DONE. */
|
* Цепочка (SILENCE_OPL ≈ 384 байта + WAVE_OFF + WAVE_TL *
|
||||||
|
* + CMD_ISR_DONE) НЕ помещается в один буфер: лимит на *
|
||||||
|
* заполнение — CMD_BUF_SIZE-48 = 464 байта, а cmdblock *
|
||||||
|
* нельзя продолжить с середины — при переполнении он *
|
||||||
|
* начинается заново со следующего вызова. Поэтому *
|
||||||
|
* заполняем сразу оба буфера, пока ISR заморожен: иначе *
|
||||||
|
* после первого CMD_END_BUF он уйдёт во второй буфер со *
|
||||||
|
* СТАРЫМ содержимым и никогда не дойдёт до CMD_ISR_DONE *
|
||||||
|
* (трек зависает на месте вместо перехода к следующему). */
|
||||||
free_idx = (isr_active_buf & 1) ^ 1;
|
free_idx = (isr_active_buf & 1) ^ 1;
|
||||||
vgm_fill_buffer(free_idx);
|
vgm_fill_buffer(free_idx); /* часть 1: SILENCE_OPL + ... */
|
||||||
|
vgm_fill_buffer(free_idx ^ 1); /* часть 2: ... + CMD_ISR_DONE */
|
||||||
|
|
||||||
/* ── 5. Переключить ISR на заполненный буфер ─────────── */
|
/* ── 5. Переключить ISR на первый из них ─────────────── */
|
||||||
isr_active_buf = free_idx;
|
isr_active_buf = free_idx;
|
||||||
isr_read_ptr = (uint16_t)(free_idx ? cmd_buf_b : cmd_buf_a);
|
isr_read_ptr = (uint16_t)(free_idx ? cmd_buf_b : cmd_buf_a);
|
||||||
|
|
||||||
|
/* Синхронизировать учёт update_buffer(): оба буфера только что
|
||||||
|
* заполнены здесь, напрямую, минуя его собственный механизм. */
|
||||||
|
s_last_active_buf = free_idx;
|
||||||
|
s_buf_ready[0] = 1;
|
||||||
|
s_buf_ready[1] = 1;
|
||||||
|
|
||||||
/* ── 6. Разморозить → ISR обработает shutdown ───────── */
|
/* ── 6. Разморозить → ISR обработает shutdown ───────── */
|
||||||
isr_enabled = 1;
|
isr_enabled = 1;
|
||||||
}
|
}
|
||||||
@@ -824,7 +1007,14 @@ void update_buffer(void)
|
|||||||
uint8_t active;
|
uint8_t active;
|
||||||
uint8_t free_idx;
|
uint8_t free_idx;
|
||||||
|
|
||||||
if (!s_playback_inited || !isr_enabled || isr_done)
|
/* isr_final_pending: ISR замер на CMD_FINALIZE, ждёт instant_abort()
|
||||||
|
* из главного цикла. Продолжать обычное упреждающее заполнение буфера
|
||||||
|
* здесь нельзя: vgm_hl_pos уже не совпадает с тем, что предполагает
|
||||||
|
* instant_abort() при вызове vgm_fill_buffer() для shutdown-цепочки,
|
||||||
|
* и повторное/параллельное заполнение может затереть буфер до того,
|
||||||
|
* как ISR доберётся до настоящего CMD_ISR_DONE — трек тогда никогда
|
||||||
|
* не завершается (зависает на последних тактах без перехода дальше). */
|
||||||
|
if (!s_playback_inited || !isr_enabled || isr_done || isr_final_pending)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
active = (uint8_t)(isr_active_buf & 1U);
|
active = (uint8_t)(isr_active_buf & 1U);
|
||||||
@@ -1012,12 +1202,14 @@ void main(void)
|
|||||||
s_vgz_compressed_pages = 0;
|
s_vgz_compressed_pages = 0;
|
||||||
draw_pre_load_info();
|
draw_pre_load_info();
|
||||||
|
|
||||||
/* Если файл .vgz — показать "Unpacking..." ДО начала загрузки. *
|
/* Показать "Loading..." ДО начала загрузки — для .vgz (распаковка)
|
||||||
* Это последний WC API вызов перед inflate — после него load_vgm *
|
* и для файлов >= 1 МБ (синхронная заливка сэмплов OPL4 в SRAM
|
||||||
* работает только через порты, без обращений к WC. */
|
* карты идёт байт за байтом с busy-wait и занимает секунды; без
|
||||||
if (is_vgz_filename()) {
|
* надписи выглядит как зависание). Для .vgz это последний WC API
|
||||||
|
* вызов перед inflate — дальше load_vgm работает только портами. */
|
||||||
|
if (is_vgz_filename() || ((const uint8_t *)&wc_file_size)[2] >= 0x10) {
|
||||||
buf_clear(work_buf);
|
buf_clear(work_buf);
|
||||||
buf_append_str(work_buf, " Unpacking...");
|
buf_append_str(work_buf, " Loading...");
|
||||||
print_line(&s_wnd, ROW_VGM_START, work_buf, WC_COLOR(WC_YELLOW, WC_BLACK));
|
print_line(&s_wnd, ROW_VGM_START, work_buf, WC_COLOR(WC_YELLOW, WC_BLACK));
|
||||||
|
|
||||||
/* Настройка progress bar: получить адрес текст. экрана для VGM секции col 2 */
|
/* Настройка progress bar: получить адрес текст. экрана для VGM секции col 2 */
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const [targetPath, overlayPath, offsetText] = process.argv.slice(2);
|
||||||
|
if (!targetPath || !overlayPath || !offsetText) process.exit(2);
|
||||||
|
|
||||||
|
const target = fs.readFileSync(targetPath);
|
||||||
|
const overlay = fs.readFileSync(overlayPath);
|
||||||
|
const offset = Number(offsetText);
|
||||||
|
if (!Number.isInteger(offset) || offset < 0 || offset + overlay.length > target.length)
|
||||||
|
process.exit(1);
|
||||||
|
|
||||||
|
overlay.copy(target, offset);
|
||||||
|
fs.writeFileSync(targetPath, target);
|
||||||
|
console.log(` overlay: ${overlay.length} bytes at +0x${offset.toString(16)}`);
|
||||||
Reference in New Issue
Block a user