Skip to content

Commit 3937c14

Browse files
zlagoavivaceISSOtmpinobatch
authored
Add a guide about adding a SGB border (#70)
--------- Co-authored-by: Antonio Vivace <[email protected]> Co-authored-by: Eldred Habert <[email protected]> Co-authored-by: Damian Yerrick <[email protected]>
1 parent 0b6879f commit 3937c14

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

website/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ module.exports = {
8282
text: "The wonders of Prehistorik Man",
8383
},
8484
{ link: "/guides/dma_hijacking", text: "DMA Hijacking" },
85+
{ link: "/guides/sgb_border", text: "Adding a custom SGB border" },
8586
],
8687
},
8788
],

website/guides/sgb_border.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Adding a custom SGB border
2+
3+
This document aims to help developers of DMG-compatible homebrew with adding Super Game Boy borders.
4+
5+
We will see how to:
6+
7+
- Detect whether we are running on a SGB
8+
- Transfer the border's tiles
9+
- Transfer and display the border's tilemap and palettes
10+
11+
<small>
12+
13+
Written by [sylvie (zlago)](https://zlago.github.io/me/), idea (and minor help) by [valentina (coffee bat)](https://coffeebat.neocities.org/), reviews and improvements by [ISSOtm](https://eldred.fr) and [avivace](https://github.com/avivace).
14+
15+
</small>
16+
17+
## Enabling SGB features
18+
19+
Before we can do anything else, we must first specify in the header that this game is aware of SGB features.
20+
Otherwise, the SGB BIOS will ignore any packets we send, and we won't even be able to detect when the program is running on SGB.
21+
22+
To enable SGB features:
23+
24+
- The [SGB flag] must be set to `$03`;
25+
- The [old licensee code] must be set to `$33`.
26+
27+
[SGB flag]: https://gbdev.io/pandocs/The_Cartridge_Header#0146--sgb-flag
28+
[old licensee code]: https://gbdev.io/pandocs/The_Cartridge_Header#014b--old-licensee-code
29+
30+
This can be achieved by passing the `--sgb-compatible` and `--old-licensee 0x33` flags to [`rgbfix`](https://rgbds.gbdev.io/docs/rgbfix.1).
31+
32+
## Packets
33+
34+
The SGB BIOS can be "talked to" via *command packets*, sent bit by bit via the `P1`/`JOYP` register.
35+
36+
An SGB packet consists of:
37+
38+
- a "start" pulse (`P1`=`%xx00xxxx`)
39+
- 128 data pulses (`P1`=`%xx01xxxx` for "1", `P1`=`%xx10xxxx` for "0")
40+
- a "0" pulse (`P1`=`%xx10xxxx`)
41+
42+
You must set `P1` to `%xx11xxxx` between each pulse.
43+
44+
This adds up to 16 Bytes of data (LSB first), If a packet doesn't read all 16 bytes, the unused bytes are ignored.
45+
46+
**You should wait 4 frames between each packet.**
47+
48+
For an example of such routine, see this [code](https://github.com/zlago/violence-gbc/blob/11cfdb6ee8a35e042fa9712484d814e0961cea7c/src/sub.sm83#L413-L463) and the related Pan Docs entry: [SGB Command Packet on Pandocs](https://gbdev.io/pandocs/SGB_Command_Packet.html).
49+
50+
This guide glosses over a minor detail, as certain packets can be (albeit unccomon) more than 16 bytes.
51+
52+
## TRN
53+
54+
Bulk transfer (TRN) packets tell the SGB to copy the contents of the screen to buffers in Super NES work RAM. The `CHR_TRN` and `PCT_TRN` packets are used to send data for SGB borders.
55+
56+
For that to function properly, you must:
57+
58+
1. set `BGP` to `$e4` and `LCDC` to `$91` (screen enabled, BG uses tiles `$8000`-`$8fff` and tilemap `$9800`, WIN and OBJ disabled, BG enabled)
59+
2. set `SCX` and `SCY` to `$00`
60+
3. the tilemap consists of `$00`, `$01`..`$13`, 12 bytes padding (offscreen), `$14`..`$27`, padding, repeat until `$ff` (inclusive)
61+
4. the data you want to send must be loaded at `$8000`-`$8fff`
62+
63+
You can do 1, 2 and 3 via [this snippet](https://github.com/zlago/snek-gbc/blob/baef0369f57d2b0d58316cb1c28c6cc22475a6c9/code/init.sm83#L208-L230)
64+
65+
- **You must load the data into VRAM and enable the screen before sending the TRN packet**
66+
- **You must wait ~8 frames after each TRN** instead of just 4
67+
68+
## Detecting SGB
69+
70+
Here's how a SGB detection routine should look like:
71+
72+
- wait 12 or more frames after the console boots
73+
- send a `MLT_REQ`, selecting 2 or 4 players (`$89, $01` or `$89, $03`), and wait at least 1 frame
74+
- attempt to advance the read player (set `P1.5` to 0 then 1)
75+
- set `P1.4`-`P1.5` to `%11` (`%xx11xxxx`)
76+
- read `P1.0`-`P1.3`, and check if it either
77+
* has changed
78+
* isn't `%1111`
79+
- advance and reread a few times and branch somewhere if the test keeps failing
80+
81+
Let's go over an example snippet ([source](https://github.com/zlago/snek-gbc/blob/baef0369f57d2b0d58316cb1c28c6cc22475a6c9/code/init.sm83#L167-L196)):
82+
83+
```sm83asm
84+
; test for SGB
85+
xor a /* first it enables STAT interrupt, since snek-gbc happens to just have a `reti` as the handler */
86+
ldh [rLYC], a
87+
ld a, STATF_LYC
88+
ldh [rSTAT], a
89+
ld a, IEF_STAT
90+
ldh [rIE], a
91+
ei
92+
; wait for SGB
93+
ld b, 12 /* You must wait 12 or more frames before trying to send a packet */
94+
:halt
95+
dec b
96+
jr nz, :-
97+
; enable multi
98+
ld hl, Packets.mlt /* send a `MLT_REQ` */
99+
call Packet
100+
rept 4 /* then wait 4 more frames, since the SNES won't "read" the packet instantly */
101+
halt
102+
endr
103+
; check if SGB responds
104+
/* and now We actually try to detect the SGB
105+
setting `P1.5` to low then high advances the "read player"
106+
setting `P1.4` and `P1.5` high will make the SGB return which player is currently selected in `P1.0` and `P1.1` */
107+
lb bc, 5, LOW(rP1) /* b is loaded with 5 which is how many times We try to check if the player changes to anything but P1 */
108+
:ld a, P1F_4 /* try to advance player */
109+
ldh [c], a
110+
ld a, P1F_4|P1F_5 /* then set P1 to return the current player */
111+
ldh [c], a
112+
ldh a, [c]
113+
dec b
114+
jp z, .init ; give up /* if 5 (4 actually) attempts fail, assume this isn't an SGB */
115+
cp $ff /* `P1.6` and `P1.7` always return %1, We set `P1.4` and `P1.5` to %1, and DMG, or SGB player 1, return %1111 in `P1.0`-`P1.3` */
116+
jr z, :- ; /* try again if detection fails */
117+
```
118+
119+
If you adopt this, remember to change `.init` to wherever code should jump to if it's _not_ on SGB, and put code meant to run on SGB after the snippet.
120+
121+
### SGB detection notes
122+
123+
- It would be a good idea to save somewhere in RAM whether the game is running on an SGB capable device or not, such that if you wish to change the border mid-gameplay, you won't have to perform SGB detection again
124+
125+
- **If you wish to only use 1 controller for the game, you will have to send another `MLT_REQ` to disable multiplayer** (`$89, $00`)
126+
127+
- You can change the waitloop to not use interrupts, or to use di+halt instead
128+
129+
## Border limitations
130+
131+
An SGB border has:
132+
133+
- 255 tiles + 1 transparent tile (preferably tile #0)
134+
- 3 palettes of 15 (+ 1 transparent) colors, (up to 45 solid colors total)
135+
- a 256x224px tilemap (there's a bit more to this, see [notes](#notes))
136+
137+
## Converting borders
138+
139+
With a recent version of [superfamiconv](https://github.com/Optiroc/SuperFamiconv):
140+
141+
```sh
142+
superfamiconv -v -i input.png -p output.pal -t output.4bpp -P 4 -m output.pct -M snes --color-zero 0000ff -B 4
143+
```
144+
145+
- `--color-zero` should be the color that your image for transparency, in my case it was blue.
146+
* If your image has an alpha channel, it can be set can also be set to `00000000` to use the actual transparent color; however, this may cause some issues.
147+
- `-v` is optional, for showing details of the conversion process
148+
- You can add a row of the transparent color at the top of the image to force superfamiconv to make it tile #0, then `incbin "output.pct", 64` to leave out that row.
149+
- `-P 4` sets the base palette to the 4th one, and **SGB borders use SNES palettes 4, 5, and 6.** as of writing this, this option only works if you built superfamiconv from source.
150+
151+
## Uploading borders
152+
153+
As stated before, the SGB border consists of tile data, picture data, and palette data. These are split across 2-3 packets:
154+
155+
- `CHR_TRN` (`$99`) is used to send 4KiB of tile data.
156+
157+
* since the border can use up to 8KiB of tiles, bit 0 of the second byte specifies which "half" you're sending
158+
- `$99, $00` if the screen is loaded with the first 4KiB of tile data
159+
- `$99, $01` if the screen is loaded with the second 4KiB of tile data
160+
161+
- `PCT_TRN` (`$a1`) is used to send the picture and palette data. it also swaps the border, generally a good idea to send it after the tile data[^1]
162+
163+
* assuming tiles `0`-`255` use VRAM from `$8000` to `$8fff`:
164+
- the picture data must be at `$8000`-`$873f` (last 64 bytes are _usually_ offscreen, see [notes](#notes))
165+
- palette data must be at `$8800`-`$885f`
166+
- everything else is ignored
167+
* how you skip putting data at `$8740`-`$87ff` is up to you, I prefer doing separate copies, valen prefers copying tilemap and palette data in one go, with the area between them pa
168+
169+
See also the related Pan Docs entry: [SGB Command Border](https://gbdev.io/pandocs/SGB_Command_Border.html).
170+
171+
[^1]: You can send a `CHR_TRN` [up to \~60 frames](https://github.com/pinobatch/little-things-gb/blob/b11b554d73c48a0f54fee0df31e59eb83806fcb4/sgbears/docs/long_story.md) after the `PCT_TRN` for it to apply to the current border, but not all emulators will emulate this. It's fine to just pretend `CHR_TRN`s must go before `PCT_TRN`.
172+
173+
## Notes
174+
175+
1. You can set the first row of tiles to your transparent color to force superfamiconv to put the transparent tile as the 1st tile, however you must then exclude 64 bytes of the tilemap (`incbin "border.pct"` -> `incbin "border.pct", 64`)
176+
177+
2. You can use palettes 0-3 and 7, if you really know what you're doing (animated borders yay). You will probably have to edit the border in YY-CHR, as there aren't really any other tools for that.
178+
179+
3. When the SNES lags, scanline 225 of the SGB border will be visible! You can set the topmost row of the 29th row of tiles to black to hide this.
180+
181+
4. If this doesn't work for you, you can ask for help on the [gbdev](https://gbdev.io/chat.html) channels.

0 commit comments

Comments
 (0)