Merge branch 'bootrom-2bpp'

This commit is contained in:
Lior Halphon 2020-05-06 23:30:56 +03:00
commit 8f69703726
5 changed files with 415 additions and 619 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 479 B

View File

@ -6,14 +6,12 @@ Start:
ld sp, $fffe
; Clear memory VRAM
ld hl, $8000
call ClearMemoryPage
call ClearMemoryPage8000
ld a, 2
ld c, $70
ld [c], a
; Clear RAM Bank 2 (Like the original boot ROM
; Clear RAM Bank 2 (Like the original boot ROM)
ld h, $D0
xor a
call ClearMemoryPage
ld [c], a
@ -22,15 +20,6 @@ Start:
; Clear title checksum
ldh [TitleChecksum], a
; Clear OAM
ld h, $fe
ld c, $a0
.clearOAMLoop
ldi [hl], a
dec c
jr nz, .clearOAMLoop
; Init Audio
ld a, $80
ldh [$26], a
ldh [$11], a
@ -39,8 +28,23 @@ Start:
ldh [$25], a
ld a, $77
ldh [$24], a
ld hl, $FF30
; Init waveform
ld c, $10
.waveformLoop
ldi [hl], a
cpl
dec c
jr nz, .waveformLoop
call InitWaveform
; Clear OAM
ld h, $fe
ld c, $a0
.clearOAMLoop
ldi [hl], a
dec c
jr nz, .clearOAMLoop
; Init BG palette
ld a, $fc
@ -67,9 +71,7 @@ Start:
; Clear the second VRAM bank
ld a, 1
ldh [$4F], a
xor a
ld hl, $8000
call ClearMemoryPage
call ClearMemoryPage8000
call LoadTileset
ld b, 3
@ -87,20 +89,24 @@ ELSE
.tilemapRowLoop
call .write_with_palette
; Repeat the 3 tiles common between E and B. This saves 27 bytes after
; compression, with a cost of 17 bytes of code.
push af
; Switch to second VRAM Bank
ld a, 1
ldh [$4F], a
ld [hl], 8
; Switch to back first VRAM Bank
xor a
ldh [$4F], a
sub $20
sub $3
jr nc, .notspecial
add $20
call .write_with_palette
dec c
.notspecial
pop af
ldi [hl], a
add d
add d ; d = 3 for SameBoy logo, d = 1 for Nintendo logo
dec c
jr nz, .tilemapRowLoop
sub 47
sub 44
push de
ld de, $10
add hl, de
@ -116,6 +122,19 @@ ELSE
ld l, $a7
ld bc, $0107
jr .tilemapRowLoop
.write_with_palette
push af
; Switch to second VRAM Bank
ld a, 1
ldh [$4F], a
ld [hl], 8
; Switch to back first VRAM Bank
xor a
ldh [$4F], a
pop af
ldi [hl], a
ret
.endTilemap
ENDC
@ -125,38 +144,40 @@ ENDC
ld hl, BgPalettes
xor a
.expandPalettesLoop:
IF !DEF(FAST)
cpl
ENDC
; One white
ldi [hl], a
ldi [hl], a
ld [hli], a
ld [hli], a
IF DEF(FAST)
; 3 more whites
ldi [hl], a
ldi [hl], a
ldi [hl], a
ldi [hl], a
ldi [hl], a
ldi [hl], a
ELSE
; The actual color
; Mixed with white
ld a, [de]
inc de
ldi [hl], a
inc e
or $20
ld b, a
ld a, [de]
inc de
ldi [hl], a
dec e
or $84
rra
rr b
ld [hl], b
inc l
ld [hli], a
; One black
xor a
ld [hli], a
ld [hli], a
; One color
ld a, [de]
inc e
ld [hli], a
ld a, [de]
inc e
ld [hli], a
xor a
; Two blacks
ldi [hl], a
ldi [hl], a
ldi [hl], a
ldi [hl], a
ENDC
dec c
jr nz, .expandPalettesLoop
@ -170,8 +191,10 @@ ENDC
IF !DEF(FAST)
call DoIntroAnimation
ld a, 45
ldh [WaitLoopCounter], a
; Wait ~0.75 seconds
ld b, 45
ld b, a
call WaitBFrames
; Play first sound
@ -183,10 +206,6 @@ IF !DEF(FAST)
ld a, $c1
call PlaySound
; Wait ~0.5 seconds
ld a, 30
ldh [WaitLoopCounter], a
.waitLoop
call GetInputPaletteIndex
call WaitFrame
@ -198,6 +217,9 @@ ELSE
call PlaySound
ENDC
call Preboot
IF DEF(AGB)
ld b, 1
ENDC
; Will be filled with NOPs
@ -206,7 +228,6 @@ BootGame:
ldh [$50], a
SECTION "MoreStuff", ROM0[$200]
; Game Palettes Data
TitleChecksums:
db $00 ; Default
@ -510,29 +531,30 @@ Palettes:
dw $4778, $3290, $1D87, $0861 ; DMG LCD
KeyCombinationPalettes
db 1 ; Right
db 48 ; Left
db 5 ; Up
db 8 ; Down
db 0 ; Right + A
db 40 ; Left + A
db 43 ; Up + A
db 3 ; Down + A
db 6 ; Right + B
db 7 ; Left + B
db 28 ; Up + B
db 49 ; Down + B
db 1 * 3 ; Right
db 48 * 3 ; Left
db 5 * 3 ; Up
db 8 * 3 ; Down
db 0 * 3 ; Right + A
db 40 * 3 ; Left + A
db 43 * 3 ; Up + A
db 3 * 3 ; Down + A
db 6 * 3 ; Right + B
db 7 * 3 ; Left + B
db 28 * 3 ; Up + B
db 49 * 3 ; Down + B
; SameBoy "Exclusives"
db 51 ; Right + A + B
db 52 ; Left + A + B
db 53 ; Up + A + B
db 54 ; Down + A + B
db 51 * 3 ; Right + A + B
db 52 * 3 ; Left + A + B
db 53 * 3 ; Up + A + B
db 54 * 3 ; Down + A + B
TrademarkSymbol:
db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c
SameBoyLogo:
incbin "SameBoyLogo.pb8"
incbin "SameBoyLogo.pb12"
AnimationColors:
dw $7FFF ; White
@ -545,9 +567,6 @@ AnimationColors:
dw $7102 ; Blue
AnimationColorsEnd:
DMGPalettes:
dw $7FFF, $32BF, $00D0, $0000
; Helper Functions
DoubleBitsAndWriteRowTwice:
call .twice
@ -594,8 +613,11 @@ PlaySound:
ldh [$14], a
ret
ClearMemoryPage8000:
ld hl, $8000
; Clear from HL to HL | 0x2000
ClearMemoryPage:
xor a
ldi [hl], a
bit 5, h
jr z, ClearMemoryPage
@ -634,30 +656,28 @@ ReadCGBLogoHalfTile:
ld a, e
ret
; LoadTileset using PB8 codec, 2019 Damian Yerrick
;
; The logo is compressed using PB8, a form of RLE with unary-coded
; run lengths. Each block representing 8 bytes consists of a control
; byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat
; previous, followed by the literals in that block.
; LoadTileset using PB12 codec, 2020 Jakub Kądziołka
; (based on PB8 codec, 2019 Damian Yerrick)
SameBoyLogo_dst = $8080
SameBoyLogo_length = (128 * 24) / 64
LoadTileset:
ld hl, SameBoyLogo
ld de, SameBoyLogo_dst
ld de, SameBoyLogo_dst - 1
ld c, SameBoyLogo_length
.pb8BlockLoop:
; Register map for PB8 decompression
.refill
; Register map for PB12 decompression
; HL: source address in boot ROM
; DE: destination address in VRAM
; A: Current literal value
; B: Repeat bits, terminated by 1000...
; C: Number of 8-byte blocks left in this block
; Source address in HL lets the repeat bits go straight to B,
; bypassing A and avoiding spilling registers to the stack.
ld b, [hl]
dec b
jr z, .sameboyLogoEnd
inc b
inc hl
; Shift a 1 into lower bit of shift value. Once this bit
@ -665,26 +685,53 @@ LoadTileset:
scf
rl b
.pb8BitLoop:
.loop
; If not a repeat, load a literal byte
jr c,.pb8Repeat
ld a, [hli]
.pb8Repeat:
; Decompressed data uses colors 0 and 1, so write once, inc twice
ld [de], a
inc de
inc de
jr c, .simple_repeat
sla b
jr nz, .pb8BitLoop
dec c
jr nz, .pb8BlockLoop
; End PB8 decoding. The rest uses HL as the destination
ld h, d
ld l, e
jr c, .shifty_repeat
ld a, [hli]
jr .got_byte
.shifty_repeat
sla b
jr nz, .no_refill_during_shift
ld b, [hl] ; see above. Also, no, factoring it out into a callable
inc hl ; routine doesn't save bytes, even with conditional calls
scf
rl b
.no_refill_during_shift
ld c, a
jr nc, .shift_left
srl a
db $fe ; eat the add a with cp d8
.shift_left
add a
sla b
jr c, .go_and
or c
db $fe ; eat the and c with cp d8
.go_and
and c
jr .got_byte
.simple_repeat
sla b
jr c, .got_byte
; far repeat
dec de
ld a, [de]
inc de
.got_byte
inc de
ld [de], a
sla b
jr nz, .loop
jr .refill
; End PB12 decoding. The rest uses HL as the destination
.sameboyLogoEnd
ld h, d
ld l, $80
; Copy (unresized) ROM logo
ld de, $104
.CGBROMLogoLoop
@ -711,29 +758,6 @@ ReadTrademarkSymbol:
jr nz, .loadTrademarkSymbolLoop
ret
LoadObjPalettes:
ld c, $6A
jr LoadPalettes
LoadBGPalettes64:
ld d, 64
LoadBGPalettes:
ld e, 0
ld c, $68
LoadPalettes:
ld a, $80
or e
ld [c], a
inc c
.loop
ld a, [hli]
ld [c], a
dec d
jr nz, .loop
ret
DoIntroAnimation:
; Animate the intro
ld a, 1
@ -771,30 +795,75 @@ IF !DEF(FAST)
.fadeLoop
ld c, 32 ; 32 colors to fade
ld hl, BgPalettes
push hl
.frameLoop
push bc
call BrightenColor
; Brighten Color
ld a, [hli]
ld e, a
ld a, [hld]
ld d, a
; RGB(1,1,1)
ld bc, $421
; Is blue maxed?
ld a, e
and $1F
cp $1F
jr nz, .blueNotMaxed
dec c
.blueNotMaxed
; Is green maxed?
ld a, e
cp $E0
jr c, .greenNotMaxed
ld a, d
and $3
cp $3
jr nz, .greenNotMaxed
res 5, c
.greenNotMaxed
; Is red maxed?
ld a, d
and $7C
cp $7C
jr nz, .redNotMaxed
res 2, b
.redNotMaxed
; add de, bc
; ld [hli], de
ld a, e
add c
ld [hli], a
ld a, d
adc b
ld [hli], a
pop bc
dec c
jr nz, .frameLoop
call WaitFrame
call WaitFrame
ld hl, BgPalettes
pop hl
call LoadBGPalettes64
call WaitFrame
dec b
jr nz, .fadeLoop
ENDC
ld a, 1
call ClearVRAMViaHDMA
; Select the first bank
xor a
ldh [$4F], a
cpl
call _ClearVRAMViaHDMA
call ClearVRAMViaHDMA ; A = $40, so it's bank 0
ld a, $ff
ldh [$00], a
call ClearVRAMViaHDMA
; Final values for CGB mode
ld de, $ff56
ld d, a
ld e, c
ld l, $0d
ld a, [$143]
@ -818,7 +887,7 @@ IF DEF(AGB)
ld c, a
add a, $11
ld h, c
ld b, 1
; B is set to 1 after ret
ELSE
; Set registers to match the original CGB boot
; AF = $1180, C = 0
@ -836,6 +905,14 @@ ENDC
ld a, $1
ret
GetKeyComboPalette:
ld hl, KeyCombinationPalettes - 1 ; Return value is 1-based, 0 means nothing down
ld c ,a
ld b, 0
add hl, bc
ld a, [hl]
ret
EmulateDMG:
ld a, 1
ldh [$6C], a ; DMG Emulation
@ -847,11 +924,7 @@ EmulateDMG:
ldh a, [InputPalette]
and a
jr z, .nothingDown
ld hl, KeyCombinationPalettes - 1 ; Return value is 1-based, 0 means nothing down
ld c ,a
ld b, 0
add hl, bc
ld a, [hl]
call GetKeyComboPalette
jr .paletteFromKeys
.nothingDown
ld a, b
@ -860,8 +933,7 @@ EmulateDMG:
call LoadPalettesFromIndex
ld a, 4
; Set the final values for DMG mode
ld d, 0
ld e, $8
ld de, 8
ld l, $7c
ret
@ -934,16 +1006,15 @@ GetPaletteIndex:
xor a
ret
LoadPalettesFromIndex: ; a = index of combination
ld b, a
; Multiply by 3
add b
add b
GetPaletteCombo:
ld hl, PaletteCombinations
ld b, 0
ld c, a
add hl, bc
ret
LoadPalettesFromIndex: ; a = index of combination
call GetPaletteCombo
; Obj Palettes
ld e, 0
@ -955,7 +1026,8 @@ LoadPalettesFromIndex: ; a = index of combination
ld c, a
add hl, bc
ld d, 8
call LoadObjPalettes
ld c, $6A
call LoadPalettes
pop hl
bit 3, e
jr nz, .loadBGPalette
@ -969,76 +1041,39 @@ LoadPalettesFromIndex: ; a = index of combination
ld c, a
add hl, bc
ld d, 8
jp LoadBGPalettes
jr LoadBGPalettes
BrightenColor:
LoadBGPalettes64:
ld d, 64
LoadBGPalettes:
ld e, 0
ld c, $68
LoadPalettes:
ld a, $80
or e
ld [c], a
inc c
.loop
ld a, [hli]
ld e, a
ld a, [hld]
ld d, a
; RGB(1,1,1)
ld bc, $421
; Is blue maxed?
ld a, e
and $1F
cp $1F
jr nz, .blueNotMaxed
res 0, c
.blueNotMaxed
; Is green maxed?
ld a, e
and $E0
cp $E0
jr nz, .greenNotMaxed
ld a, d
and $3
cp $3
jr nz, .greenNotMaxed
res 5, c
.greenNotMaxed
; Is red maxed?
ld a, d
and $7C
cp $7C
jr nz, .redNotMaxed
res 2, b
.redNotMaxed
; Add de to bc
push hl
ld h, d
ld l, e
add hl, bc
ld d, h
ld e, l
pop hl
ld a, e
ld [hli], a
ld a, d
ld [hli], a
ld [c], a
dec d
jr nz, .loop
ret
ClearVRAMViaHDMA:
ld hl, $FF51
; Src
ld a, $88
ld [hli], a
xor a
ld [hli], a
; Dest
ld a, $98
ld [hli], a
ld a, $A0
ld [hli], a
; Do it
ld [hl], $12
ldh [$4F], a
ld hl, HDMAData
_ClearVRAMViaHDMA:
ld c, $51
ld b, 5
.loop
ld a, [hli]
ldh [c], a
inc c
dec b
jr nz, .loop
ret
GetInputPaletteIndex:
@ -1077,24 +1112,12 @@ GetInputPaletteIndex:
; Slide into change Animation Palette
ChangeAnimationPalette:
push af
push hl
push bc
push de
ld hl, KeyCombinationPalettes - 1 ; Input palettes are 1-based, 0 means nothing down
ld c ,a
ld b, 0
add hl, bc
ld a, [hl]
ld b, a
; Multiply by 3
add b
add b
ld hl, PaletteCombinations + 2; Background Palette
ld b, 0
ld c, a
add hl, bc
call GetKeyComboPalette
call GetPaletteCombo
inc l
inc l
ld a, [hl]
ld hl, Palettes + 1
ld b, 0
@ -1108,47 +1131,73 @@ ChangeAnimationPalette:
.isWhite
push af
ld a, [hli]
push hl
ld hl, BgPalettes ; First color, all palette
ld hl, BgPalettes ; First color, all palettes
call ReplaceColorInAllPalettes
ld l, LOW(BgPalettes + 2) ; Second color, all palettes
call ReplaceColorInAllPalettes
pop hl
ldh [BgPalettes + 2], a ; Second color, first palette
ldh [BgPalettes + 6], a ; Fourth color, first palette
ld a, [hli]
push hl
ld hl, BgPalettes + 1 ; First color, all palette
ld hl, BgPalettes + 1 ; First color, all palettes
call ReplaceColorInAllPalettes
ld l, LOW(BgPalettes + 3) ; Second color, all palettes
call ReplaceColorInAllPalettes
pop hl
ldh [BgPalettes + 3], a ; Second color, first palette
ldh [BgPalettes + 7], a ; Fourth color, first palette
pop af
jr z, .isNotWhite
inc hl
inc hl
.isNotWhite
; Mixing code by ISSOtm
ldh a, [BgPalettes + 7 * 8 + 2]
and ~$21
ld b, a
ld a, [hli]
ldh [BgPalettes + 7 * 8 + 2], a ; Second color, 7th palette
and ~$21
add a, b
ld b, a
ld a, [BgPalettes + 7 * 8 + 3]
res 2, a ; and ~$04, but not touching carry
ld c, [hl]
res 2, c ; and ~$04, but not touching carry
adc a, c
rra ; Carry sort of "extends" the accumulator, we're bringing that bit back home
ld [BgPalettes + 7 * 8 + 3], a
ld a, b
rra
ld [BgPalettes + 7 * 8 + 2], a
dec l
ld a, [hli]
ldh [BgPalettes + 7 * 8 + 3], a ; Second color, 7th palette
ldh [BgPalettes + 7 * 8 + 6], a ; Fourth color, 7th palette
ld a, [hli]
ldh [BgPalettes + 7 * 8 + 7], a ; Fourth color, 7th palette
ld a, [hli]
ldh [BgPalettes + 4], a ; Third color, first palette
ld a, [hl]
ld a, [hli]
ldh [BgPalettes + 5], a ; Third color, first palette
call WaitFrame
ld hl, BgPalettes
call LoadBGPalettes64
; Delay the wait loop while the user is selecting a palette
ld a, 30
ld a, 45
ldh [WaitLoopCounter], a
pop de
pop bc
pop hl
pop af
ret
ReplaceColorInAllPalettes:
ld de, 8
ld c, 8
ld c, e
.loop
ld [hl], a
add hl, de
@ -1159,7 +1208,7 @@ ReplaceColorInAllPalettes:
LoadDMGTilemap:
push af
call WaitFrame
ld a,$19 ; Trademark symbol
ld a, $19 ; Trademark symbol
ld [$9910], a ; ... put in the superscript position
ld hl,$992f ; Bottom right corner of the logo
ld c,$c ; Tiles in a logo row
@ -1169,27 +1218,20 @@ LoadDMGTilemap:
ldd [hl], a
dec c
jr nz, .tilemapLoop
ld l,$0f ; Jump to top row
ld l, $0f ; Jump to top row
jr .tilemapLoop
.tilemapDone
pop af
ret
InitWaveform:
ld hl, $FF30
; Init waveform
xor a
ld c, $10
.waveformLoop
ldi [hl], a
cpl
dec c
jr nz, .waveformLoop
ret
HDMAData:
db $88, $00, $98, $A0, $12
db $88, $00, $80, $00, $40
SECTION "ROMMax", ROM0[$900]
; Prevent us from overflowing
ds 1
BootEnd:
IF BootEnd > $900
FAIL "BootROM overflowed: {BootEnd}"
ENDC
SECTION "HRAM", HRAM[$FF80]
TitleChecksum:

95
BootROMs/pb12.c Normal file
View File

@ -0,0 +1,95 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
void opts(uint8_t byte, uint8_t *options)
{
*(options++) = byte | ((byte << 1) & 0xff);
*(options++) = byte & (byte << 1);
*(options++) = byte | ((byte >> 1) & 0xff);
*(options++) = byte & (byte >> 1);
}
int main()
{
static uint8_t source[0x4000];
size_t size = read(STDIN_FILENO, &source, sizeof(source));
unsigned pos = 0;
assert(size <= 0x4000);
while (size && source[size - 1] == 0) {
size--;
}
uint8_t *literals = NULL;
size_t literals_size = 0;
unsigned bits = 0;
unsigned control = 0;
unsigned prev[2] = {-1, -1}; // Unsigned to allow "not set" values
while (true) {
uint8_t byte = 0;
if (pos == size){
if (bits == 0) break;
}
else {
byte = source[pos++];
}
if (byte == prev[0] || byte == prev[1]) {
bits += 2;
control <<= 1;
control |= 1;
control <<= 1;
if (byte == prev[1]) {
control |= 1;
}
}
else {
bits += 2;
control <<= 2;
uint8_t options[4];
opts(prev[1], options);
bool found = false;
for (unsigned i = 0; i < 4; i++) {
if (options[i] == byte) {
// 01 = modify
control |= 1;
bits += 2;
control <<= 2;
control |= i;
found = true;
break;
}
}
if (!found) {
literals = realloc(literals, literals_size++);
literals[literals_size - 1] = byte;
}
}
prev[0] = prev[1];
prev[1] = byte;
if (bits >= 8) {
uint8_t outctl = control >> (bits - 8);
assert(outctl != 1);
write(STDOUT_FILENO, &outctl, 1);
write(STDOUT_FILENO, literals, literals_size);
bits -= 8;
control &= (1 << bits) - 1;
literals_size = 0;
}
}
uint8_t end_byte = 1;
write(STDOUT_FILENO, &end_byte, 1);
if (literals) {
free(literals);
}
return 0;
}

View File

@ -1,341 +0,0 @@
/*
PB8 compressor and decompressor
Copyright 2019 Damian Yerrick
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
// For setting stdin/stdout to binary mode
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
#include <unistd.h>
#define fd_isatty isatty
#elif defined (_WIN32)
#include <io.h>
#include <fcntl.h>
#define fd_isatty _isatty
#endif
/*
; The logo is compressed using PB8, a form of RLE with unary-coded
; run lengths. Each block representing 8 bytes consists of a control
; byte, where each bit (MSB to LSB) is 0 for literal or 1 for repeat
; previous, followed by the literals in that block.
SameBoyLogo_dst = $8080
SameBoyLogo_length = (128 * 24) / 64
LoadTileset:
ld hl, SameBoyLogo
ld de, SameBoyLogo_dst
ld c, SameBoyLogo_length
.pb8BlockLoop:
; Register map for PB8 decompression
; HL: source address in boot ROM
; DE: destination address in VRAM
; A: Current literal value
; B: Repeat bits, terminated by 1000...
; C: Number of 8-byte blocks left in this block
; Source address in HL lets the repeat bits go straight to B,
; bypassing A and avoiding spilling registers to the stack.
ld b, [hl]
inc hl
; Shift a 1 into lower bit of shift value. Once this bit
; reaches the carry, B becomes 0 and the byte is over
scf
rl b
.pb8BitLoop:
; If not a repeat, load a literal byte
jr c,.pb8Repeat
ld a, [hli]
.pb8Repeat:
; Decompressed data uses colors 0 and 1, so write once, inc twice
ld [de], a
inc de
inc de
sla b
jr nz, .pb8BitLoop
dec c
jr nz, .pb8BlockLoop
ret
*/
/* Compressor and decompressor *************************************/
/**
* Compresses an input stream to PB8 data on an output stream.
* @param infp input stream
* @param outfp output stream
* @param blocklength size of an independent input block in bytes
* @return 0 for reaching infp end of file, or EOF for error
*/
int pb8(FILE *infp, FILE *outfp, size_t blocklength)
{
blocklength >>= 3; // convert bytes to blocks
assert(blocklength > 0);
while (1) {
int last_byte = EOF; // value that never occurs in a file
for (size_t blkleft = blocklength; blkleft > 0; --blkleft) {
unsigned int control_byte = 0x0001;
unsigned char literals[8];
size_t nliterals = 0;
while (control_byte < 0x100) {
int c = fgetc(infp);
if (c == EOF) break;
control_byte <<= 1;
if (c == last_byte) {
control_byte |= 0x01;
}
else {
literals[nliterals++] = last_byte = c;
}
}
if (control_byte > 1) {
// Fill partial block with repeats
while (control_byte < 0x100) {
control_byte = (control_byte << 1) | 1;
}
// Write control byte and check for write failure
int ok = fputc(control_byte & 0xFF, outfp);
if (ok == EOF) return EOF;
size_t ok2 = fwrite(literals, 1, nliterals, outfp);
if (ok2 < nliterals) return EOF;
}
// If finished, return success or failure
if (ferror(infp) || ferror(outfp)) return EOF;
if (feof(infp)) return 0;
} // End 8-byte block
} // End packet, resetting last_byte
}
/**
* Decompresses PB8 data on an input stream to an output stream.
* @param infp input stream
* @param outfp output stream
* @return 0 for reaching infp end of file, or EOF for error
*/
int unpb8(FILE *infp, FILE *outfp)
{
int last_byte = 0;
while (1) {
int control_byte = fgetc(infp);
if (control_byte == EOF) {
return feof(infp) ? 0 : EOF;
}
control_byte &= 0xFF;
for (size_t bytesleft = 8; bytesleft > 0; --bytesleft) {
if (!(control_byte & 0x80)) {
last_byte = fgetc(infp);
if (last_byte == EOF) return EOF; // read error
}
control_byte <<= 1;
int ok = fputc(last_byte, outfp);
if (ok == EOF) return EOF;
}
}
}
/* CLI frontend ****************************************************/
static inline void set_fd_binary(unsigned int fd)
{
#ifdef _WIN32
_setmode(fd, _O_BINARY);
#else
(void) fd;
#endif
}
static const char *usage_msg =
"usage: pb8 [-d] [-l blocklength] [infile [outfile]]\n"
"Compresses a file using RLE with unary run and literal lengths.\n"
"\n"
"options:\n"
" -d decompress\n"
" -l blocklength allow RLE packets to span up to blocklength\n"
" input bytes (multiple of 8; default 8)\n"
" -h, -?, --help show this usage page\n"
" --version show copyright info\n"
"\n"
"If infile is - or missing, it is standard input.\n"
"If outfile is - or missing, it is standard output.\n"
"You cannot compress to or decompress from a terminal.\n"
;
static const char *version_msg =
"PB8 compressor (C version) v0.01\n"
"Copyright 2019 Damian Yerrick <https://pineight.com/contact/>\n"
"This software is provided 'as-is', without any express or implied\n"
"warranty.\n"
;
static const char *toomanyfilenames_msg =
"pb8: too many filenames; try pb8 --help\n";
int main(int argc, char **argv)
{
const char *infilename = NULL;
const char *outfilename = NULL;
bool decompress = false;
size_t blocklength = 8;
for (int i = 1; i < argc; ++i) {
if (argv[i][0] == '-' && argv[i][1] != 0) {
if (!strcmp(argv[i], "--help")) {
fputs(usage_msg, stdout);
return 0;
}
if (!strcmp(argv[i], "--version")) {
fputs(version_msg, stdout);
return 0;
}
// -t1 or -t 1
int argtype = argv[i][1];
switch (argtype) {
case 'h':
case '?':
fputs(usage_msg, stdout);
return 0;
case 'd':
decompress = true;
break;
case 'l': {
const char *argvalue = argv[i][2] ? argv[i] + 2 : argv[++i];
const char *endptr = NULL;
unsigned long tvalue = strtoul(argvalue, (char **)&endptr, 10);
if (endptr == argvalue || tvalue == 0 || tvalue > SIZE_MAX) {
fprintf(stderr, "pb8: block length %s not a positive integer\n",
argvalue);
return EXIT_FAILURE;
}
if (tvalue % 8 != 0) {
fprintf(stderr, "pb8: block length %s not a multiple of 8\n",
argvalue);
return EXIT_FAILURE;
}
blocklength = tvalue;
} break;
default:
fprintf(stderr, "pb8: unknown option -%c\n", argtype);
return EXIT_FAILURE;
}
}
else if (!infilename) {
infilename = argv[i];
}
else if (!outfilename) {
outfilename = argv[i];
}
else {
fputs(toomanyfilenames_msg, stderr);
return EXIT_FAILURE;
}
}
if (infilename && !strcmp(infilename, "-")) {
infilename = NULL;
}
if (!infilename && decompress && fd_isatty(0)) {
fputs("pb8: cannot decompress from terminal; try redirecting stdin\n",
stderr);
return EXIT_FAILURE;
}
if (outfilename && !strcmp(outfilename, "-")) {
outfilename = NULL;
}
if (!outfilename && !decompress && fd_isatty(1)) {
fputs("pb8: cannot compress to terminal; try redirecting stdout or pb8 --help\n",
stderr);
return EXIT_FAILURE;
}
FILE *infp = NULL;
if (infilename) {
infp = fopen(infilename, "rb");
if (!infp) {
fprintf(stderr, "pb8: error opening %s ", infilename);
perror("for reading");
return EXIT_FAILURE;
}
}
else {
infp = stdin;
set_fd_binary(0);
}
FILE *outfp = NULL;
if (outfilename) {
outfp = fopen(outfilename, "wb");
if (!outfp) {
fprintf(stderr, "pb8: error opening %s ", outfilename);
perror("for writing");
fclose(infp);
return EXIT_FAILURE;
}
}
else {
outfp = stdout;
set_fd_binary(1);
}
int compfailed = 0;
int has_ferror = 0;
if (decompress) {
compfailed = unpb8(infp, outfp);
}
else {
compfailed = pb8(infp, outfp, blocklength);
}
fflush(outfp);
if (ferror(infp)) {
fprintf(stderr, "pb8: error reading %s\n",
infilename ? infilename : "<stdin>");
has_ferror = EOF;
}
fclose(infp);
if (ferror(outfp)) {
fprintf(stderr, "pb8: error writing %s\n",
outfilename ? outfilename : "<stdout>");
has_ferror = EOF;
}
fclose(outfp);
if (compfailed && !has_ferror) {
fputs("pb8: unknown compression failure\n", stderr);
}
return (compfailed || has_ferror) ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@ -20,7 +20,7 @@ else
EXESUFFIX:=
endif
PB8_COMPRESS := build/pb8$(EXESUFFIX)
PB12_COMPRESS := build/pb12$(EXESUFFIX)
ifeq ($(PLATFORM),Darwin)
DEFAULT := cocoa
@ -382,21 +382,21 @@ $(BIN)/SDL/Shaders: Shaders
# Boot ROMs
$(OBJ)/%.1bpp: %.png
$(OBJ)/%.2bpp: %.png
-@$(MKDIR) -p $(dir $@)
rgbgfx -d 1 -h -o $@ $<
rgbgfx -h -u -o $@ $<
$(OBJ)/BootROMs/SameBoyLogo.pb8: $(OBJ)/BootROMs/SameBoyLogo.1bpp $(PB8_COMPRESS)
$(realpath $(PB8_COMPRESS)) -l 384 $< $@
$(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS)
$(PB12_COMPRESS) < $< > $@
$(PB8_COMPRESS): BootROMs/pb8.c
$(CC) $< -o $@
$(PB12_COMPRESS): BootROMs/pb12.c
$(CC) -Wall -Werror $< -o $@
$(BIN)/BootROMs/agb_boot.bin: BootROMs/cgb_boot.asm
$(BIN)/BootROMs/cgb_boot_fast.bin: BootROMs/cgb_boot.asm
$(BIN)/BootROMs/sgb2_boot: BootROMs/sgb_boot.asm
$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb8
$(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb12
-@$(MKDIR) -p $(dir $@)
rgbasm -i $(OBJ)/BootROMs/ -i BootROMs/ -o $@.tmp $<
rgblink -o $@.tmp2 $@.tmp