0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

昔、スーパーファミコンで作った、圧縮解凍ルーチン

Last updated at Posted at 2025-08-17

今さら参考にならないと思いますが、読み物としてお読みください

生まれてはじめて「まともに」書いた65816のコードはこれだけです
(抜粋)

LZEXT.ASM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; LZExt	 LZExt Extracter for 65816/SNES
;
; Input	 : a.X LZ address
;	     Y Extract Address
; Output : a.X Extracted Data Start Address
; Broken : A,X,Y,dbr,Bank 7E Ram
; 備考	 : バンクまたぎ対応
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

RamBank		= $7e
RamAdr		= RamBank*$10000
LZStart		= $7200
;sndadr = $f
;calctmp = $1f
LZTemp		= sndadr+3
LZBit		= sndadr+4
LZValue		= sndadr+5
LZSave_X	= sndadr+6
LZGBflag	= sndadr+8
LZsave_Y	= sndadr+9
LZMVN		= sndadr+13
LZExtSave	= sndadr+11
LZRomAdr	= sndadr
LZRomBank	= LZRomAdr+2
LZRamAdr	= calctmp
LZRamBank	= LZRamAdr+2
LZExtData	= RamAdr+LZStart

LZExt
	php
	phb
	sep	#$20
	phx			; src store
	ldx.w	#0		; direct register clear
	phx
	pld
	stx	LZRomAdr	; address=0
	sta	LZRomBank	; bank=rombank
	stx	LZRamAdr	; address=0
	lda.b	#LZExtData/65536
	sta	LZMVN+1
	sta	LZMVN+2
	sta	LZRamBank	; bank=rambank
	pha
	plb
;	ldx	rom address
;	ldy.w	#LZExtData.mod.65536	; ram address
	sty	LZExtSave
	plx

; Self Program Code

	lda	#$54		; mvn
	sta	LZMVN+0
;	lda	LZRamBank
;	sta	LZMVN+1
;	lda	LZRomBank
;	sta	LZMVN+2
	lda	#$6b		; rtl
	sta	LZMVN+3

;	sep	#$20		; 8 bit mode for A register
	jsr	xyx
	lda	[LZRomAdr],y
	cmp	#$4c
	bne	nocomp
	iny
	lda	[LZRomAdr],y
	dey
	cmp	#$5a
	bne	nocomp
	jsr	xyx
	inx
	inx
	inx
	inx
	inx
	inx
	jsr	LZEmain

	ldx.w	LZExtSave
	lda.b	#LZExtData/65536
	plb
	plp			; Pop Status Flag
	rts			; Return

nocomp
	jsr	xyx
	lda	LZRomBank
	plb
	plp
	rts

xyx
	phx
	tyx
	ply
	rts

; rombank.X : LZData
; rambank.Y : ExtData

LZEMain
	jsr	xyx

	sep	#$20		; 8 bit mode for A register

	lda.b	#1		; exx
				; ld	hl,lzdata
	sta	LZBit		; ld	b,1
				; exx
	jsr	getLZbit	; call	getLZbit
				; kokomade ok
emain
	jsr	getLZbit	; call	getLZbit
	bcc	comp		; jr	nc,comp
				; exx
	lda	[LZRomAdr],y	; ld	a,(hl)
	iny			; inc	hl
	bne	emain_		; バンクまたぎ
	inc	LZRomBank
	ldy	#$8000
emain_
	jsr	xyx
	sta	[LZRamAdr],y	; ld	(de),a
	iny			; inc	de
	jsr	xyx
	bra	emain		; jr	emain

comp
	jsr	getLZbit	; call	getLZbit
	bcs	comp1		; jr	c,comp1

	lda.b	#0		; xor	a
	jsr	getLZbit	; call	getLZbit
	rol	a		; rla
	jsr	getLZbit	; call	getLZbit
	rol	a		; rla

	sta	LZTemp		; ld	c,a
				; exx
	lda	[LZRomAdr],y	; ld	a,(hl)
	iny			; inc	hl
	bne	comp_		; バンクまたぎ
	inc	LZRomBank
	ldy	#$8000
comp_
				; exx
	pha			; ld	l,a
	lda.b	#255		; ld	h,-1
	pha
	lda	LZTemp		; ld	a,c
	bra	comp3

comp1
				; exx
	lda	[LZRomAdr],y	; ld	a,(hl)
	iny			; inc	hl
	bne	comp1_		; バンクまたぎ
	inc	LZRomBank
	ldy	#$8000
comp1_
				; exx
	pha			; ld	l,a
				; exx
	lda	[LZRomAdr],y	; ld	a,(hl)
	iny			; inc	hl
	bne	comp12_		; バンクまたぎ
	inc	LZRomBank
	ldy	#$8000
comp12_
				; exx
	pha			; ld	h,a
	clc
	cmp	#$20		; cp	20h
	bcc	comp2		; jr	c,comp2
	lsr	a		; rlca
	lsr	a		; rlca
	lsr	a		; rlca
	lsr	a
	lsr	a
	and.b	#7		; and	7
	bra	comp3		; jr	comp3

comp2				; exx
	lda	[LZRomAdr],y	; ld	a,(hl)
	php			; inc	x
	iny
	bne	comp2_		; バンクまたぎ
	inc	LZRomBank
	ldy	#$8000
comp2_
				; exx
	plp			; or	a
	beq	comp4		; jr	z,comp4

comp3
	sty	LZsave_X
	txy
	plx
	pha			; ld	c,a
	lda.b	#0		; ld	b,0
	pha
	rep	#$20		; 16bit mode
	txa			; X -> A
	xba
	ora.w	#$e000		; or	0e0h
	sty	LZsave_Y
	clc
	adc	LZsave_Y	; add	hl,de
	tax			; A -> X
	pla
	xba
	inc	a		; inc	bc
;	inc	a		; inc	bc

;	phb
	jsl	LZMVN		; X=src,Y=LZExtData,A=Len
;	plb

	sep	#$20		; 8bit mode

	tyx
	ldy	LZsave_X
	jmp	emain		; jr	emain

comp4	pla			; trash
	pla			; trash
	rts

getLZbit
	pha			; exx ?
	rol	LZGBflag	; rlc	c
	php

	dec	LZBit		; djnz	getLZbit0
	bne	getLZb0

	lda	#$08		; ld	b,8
	sta	LZBit
	lda	[LZRomAdr],y	; ld	c,(hl)
	sta	LZGBflag
	iny
	bne	getLZb0		; バンクまたぎ
	inc	LZRomBank
	ldy	#$8000
getLZb0
	plp			; for Carry Flag
	pla			; exx ?
	rts

実はこれ

これのパクりです

Cのソースコードも公開されており、圧縮ルーチンはCでほぼそのまま(ただし実行自動解凍部分は除く)、アセンブラは、Cであるのを一度Z80アセンブラに移植し、その後 65816 に移植しました

でも、無知であるため、あんまり最適化されていないのです

でも、これだけで24Mbit分のデータを4MbitのROMに収めることができ
まるで誇大広告なように誇張していました

また、そんなにストレスを感じさせない解凍速度を実現していました

起動直後に圧縮済で約49kbytesのサウンドバンクを展開し、それをSPC700に転送するのですが、そんなにストレスを感じない、当たり前の速度を実現しています

このソースコードをChatGPT(GPT5-Thinking)に最適化させてみたら?

ちなみに動作確認は行っていません

LZEXT-OPT.ASM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; LZExt  – LZExt Extracter for 65816/SNES (Optimized)
; Input  : A = ROM Bank, X = LZ data offset, Y = Extract dst offset
; Output : A = RAM Bank (LZExtData bank), X = Extracted start low
; Clobber: A,X,Y, DBR, P, some DP vars
; Note   : Uses long-indirect [dp],index → bank crossing handled by HW
;          MVN self-mod JSL for fast block copy (RAM→RAM)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

RamBank       = $7e
RamAdr        = RamBank * $10000
LZStart       = $7200

; ---- DP layout (置き場所は既存のsndadr/calctmp流用でOK) ----
; LZRomPtr: 24-bit (lo/hi/bk)   ; 圧縮元
; LZRamPtr: 24-bit (lo/hi/bk)   ; 展開先
; 以下ワーク
        .set    LZRomPtr,  sndadr        ; +0,+1,+2
        .set    LZRamPtr,  calctmp       ; +0,+1,+2
        .set    LZBit,     sndadr+3
        .set    LZVal,     sndadr+4
        .set    LZTmp,     sndadr+5      ; 汎用
        .set    LZSrcLo,   sndadr+6      ; MVN用 一時ソース
        .set    LZDstLo,   sndadr+8      ; MVN用 一時デスト
        .set    LZLenA,    sndadr+10     ; MVN用 A退避

; ---- 固定の展開先(従来互換)----
LZExtData     = RamAdr + LZStart

; ---- MVN trampoline(自己書換)----
LZMVN:
        .byte   $54        ; MVN  (srcBank,dstBank) ← 後で両方RamBankに書く
        .byte   RamBank
        .byte   RamBank
        .byte   $6B        ; RTL

; ===========================================================================

LZExt:
        php
        phb

        ; DPを安全ゾーンに(既存コード準拠)
        sep     #$20       ; A 8bit
        phx
        ldx     #$0000
        phx
        pld                 ; D=0

        ; 入力をDPポインタへ設定
        ; ROM ptr = (A:Bank, X:ofs), RAM ptr = (7E:LZExtData)
        stx     LZRomPtr
        sta     LZRomPtr+2

        ldy     #LZExtData & $FFFF
        sty     LZRamPtr
        lda     #LZExtData>>16
        sta     LZRamPtr+2

        ; DBRは触らない([dp],index は24bit解決するため不要)

        ; ヘッダ "LZ" 確認(長間接で一発)
        ldy     #0
        lda     [LZRomPtr],Y
        cmp     #$4C
        bne     .NotComp
        iny
        lda     [LZRomPtr],Y
        cmp     #$5A
        bne     .NotComp
        iny                 ; ヘッダ2B消費

        ; エンジンへ
        jsr     LZEmain

        ; 戻り値セット
        ldx     #LZExtData & $FFFF
        lda     #LZExtData>>16
        plb
        plp
        rts

.NotComp:
        ; ヘッダ不一致 → 何もしない
        lda     LZRomPtr+2  ; ROM bank を戻すだけ
        plb
        plp
        rts

; ===========================================================================

; getLZbit:  Carry = 次ビット(1/0)
; LZBit: 残りビット数(1..8), LZVal: 現在のビットバッファ
getLZbit:
        dec     LZBit
        bne     .shift
        lda     #8
        sta     LZBit
        lda     [LZRomPtr],Y
        sta     LZVal
        iny
.shift:
        lda     LZVal
        asl     a           ; MSB→Carry
        sta     LZVal
        rts

; ===========================================================================

; LZEmain – 本体(Z80版のフローを保持、XYスワップ排除)
LZEmain:
        ; 初期ビット供給
        lda     #1
        sta     LZBit
        jsr     getLZbit

.Main:
        jsr     getLZbit
        bcc     .Comp                  ; 0=マッチ, 1=リテラル

        ; ---- Literal: *dst++ = *src++ ----
        lda     [LZRomPtr],Y
        ; RAM書き込み: [LZRamPtr],X
        sta     [LZRamPtr],X
        inx
        iny
        bra     .Main

.Comp:
        jsr     getLZbit
        bcs     .Comp1

        ; a = (getbit<<1)+getbit
        lda     #0
        jsr     getLZbit
        rol     a
        jsr     getLZbit
        rol     a
        sta     LZTmp                  ; a→LZTmp (length種別)

        ; l = *src++
        lda     [LZRomPtr],Y
        iny
        pha                             ; push l
        lda     #$FF
        pha                             ; push h=-1
        lda     LZTmp                   ; a(種別)
        bra     .Comp3_prep

.Comp1:
        ; l = *src++; h = *src++
        lda     [LZRomPtr],Y
        iny
        pha                             ; l
        lda     [LZRomPtr],Y
        iny
        pha                             ; h
        cmp     #$20
        bcc     .Comp2

        ; a = h>>5 (&7)
        lsr     a
        lsr     a
        lsr     a
        and     #7
        bra     .Comp3_prep

.Comp2:
        ; a = *src++; if (a==0) end
        lda     [LZRomPtr],Y
        iny
        beq     .Done

.Comp3_prep:
        ; ---- ここで A=コード, スタック=[h][l] ----
        ; 長さ = A + 2 (元コード互換:inc 1回のみ)
        clc
        adc     #2
        dec     a             ; MVNのAは「(len-1)」なので-1
        sta     LZLenA

        ; 後方参照のソース= (dst + ((h|0xE0)<<8) + l)
        ; 1) 現在dstオフセットXを16bitで取り出し
        phx
        rep     #$20          ; A=16
        txa

        ; 2) l,h を取り出してオフセットに加算
        sep     #$20          ; 8bitで扱う
        pla                   ; h
        ora     #$E0
        xba                   ; A:00→AH=h|E0
        pla                   ; l
        rep     #$20
        xba                   ; AH:AL = h|E0 : l
        clc
        adc     0,S           ; 0,S には元の X 値(16bit)が乗ってる
        tax                   ; X = src(下位16bit)
        sep     #$20
        plx                   ; X を戻す…のではなく、今のXはdst!なので退避不要

        ; 3) MVN 呼び出し準備
        ;    X = srcLow16, Y = dstLow16, A = (len-1)
        ;    src/dst Bank は 7E に固定(自己書換済み)
        ;    Y = 現在の展開先
        txy                   ; preserve dst → Y
        rep     #$20
        tya
        xba
        xba                   ; no-op(確実化)
        ; 16bitへ戻しておく
        sep     #$20

        lda     LZLenA
        jsl     LZMVN         ; RAM→RAM 高速コピー

        ; MVN 後は Y と X が len 分進む(仕様)
        ; 我々の「通常運転」では X=dst を使うので、Y→Xへ合わせる
        tyx
        bra     .Main

.Done:
        rts

参考 同じもののZ80版

LZEXT.MAC
;	-----------------
;
;	  Subroutine 圧縮データの展開(実はLZ*OMのパ○リ(^^;;)
;	     Inputs  HL=圧縮データアドレス DE=展開先アドレス
;	     Outputs HL=展開したバイト数
;
;	  unsigned int lzext(sd, dd)
;	  unsigned int sd, dd;
;

LZEXT:
	PUSH	DE
	PUSH	HL
	EXX
	POP	HL
	LD	B,1
	EXX
	CALL	GETBIT

EMAIN:	CALL	GETBIT
	JR	NC,COMP

	EXX
	LD	A,(HL)
	INC	HL
	EXX
	LD	(DE),A
	INC	DE
	JR	EMAIN

;	-----------------
COMP:	CALL	GETBIT
	JR	C,COMP1

	XOR	A
	CALL	GETBIT
	RLA
	CALL	GETBIT
	RLA

	LD	C,A
	EXX
	LD	A,(HL)
	INC	HL
	EXX
	LD	L,A
	LD	H,-1
	LD	A,C
	JR	COMP3

;	-----------------
COMP1:	EXX
	LD	A,(HL)
	INC	HL
	EXX
	LD	L,A
	EXX
	LD	A,(HL)
	INC	HL
	EXX
	LD	H,A
	CP	20H
	JR	C,COMP2

	RLCA
	RLCA
	RLCA
	AND	7
	JR	COMP3

;	-----------------
COMP2:	EXX
	LD	A,(HL)
	INC	HL
	EXX
	OR	A
	JR	Z,COMP4

COMP3:	LD	C,A
	LD	B,0
	LD	A,H
	OR	0E0H
	LD	H,A
	ADD	HL,DE
	INC	BC
	INC	BC
	LDIR
	JR	EMAIN

;	-----------------
COMP4:	POP	HL
	SBC	HL,DE
	RET

;	-----------------
GETBIT:	EXX
	RLC	C
	DJNZ	GETBI0

	LD	B,8
	LD	C,(HL)
	INC	HL
GETBI0:	EXX
	RET

さいごに

65816をガチで組むこと自体、珍しいことなのかもしれません

更に、任天堂非公認ソフトということもあるので、もっと珍しくなります

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?