Apple2000/src/Compress.s

581 lines
15 KiB
ArmAsm

SECTION APPLEII,CODE
EVEN
** NOTE ** Disk image is extraced from "Src_Disk_Buffer" (either buffer1 or buffer2)!!!
**************************************
*
* CompressDisk:
* This function does an entire Decoding & Compression of a disk image,
* using the "Dalton Disk Disintegrator 2.0" algorithm. (see DDD, $1b41-$1b86, $1bc6...)
*
* Inputs: Floppy Image in disk_Buffer memory
* A0 -> Destination Buffer (at least 145,000 bytes to be safe)
*
* Output: Compressed Disk Image (DDD) in Destination Buffer w/ simple ProDos header
* D0 = FileSize; Total length of compressedData (including header) (0 if failed)
*
CompressDisk:
movem.l a2-a6/d2-d5,-(sp)
move.l a0,a5 ;A5-> Base of Dest Buffer!
moveq.l #0,d5 ;D5 = Dest buffer bit offset!
lea ReverseByteTbl,a6 ;a6-> Reverse byte lookup tbl
move.l #$00000000,(a5) ;Make blank 4 byte Dos3.3 header
add.l #32,d5
move.l #0,.TrackNum
.loop move.l .TrackNum,d0 ;trk num <----- main loop
lea .TrackData,a0 ;dest buffer
jsr ReadTrack
tst d0
beq .Error
lea .TrackData,a0 ;track data
lea .FreqTable,a1 ;dest for Frequency Count Tbl
jsr FrequencyCount
tst.l .TrackNum
bne .Not0
.Trk0 moveq.b #0,d0 ;val Encode DDD header (Three 0 bits)
moveq.w #3,d1 ;# of bits
bsr .WriteNBits
moveq.l #0,d0
move.b SecVolumeNumB,d0 ;Encode Volume # of disk
move.b (a6,d0.w),d0 ;ReverseByte & add 8 bits at once...
bfins d0,(a5){d5:8}
addq.l #8,d5
.Not0
lea .FreqTable,a0 ;freq count table
lea .Top20List,a1 ;top 20 values list destination
bsr Top20Search
lea .Top20List,a2
move.w #19,d2 ;Encode the 20 top values...
moveq.l #0,d0
.Encd20 move.b (a2)+,d0
move.b (a6,d0.w),d0 ;ReverseByte & add 8 bits at once...
bfins d0,(a5){d5:8}
addq.l #8,d5
dbf d2,.Encd20
;-------------------
.CompressTrackData ;The real compression algorithm, using top 20 values
lea .TrackData,a2 ;A2 -> TrackData (4096 bytes)
moveq.w #0,d2 ;D2 = main index into track data
.CompLp move.b (a2,d2.w),d0 ;read 1st val
move.w d2,d3 ;d3 = local index. How many times does D0 repeat?
.Count cmp.b (a2,d3.w),d0 ;1st check against self to maintain bounds check & get >=1
bne.b 5$
addq.w #1,d3
cmp.w #4095,d3 ;still in range?
bls.b .Count
5$ sub.w d2,d3 ;d3 = # of times byte D0 is there (at least 1)!
cmp.w #4,d3
blo.b .Single
.Multiple ;------- value D0 repeats D3 times... (D3 >= #4)
cmp.w #$100,d3
bls.b 10$
move.w #$100,d3 ;limit of $100 repeat values (written as #$00)
10$ move.b d0,d1 ;save value temporarily...
move.w #$97,d0 ;Encode code for "Repeat"
move.b (a6,d0.w),d0 ;ReverseByte & add 8 bits at once...
bfins d0,(a5){d5:8}
addq.l #8,d5
move.b d1,d0 ;Encode data value
move.b (a6,d0.w),d0 ;ReverseByte & add 8 bits at once...
bfins d0,(a5){d5:8}
addq.l #8,d5
move.b d3,d0 ;Encode # of times repeats
move.b (a6,d0.w),d0 ;ReverseByte & add 8 bits at once...
bfins d0,(a5){d5:8}
addq.l #8,d5
add.w d3,d2 ;update main index into track data...
bra 100$
.Single ;------- single value D0...
move.w #19,d3 ;d3 = index to search Top20List with (19 -> 0)
lea .Top20List,a0 ;a0 -> Top20List, UBYTES[20]
20$ cmp.b (a0,d3.w),d0
beq.b .SingleTop20 ;is D0 a top 20 value?
dbf d3,20$
;Not top20, so encode this byte by itself...
move.b d0,-(sp)
move.b #0,d0
moveq.b #1,d1 ;Encode "SingleByte" code
bsr .WriteNBits
moveq.l #0,d0
move.b (sp)+,d0
move.b (a6,d0.w),d0 ;ReverseByte & add 8 bits at once...
bfins d0,(a5){d5:8}
addq.l #8,d5
bra 99$
.SingleTop20 ;-- single value is a Top 20 value! UBYTE[d3.w]
lea .Top20Codes,a0
move.b (a0,d3.w),d0
lea .Top20Lengths,a0
move.b (a0,d3.w),d1
bsr .WriteNBits
; bra 99$ (fall through!)
99$ addq.w #1,d2
100$ cmp.w #4095,d2
bls .CompLp ;Do all 4096 bytes of track data...
;---------------------
addq.l #1,.TrackNum
cmp.l #34,.TrackNum ;Do all 35 tracks!
bls .loop
;Disk image compressed, now report len in bytes
;D5 = Length in bits...
addq.l #7,d5 ;we want any partial bytes...
divu.l #8,d5 ;d5 = Length in bytes!
move.l d5,d0 ;lenth of compression!
movem.l (sp)+,a2-a6/d2-d5
rts
.Error movem.l (sp)+,a2-a6/d2-d5
moveq.l #0,d0
rts
.WriteNBits: ;Enter: d0 = Data to write (low order bits), d1 = # of bits to write
; a5 -> Base of destination memory, d5 = Offset in BITS...
;Output: Memory written, updated Offset (d5)
; (see DDD $1d28 - $1d54)
subq.b #1,d1
and.w #$000f,d1
.lp bfins d0,(a5){d5:1} ;place low order bit in memory
lsr.l d0
addq.l #1,d5 ;1 at a time until done...
dbf d1,.lp
rts
CNOP 0,4
.Top20Codes dc.b $03,$09,$1f,$0f,$07,$1b,$0b,$0d,$15,$37 ;(see DDD $1d55-$1d7c)
dc.b $3d,$25,$05,$b1,$11,$21,$01,$57,$5d,$1d
.Top20Lengths dc.b $04,$04,$05,$05,$05,$05,$05,$05,$05,$06
dc.b $06,$06,$06,$06,$06,$06,$06,$07,$07,$07
.TrackNum ds.l 1
.TrackData ds.b 4096
.FreqTable ds.w 256
.Top20List ds.b 20
even
**************************************
*
* PlainDiskImage_Save:
* This function takes a disk image in ram, and saved it to disk in the "plain image"
* format, which is the normalilzed track/sector data (143,360 bytes) in order.
*
* Inputs: Floppy Image in disk_Buffer memory
* A0 -> Destination Buffer (at least 143,360 bytes to be safe)
*
* Output: Plain Image Data in Destination Buffer w/ no headers/trailor/checksums
* D0 = FileSize; Total length of data (should be 143,360) (0 if failed)
*
PlainDiskImage_Save:
movem.l a2-a5/d2-d5,-(sp)
move.l a0,a5 ;A5-> Base of Dest Buffer!
moveq.l #0,d5 ;D5 = Dest buffer bit offset!
move.l #0,.TrackNum
.loop move.l .TrackNum,d0 ;trk num <----- main loop
move.l d0,d1
mulu.w #4096,d1
lea (a5,d1.l),a0 ;A0 = dest buffer...
jsr ReadTrack ;fix dest buffer
tst d0
beq .Error
addq.l #1,.TrackNum
cmp.l #34,.TrackNum ;Do all 35 tracks!
bls .loop
;Disk image compressed, now report len in bytes
move.l #143360,d0 ;d0 = lenth of disk data
movem.l (sp)+,a2-a5/d2-d5
rts
.Error movem.l (sp)+,a2-a5/d2-d5
moveq.l #0,d0
rts
CNOP 0,4
.TrackNum ds.l 1
.TrackData ds.b 4096
.FreqTable ds.w 256
.Top20List ds.b 20
EVEN
*****************************************
*
* ReadTrack:
* This function will extract 1 track of normalized data from a disk image,
* using Apple Dos's normal decoding algorithms. (uses ReadSector)
*
* Inputs: Floppy Image in disk_Buffer memory (normal)
* D0 = Track number to extract from
* A0 -> Destination for data (4096 bytes!)
*
* Output: 4096 bytes of sector data in dest. buffer
* D0 = True (1) for Successful extraction
* False (0) for ERROR
*
ReadTrack:
movem.l a2/d2-d3,-(sp)
cmp.w #34,d0
bhi.b .Error ;track out of range...
move.l d0,d2 ;d2 = Track #
moveq.l #0,d3 ;d3 = Sector #
move.l a0,a2 ;a2 -> Ptr to destination buffer
.loop move.l d2,d0 ;track num
move.l d3,d1 ;sector num
move.l a2,a0 ;dest buffer
jsr ReadSector
tst d0 ;did it work?
beq.b .Error
add.l #$100,a2 ;set dest to next page of mem
addq.l #1,d3 ;inc sector
cmp.b #15,d3
bls.b .loop ;read all 16 sectors!
moveq.l #1,d0
movem.l (sp)+,a2/d2-d3
rts
.Error moveq.l #0,d0
movem.l (sp)+,a2/d2-d3
rts
;----------------------------------------------------------------------------------------
*****************************************
*
* ReadSector:
* This function will extract 1 sector of normalized data from a disk image,
* using Apple Dos's normal decoding algorithms.
*
* Inputs: Floppy Image in disk_Buffer memory (normal)
* D0 = Track number (0 - 34)
* D1 = Sector number (0 - 15)
* A0 -> Destination for data (256 bytes)
*
* Output: 256 bytes of sector data in dest. buffer
* D0 = True (1) for Successful extraction
* False (0) for ERROR
*
ReadSector:
movem.l a2-a5/d2-d4,-(sp)
mulu.w #disk_TrackLen,d0
add.l Src_Disk_Buffer,d0
move.l d0,a2 ;a2 -> Start of raw track data
move.l a2,a3 ;a3 -> CURRENT ptr to raw track data (a2 <= a3 <= a4)
lea disk_TrackLen-1(a2),a4 ;a4 -> Last byte of raw track data
move.l d1,d4 ;d4 = Sector # we want...
move.l #disk_TrackLen+500,d3 ;d3 = Max "GetNext" searches allowed!
bra.b .addr
.NxtSec
lea 345(a3),a3
cmp.l a4,a3 ;make sure addr still in range
bls.b .addr
sub.l #disk_TrackLen,a3 ;if not, reset it
.addr jsr .GetNext
cmp.b #$D5,d0 ;Search for D5 AA 96 address header
bne .addr
jsr .GetNext
cmp.b #$AA,d0
bne .addr
jsr .GetNext
cmp.b #$96,d0
bne .addr
;***** Extract 4X4 address data *****
jsr .Get4X4 ;Volume Number
move.b d0,SecVolumeNumB
jsr .Get4X4 ;Track Number
jsr .Get4X4 ;HARD sector num...
cmp.b #$0f,d0
bhi.b .addr ;outside legal range? Keep searching...
move.b (.HardToSoft.l,d0.w),d0 ;d0 = SOFT sector num...
cmp.b d0,d4
bne .NxtSec ;doh! Not the right sector!
jsr .Get4X4 ;Checksum (ignore for now)
jsr .GetNext ;better get DE AA address epilog
cmp.b #$DE,d0
bne .addr
jsr .GetNext
cmp.b #$AA,d0
bne .addr
.data jsr .GetNext
cmp.b #$D5,d0 ;Wait for D5 AA AD data header
bne .data
jsr .GetNext
cmp.b #$AA,d0
bne .data
jsr .GetNext
cmp.b #$AD,d0
bne .data
moveq.w #$55,d1 ;d1 = cntr...
lea .NibbleHunk56+$56,a1 ;a1 -> destination
moveq.l #0,d4 ;d4 == ACC == running EOR checksum
lea .DecodeTable-$96,a5 ;a5 -> DecodeTable (-$96)
moveq.l #0,d0 ;See $b8ff -> $b911 in DOS 3.3
.loop jsr .GetNext ;LDY $c0ec
move.b (a5,d0.w),d0 ;eor $ba00,y
eor.b d0,d4 ; ...
move.b d4,-(a1) ;sta $bc00,y (y decrements)
dbf d1,.loop
move.w #$ff,d1 ;d1 = cntr
lea .NibbleHunk100,a1 ;a1 -> dest
.loop2 jsr .GetNext ;See $b915 -> $b923
move.b (a5,d0.w),d0 ;eor $ba00,y
eor.b d0,d4 ;
move.b d4,(a1)+ ;sta $bb00,y (y inc's)
dbf d1,.loop2
.cksum jsr .GetNext ;See $b925 (checksum)
cmp.b (a5,d0.w),d4 ;cmp $ba00,y
bne .Error
jsr .GetNext ;better get DE AA data epilog
cmp.b #$DE,d0
bne .Error
jsr .GetNext
cmp.b #$AA,d0
bne .Error
* DeNibble-ize: (see dos 3.3 code at $b800)
* Takes 342 bytes of "6 & 2" disk encoded crap as returns 256 bytes of normal data.
*
* Nib1: Nib2: Dest:
* $ff-+- o $ff-+- o
* | ^ $55-+- vvo | ^
* | ^ | vvv | ^
* | ^ | vvv | ^
* $00-+- o $00-+- ovv $00-+- o
*
* Inputs: a0 = destination for 256 bytes of data
* NibbleHunk100 & 56 = Contain nibble data
;at this point, all nibble data is read...!
;Now, DE-NIBBLIZE !!! (dos 3.3 $b8c2 - $b8db)
; d1 = YReg, d2 = XReg, d3 = ACC
lea .NibbleHunk100,a2 ;a2-> NibbleHunk1[$00] ($100)
lea .NibbleHunk56,a3 ;a3-> NibbleHunk2[$00] ($56)
move.l a0,a4 ;a4-> Dest buffer (256 bytes)
move.w #$00ff,d1 ;d1 = Byte Countdown
.DeNibl
.b8c4 move.w #$56,d2 ;LDX #$56
.b8c6 subq.b #1,d2 ;DEX
bmi.b .b8c4 ;BMI $b8c4
move.b (a2)+,d3 ;LDA $bb00,Y
move.b (a3,d2.w),d0
lsr.b d0 ;LSR $bc00,X
roxl.b d3 ;ROL
lsr.b d0 ;LSR $bc00,X
roxl.b d3 ;ROL
move.b d0,(a3,d2.w)
move.b d3,(a4)+ ;store another byte of normal data!
dbf d1,.b8c6
movem.l (sp)+,a2-a5/d2-d4
move.l #1,d0 ;success...
rts
.GetNext: ;a2 -> Start track data, a3 -> Current track data
;a4 -> Max track data ptr, d3=remaining tries
; Return: d0 = byte
move.b (a3)+,d0
cmp.l a4,a3 ;make sure addr still in range
bls.b .ok
move.l a2,a3 ;if not, reset it
.ok subq.l #1,d3
bmi .GNErr
rts
.GNErr move.l (sp)+,d0 ;Exceeded # of tries, POP STACK, and fall to error...
.Error moveq.l #0,d0 ;Failed (d0 = False)....
movem.l (sp)+,a2-a5/d2-d4
rts
.Get4X4
;a2 -> Start track data, a3 -> Current track data
;a4 -> Max track data ptr
;Return: d0 = byte
moveq.l #0,d0
move.b (a3)+,d0
cmp.l a4,a3 ;make sure addr still in range
bls.b .ok3
move.l a2,a3 ;if not, reset it
.ok3 lsl.b d0
or.b #$01,d0
and.b (a3)+,d0
cmp.l a4,a3 ;make sure addr still in range
bls.b .ok4
move.l a2,a3 ;if not, reset it
.ok4 rts
.HardToSoft dc.b $0,$7,$e,$6,$d,$5,$c,$4,$b,$3,$a,$2,$9,$1,$8,$f
;The lookup table used as $ba00,y ALWAYS had Y >= $96,
;so here is the pertinant portion of the table ($ba96-$baff)
;Make sure to subtract $96 when getting the address of it.
.DecodeTable:
dc.b $00,$01,$98,$99,$02,$03,$9C,$04,$05,$06,$A0,$A1,$A2,$A3,$A4
dc.b $A5,$07,$08,$A8,$A9,$AA,$09,$0A,$0B,$0C,$0D,$B0,$B1,$0E,$0F
dc.b $10,$11,$12,$13,$B8,$14,$15,$16,$17,$18,$19,$1A,$C0,$C1,$C2
dc.b $C3,$C4,$C5,$C6,$C7,$C8,$C9,$CA,$1B,$CC,$1C,$1D,$1E,$D0,$D1
dc.b $D2,$1F,$D4,$D5,$20,$21,$D8,$22,$23,$24,$25,$26,$27,$28,$E0
dc.b $E1,$E2,$E3,$E4,$29,$2A,$2B,$E8,$2C,$2D,$2E,$2F,$30,$31,$32
dc.b $F0,$F1,$33,$34,$35,$36,$37,$38,$F8,$39,$3A,$3B,$3C,$3D,$3E,$3F
.NibbleHunk100 ds.b $100 ;342 bytes of storage space...
.NibbleHunk56 ds.b $56
.NibblePad dc.b $ff,$ff,$ff,$ff
SecVolumeNumB ds.b 1
;---------------------------------------------------------------------------------------
EVEN
*****************************************
*
* FrequencyCount: (see DDD $1bc6 - $1c2b)
* This function will scan through 4096 bytes of data, counting occurances
* of each byte value and will create a list of UWORDS[256].
* As in DDD, any Repeating value of 4 or more will be skipped.
*
* Inputs: A0 -> 4096 bytes of data to analyze
* A1 -> Destination for UWORDS[256] result (512 bytes!)
*
* Output: list of UWORDS[256] frequency count in dest. buffer
*
FrequencyCount: ;---- Produce list of most popular bytes...
movem.l a2-a5/d2-d5,-(sp)
move.l a0,a2 ;a2 -> 4096 bytes of data
move.l a1,a3 ;a3 -> Destination table of UWORDS[256]
move.w #255,d0
.ClrLp move.w #0,(a1,d0.w*2) ;clear dest table
dbf d0,.ClrLp
move.w #0,d2 ;d2 = main index
moveq.l #0,d0
.MnLoop move.b (a2,d2.w),d0 ;read 1st val
move.w d2,d3 ;d3 = local index. How many times does D0 repeat?
.repeat cmp.b (a2,d3.w),d0
bne.b 5$
addq.w #1,d3
cmp.w #4095,d3
bls.b .repeat
5$ sub.w d2,d3 ;d3 = # of times byte D0 is there (at least 1)!
cmp.w #4,d3
bhs.b .NoCnt
.Cnt addq.w #1,(a3,d0.w*2)
move.w #1,d3 ;only inc 1 byte
.NoCnt add.w d3,d2
cmp.w #4095,d2
bls.b .MnLoop
movem.l (sp)+,a2-a5/d2-d5
rts
*****************************************
*
* Top20Search: (see DDD $1c2c - $1c75)
* This function will scan through the frequency list UWORDS[256], create a list
* of the top 20 values in UBYTE[20]. During each search, this function will examine
* each entry, and if >= current high number, replace the current high number with it.
*
* Inputs: A0 -> Source of frequency count UWORDS[256]
* A1 -> Destination for top 20 values UBYTES[20] (20 bytes!)
* Output: list of most popular values UBYTES[20] in dest. buffer
*
Top20Search: ;---- Produce list of most popular bytes...
movem.l a2-a5/d2-d5,-(sp)
move.w #0,d1 ;D1 = value index # (0 -> 19) we are looking for
.loop20 moveq.l #0,d0 ;D0 = max frequency number found so far...
move.w #0,d2 ;D2 = search index (0 - 255)
.search cmp.w (a0,d2.w*2),d0
bhi.b .nope
move.w (a0,d2.w*2),d0 ;new max frequency number
move.b d2,(a1,d1.w) ;temporarily (?) keep the number in the table...
.nope addq.b #1,d2 ;search/compare all 256 values...
bne.b .search
move.b (a1,d1.w),d2 ;get that popular value...
move.w #0,(a0,d2.w*2) ;and blank its entry out in the freq table...
addq.b #1,d1 ;get 20 values total...
cmp.b #19,d1
bls.b .loop20
movem.l (sp)+,a2-a5/d2-d5
rts