; 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