790 lines
21 KiB
NASM
790 lines
21 KiB
NASM
; 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
|
|
|
|
; ── Блок параметров прогресс-бара ───────────────────────────────────
|
|
; Основная память плагина (Win2) на время работы загрузчика отключена —
|
|
; здесь стоит страница 5, — поэтому ifl_pb_* прочитать неоткуда. main.c
|
|
; кладёт нужное сюда ДО вызова, замапив страницу 5 в Win3 (адреса ниже
|
|
; фиксированы намеренно: #9802..#9806, их знает load_large_opl4()).
|
|
; Точка входа остаётся #9800, поэтому первым стоит перепрыг блока.
|
|
JR opl4_large_entry ; #9800
|
|
pb_text_pg: DB 0 ; #9802 физ. страница текстового экрана
|
|
pb_ptr: DW 0 ; #9803 текущая ячейка атрибутов (окно-отн.)
|
|
pb_green: DB 0 ; #9805 цвет закрашенной части
|
|
pb_step: DB 0 ; #9806 прибавка к аккумулятору за страницу
|
|
pb_left: DB 0 ; #9807 сколько ячеек ещё можно закрасить
|
|
pb_acc: DB 0 ; #9808 аккумулятор Брезенхэма (шкала — 64)
|
|
|
|
; 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
|
|
; Дальше копировать нечего, как только приёмник добрал до
|
|
; result_pages — это весь сжатый поток вместе с GD3. Без этой
|
|
; проверки цикл всегда шёл до 64-й страницы и для небольшого
|
|
; файла перемалывал сотни килобайт мусора за концом данных.
|
|
; (Для большого файла не срабатывает: там исходные страницы
|
|
; кончатся раньше, хвост дочитывается уже после возврата.)
|
|
push hl
|
|
ld hl,result_pages
|
|
cp (hl)
|
|
pop hl
|
|
jr nc,.copy_done
|
|
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
|
|
push de
|
|
call pb_tick ; фаза сжатия: продолжаем ту же шкалу
|
|
pop de
|
|
ld a,(copy_src_page)
|
|
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
|
|
jp pb_tick ; фаза заливки: одна страница -> шаг бара
|
|
|
|
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
|
|
|
|
; ── Прогресс-бар ────────────────────────────────────────────────────
|
|
; Обе фазы загрузчика идут по страницам мегабуфера монотонно 0 -> 64:
|
|
; заливка двигает src_page от 0 до конца блоков, копирование продолжает
|
|
; с того же места до 64. Поэтому шкала фиксированная (64), и хватает
|
|
; одного счётчика Брезенхэма без умножения и деления.
|
|
; Вызывается из next_src_page и из цикла копирования; регистры не портит.
|
|
pb_tick:
|
|
push af
|
|
push bc
|
|
push hl
|
|
ld a,(pb_step)
|
|
ld hl,pb_acc
|
|
add a,(hl)
|
|
ld (hl),a
|
|
.spill: cp 64
|
|
jr c,.done
|
|
sub 64
|
|
ld (pb_acc),a
|
|
ld a,(pb_left)
|
|
or a
|
|
jr z,.done ; бар уже закрашен до конца
|
|
call pb_paint
|
|
ld a,(pb_acc)
|
|
jr .spill
|
|
.done: pop hl
|
|
pop bc
|
|
pop af
|
|
ret
|
|
|
|
; Закрасить одну ячейку. Текстовый экран временно подставляем в Win0.
|
|
; PORT_W0 — write-only на реальном TSConf (и не обязан возвращать ранее
|
|
; записанное значение в эмуляторе), поэтому восстанавливать страницу через
|
|
; IN нельзя. Номер приёмника нам и так известен: #20 + dst_page. Во время
|
|
; заливки dst_page=0 и Win0 не используется, а во время копирования это
|
|
; всегда текущая страница назначения.
|
|
pb_paint:
|
|
ld bc,PORT_W0
|
|
ld a,(pb_text_pg)
|
|
out (c),a
|
|
ld hl,(pb_ptr)
|
|
ld a,(pb_green)
|
|
ld (hl),a ; Win0 начинается с #0000 — смещение и есть адрес
|
|
inc hl
|
|
ld (pb_ptr),hl
|
|
ld hl,pb_left
|
|
dec (hl)
|
|
ld a,(dst_page)
|
|
add a,#20
|
|
ld bc,PORT_W0
|
|
out (c),a
|
|
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
|