refactor build; use rle

This commit is contained in:
Eugene Lozovoy
2023-01-21 17:32:47 +03:00
parent e421dc14d8
commit 21619f1a92
10 changed files with 310 additions and 84 deletions

View File

@ -2,26 +2,21 @@ ifneq ($(wildcard .git),)
VERSION := $(shell git describe --abbrev=6 --long --dirty --always --tags --first-parent | sed s/-/./)
endif
export PATH:=/cygdrive/c/Hwdev/sjasmplus/:/cygdrive/e/Emulation/ZX Spectrum/Utils/fuse-utils/:/cygdrive/e/Emulation/ZX Spectrum/Emuls/Es.Pectrum/:${PATH}
export PATH:=/cygdrive/c/Hwdev/sjasmplus/:/cygdrive/e/Emulation/ZX Spectrum/Emuls/Es.Pectrum/:${PATH}
SJOPTS = --fullpath --inc=resources/ -DVERSION=\"${VERSION}\"
SJOPTS = --nologo --fullpath --outprefix=build/ -DVERSION=\"${VERSION}\"
.PHONY: all clean .FORCE
.FORCE:
.PHONY: all clean run
all: build/main.sna
all:
@mkdir -p build
sjasmplus --msg=war --lst=build/main.lst --exp=build/main.exp --sld=build/main.sld ${SJOPTS} src/main.asm
sjasmplus --msg=err --lst=build/build.lst ${SJOPTS} src/build.asm
clean:
rm -rf build/ .tmp/
build/main.sna: src/main.asm .FORCE
mkdir -p build
sjasmplus --sld=build/main.sld --lst=build/main.lst --outprefix=build/ ${SJOPTS} $<
%.tzx: %.sna
snap2tzx -o $@ $<
run: build/main.tzx
EsPectrum $<
run:
EsPectrum build/main.tap
-include Makefile.local

42
lua/incbin_pages.lua Normal file
View File

@ -0,0 +1,42 @@
--[[
Lua function providing "incbin" replacement which can arrange file into multiple pages
for SjASMPlus (https://github.com/z00m128/sjasmplus)
Author: Eugene Lozovoy
Parameters:
1 file_name: name of file to open
2 offset: positive value (optional)
3 length: positive value (optional)
4 baseaddr: destination address
5 pages: which pages will be used
6 pagesize: default 16Kb
]]
function incbin_pages(file_name, offset, length, baseaddr, pages, pagesize)
pagesize = pagesize or 16*1024
local f = io.open(file_name, "rb")
if not f then
sj.error("[incbin_pages]: cannot open file", file_name)
return
end
filelength = f:seek("end")
f:close()
offset = offset or 0
length = length or (filelength - offset)
if (offset > filelength) or (length > filelength) or (offset+length > filelength) then
sj.error("[incbin_rle]: file is too small", file_name)
return
end
filepages = math.ceil(filelength / pagesize)
if filepages > #pages then
sj.error("[incbin_pages]: cannot fit file", file_name)
return
end
local offsetend = offset + length
for i = 1, filepages do
local portion = math.min(pagesize, offsetend - offset)
_pc(string.format("org 0x%x" ,baseaddr))
_pc(string.format("page %u", pages[i]))
_pc(string.format("incbin \"%s\",0x%x,0x%x", file_name, offset, portion))
offset = offset + portion
end
end

134
lua/incbin_rle.lua Normal file
View File

@ -0,0 +1,134 @@
--[[
Lua function providing "incbin" replacement with RLE compression
for SjASMPlus (https://github.com/z00m128/sjasmplus)
Author: Eugene Lozovoy
Original idea and Z80 code: cngsoft (https://www.cpcwiki.eu/forum/programming/realtime-rle-decoding-and-encoding/)
Z80 code:
; ENCODER: HL=^SOURCE,DE=^TARGET,IX=FULL_LENGTH; HL+=FULL_LENGTH,DE+=PAKD_LENGTH,IX=0,B=0,ACF!
rle2pack_init ld b,0
rle2pack_loop ld c,(hl)
rle2pack_find ld a,xh
or xl
jr z,rle2pack_exit
dec ix
inc hl
inc b
jr z,rle2pack_over
ld a,(hl)
cp c
jr z,rle2pack_find
rle2pack_over call rle2pack_fill
jr rle2pack_loop
rle2pack_exit cp b
call nz,rle2pack_fill
; generate the end marker from the last byte!
dec hl
ld a,(hl)
inc hl
cpl
jr rle2pack_exit_
rle2pack_fill dec b
ld a,c
jr z,rle2pack_fill_
rle2pack_exit_ ld (de),a
inc de
ld (de),a
inc de
dec b
ld a,b
rle2pack_fill_ ld (de),a
inc de
ld b,0
ret
; DECODER: HL=^SOURCE,DE=^TARGET; HL+=PAKD_LENGTH,DE+=FULL_LENGTH,B!,AF!
rle2upak_init ld b,1
ld a,(hl)
inc hl
cp (hl)
jr nz,rle2upak_fill
inc hl
ld b,(hl)
inc hl
inc b
ret z
inc b
rle2upak_fill ld (de),a
inc de
djnz $-2
jr rle2upak_init
The encoder is 49 bytes long and does almost 32 kB/s on average; the decoder fits in 19 bytes and runs twice as fast.
Both support zero-length blocks thanks to the end marker, that also means that the decoder doesn't need to know the
stream's length (packed or not) in advance.
By turning all INC HL, DEC HL and INC DE into DEC HL, INC HL and DEC DE streams will be encoded and decoded in reverse.
The format itself is as follows: single bytes are encoded as themselves, double bytes as themselves plus a $00,
and strings of three or more identical bytes (up to 256) become the first two bytes plus the length minus 2;
the end-of-stream marker is a couple of identical bytes plus a $FF, and avoids clashing with previous single bytes
(i.e. XX, XX XX $FF won't be misread as XX XX XX, $FF).
Best-case compression (all memory is made of strings of 256 identical bytes) is 3*length/256+3;
worst-case compression (all memory is made of different couples of identical bytes, i.e. XX XX YY YY XX XX YY YY...) is 3*length/2+3.
Parameters:
1 file_name: name of file to open
2 offset: positive value (optional)
3 length: positive value (optional)
]]
function incbin_rle(file_name, offset, length)
local f = io.open(file_name, "rb")
if not f then
sj.error("[incbin_rle]: cannot open file", file_name)
return
end
offset = offset or 0
filelength = f:seek("end")
length = (length or filelength-offset)
if (offset > filelength) or (length > filelength) or (offset+length > filelength) then
sj.error("[incbin_rle]: file is too small", file_name)
return
end
_pl(";; incbin_rle ;; file \"" .. file_name .. "\", offset \"" .. offset .. "\", length " .. length)
f:seek("set", offset)
local compressed = 0
local prevbyte = nil
local repeat_len = 0
for i = 1, length do
local char = f:read(1)
local byte = string.byte(char)
if byte == prevbyte and repeat_len == 0 then
sj.add_byte(byte)
compressed = compressed + 1
repeat_len = 1
elseif byte == prevbyte and repeat_len < 255 then
repeat_len = repeat_len + 1
elseif repeat_len > 0 then
sj.add_byte(repeat_len-1)
sj.add_byte(byte)
compressed = compressed + 2
repeat_len = 0
else
sj.add_byte(byte)
compressed = compressed + 1
end
prevbyte = byte
end
if repeat_len > 0 then
sj.add_byte(repeat_len-1)
compressed = compressed + 1
end
sj.add_byte(255)
sj.add_byte(255)
sj.add_byte(255)
compressed = compressed + 3
f:close()
_pl(";; incbin_rle ;; end of file \"" .. file_name .. "\"")
-- if _c("__PASS__") == 3 then
-- io.write(string.format("include data (rle): name=%s (%u bytes) Offset=%u Len=%u CompressedLen=%u\n",
-- file_name, filelength, offset, length, compressed))
-- end
end

View File

@ -3,5 +3,6 @@
@timeout 5
@PATH=C:\Hwdev\sjasmplus\;%PATH%
sjasmplus --outprefix=build/ --inc=resources/ src/main.asm
sjasmplus --outprefix=build/ --exp=build/main.exp src/main.asm
sjasmplus --outprefix=build/ src/build.asm
@pause

116
src/build.asm Normal file
View File

@ -0,0 +1,116 @@
ASSERT __SJASMPLUS__ >= 0x011401 ; SjASMPlus 1.20.1
DEVICE ZXSPECTRUM128
OPT --syntax=abf
include "build/main.exp"
includelua "lua/incbin_pages.lua"
includelua "lua/incbin_rle.lua"
; === SNA file ===
lua allpass
incbin_pages("res/play.scr", 0, nil, 0x4000, {0})
incbin_pages("res/files.scr", 0, nil, 0xC000, {7})
incbin_pages("build/main.bin", 0, nil, _c("begin"), {0})
incbin_pages("res/test0.mid", 0, nil, 0xC000, {0,4,6,3})
endlua
page 0 : savesna "main.sna", main
; === TAP file ===
emptytap "main.tap"
page 0 : savetap "main.tap", main
; === TRD file ===
org #5d3b
boot_b:
dw #0100, .end-$-4, #30fd,#000e,#b300,#005f,#f93a,#30c0,#000e,#5300,#005d,#ea3a
.enter:
di ;
ld hl, #8000 ;
ld b, screen_sectors ;
call .sub_load ;
ld hl, #8000 ;
ld de, #4000 ; screen1
call .sub_unpack ;
ld a, #17 ; screen2
ld bc, #7ffd ;
out (c), a ;
ld de, #c000 ;
call .sub_unpack ;
ld a, #10 ; code
ld bc, #7ffd ;
out (c), a ;
ld hl, #c000 ;
ld b, code_sectors ;
call .sub_load ;
ld hl, #c000 ;
ld de, begin ;
call .sub_unpack ;
ld hl, #c000 ;
ld b, testmid_sectors ;
call .sub_load ;
jp main ;
; IN - HL - destination address
; IN - B - sectors count
.sub_load:
ld de, (#5cf4) ;
ld c, #05 ;
jp #3d13 ;
; IN - DE - destination
; IN - HL - source
; OUT - DE - pointer to next untouched byte at dest
; OUT - HL - pointer to next byte after unpacked block
.sub_unpack:
ld b, 1 ;
ld a, (hl) ;
inc hl ;
cp (hl) ;
jr nz, .fill ;
inc hl ;
ld b, (hl) ;
inc hl ;
inc b ;
ret z ;
inc b ;
.fill:
ld (de), a ;
inc de ;
djnz .fill ;
jp .sub_unpack ;
db #0d
.end:
page 0
emptytrd "main.trd", "ZXMIDI"
savetrd "main.trd", "boot.B", boot_b, boot_b.end-boot_b
org 0
lua allpass
incbin_rle("res/play.scr")
incbin_rle("res/files.scr")
sj.insert_label("screen_sectors", math.ceil(sj.current_address/256))
endlua
savetrd "main.trd", &"boot.B", 0, $
org 0
lua allpass
incbin_rle("build/main.bin")
sj.insert_label("code_sectors", math.ceil(sj.current_address/256))
endlua
savetrd "main.trd", &"boot.B", 0, $
org 0
incbin "res/test1.mid"
lua allpass
sj.insert_label("testmid_sectors", math.ceil(sj.current_address/256))
endlua
savetrd "main.trd", &"boot.B", 0, $

View File

@ -1,6 +1,6 @@
ASSERT __SJASMPLUS__ >= 0x011401 ; SjASMPlus 1.20.1
DEVICE ZXSPECTRUM128,stack_top
OPT --syntax=F
OPT --syntax=abf
SLDOPT COMMENT WPMEM, LOGPOINT, ASSERTION
page 0
@ -52,7 +52,7 @@ main:
ld ix, string_title
call print_string0
ld ix, testmid
ld ix, #c000
call smf_parse
jr nz, loop
call player_loop
@ -84,8 +84,7 @@ builddate:
db __DATE__, " ", __TIME__, 0
db "Code end",0
end:
display "Program start: ",main
display "Program end: ",$
display "Code entrypoint=", main, " start=", begin, " end=",end, " len=", /d, end-begin
assert $ < stack_bottom
org #BF00
@ -94,68 +93,7 @@ stack_bottom:
stack_top:
; === SNA file ===
org #4000 : incbin "play.scr"
org #C000,7 : incbin "files.scr"
org #C000,0
testmid:
; incbin "test0.mid",0 ; <= 16 Kb
; incbin "test0.mid",0,#4000 : org #C000,4 : incbin "test0.mid",#4000 ; <= 32 Kb
; incbin "test0.mid",0,#4000 : org #C000,4 : incbin "test0.mid",#4000,#4000 : org #C000,6 : incbin "test0.mid",#8000 ; <= 48 Kb
incbin "test0.mid",0,#4000 : org #C000,4 : incbin "test0.mid",#4000,#4000 : org #C000,6 : incbin "test0.mid",#8000,#4000 : org #C000,3 : incbin "test0.mid",#C000 ; <= 64 Kb
page 0 : savesna "main.sna", main
; === TRD file ===
org #5d3b
boot_b:
dw #0100, .end-$-4, #30fd,#000e,#b300,#005f,#f93a,#30c0,#000e,#5300,#005d,#ea3a
.enter:
di
ld hl, #4000 ;
ld b, 6912/256 ;
call .sub_load ;
ld a, #17 ;
ld bc, #7ffd ;
out (c), a ;
ld hl, #c000 ;
ld b, 6912/256 ;
call .sub_load ;
ld hl, begin ;
ld b, (end-begin)/256+1 ;
call .sub_load ;
ld a, #10 ;
ld bc, #7ffd ;
out (c), a ;
ld hl, #c000 ;
ld b, test1_mid_len/256+1 ; TODO correct len
call .sub_load ;
jp main ;
; IN - HL - destination address
; IN - B - sectors count
.sub_load:
ld de, (#5cf4) ;
ld c, #05 ;
jp #3d13 ;
db #0d
.end:
emptytrd "main.trd", "ZXMIDI"
page 0 : savetrd "main.trd", "boot.B", boot_b, boot_b.end-boot_b
page 0 : savetrd "main.trd", &"boot.B", #4000, 6912
page 7 : savetrd "main.trd", &"boot.B", #C000, 6912
page 0 : savetrd "main.trd", &"boot.B", begin, end-begin
org 0 : incbin "test1.mid"
test1_mid_len = $
savetrd "main.trd", &"boot.B", 0, test1_mid_len
export begin
export end
export main
savebin "main.bin", begin, end-begin