Skip to content

Commit cc36c03

Browse files
committed
init
1 parent a0eaea3 commit cc36c03

File tree

8 files changed

+350
-1
lines changed

8 files changed

+350
-1
lines changed

Data/font.png

1.82 KB
Loading

Data/logo.bin

1.75 KB
Binary file not shown.

Data/placeholder.spc

64.5 KB
Binary file not shown.

Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name := loader
2+
debug := 0
3+
4+
5+
6+
derived_files := Data/font.png.palette
7+
derived_files += Data/font.png.tiles
8+
9+
#This is basically so it doesn't mess up tile to ascii order by deleting duplicate tiles
10+
Data/font.png.palette: palette_flags= --no-discard --no-flip
11+
Data/font.png.tiles: tiles_flags= --no-discard --no-flip
12+
Data/font.png: map_flags= --no-discard --no-flip
13+
14+
# Include libSFX.make, edit this to your libSFX location
15+
libsfx_dir := ../../../Resources/SDKs/libSFX
16+
include $(libsfx_dir)/libSFX.make
17+
18+
19+

Map.cfg

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# libSFX Default Memory Map (Mode 20 "LoROM" mapping with 4*32kB banks)
2+
# David Lindecrantz <[email protected]>
3+
4+
# Copy this file (or any of the supplied examples) to your source path as "Map.cfg" and edit as necessary
5+
# If no custom "Map.cfg" is found, this default file is included (from $(libsfx_dir)/Configurations/Map.cfg)
6+
7+
SYMBOLS {
8+
__STACKSIZE__: type = weak, value = $100;
9+
__ZPADSIZE__: type = weak, value = $10;
10+
__ZNMISIZE__: type = weak, value = $10;
11+
__RPADSIZE__: type = weak, value = $100;
12+
}
13+
14+
MEMORY {
15+
ZPAD: start = $000000, size = __ZPADSIZE__, define = yes;
16+
ZNMI: start = __ZPADSIZE__, size = __ZNMISIZE__, define = yes;
17+
ZPAGE: start = __ZPADSIZE__ + __ZNMISIZE__, size = $100 - (__ZPADSIZE__ + __ZNMISIZE__), define = yes;
18+
LORAM: start = $000100, size = $1f00 - __STACKSIZE__, define = yes;
19+
STACK: start = $002000 - __STACKSIZE__, size = __STACKSIZE__, define = yes;
20+
HIRAM: start = $7e2000, size = $e000, define = yes;
21+
EXRAM: start = $7f0000, size = $10000, define = yes;
22+
23+
ROM0: start = $808000, size = $8000, fill = yes, fillval = $ff;
24+
25+
26+
ROM1: start = $818000, size = $7F00, fill = yes, fillval = $ff;
27+
SPCHeader: start = $81FE00, size = $100, fill = yes, fillval = $00;
28+
ROM2: start = $828000, size = $8000, fill = yes, fillval = $ff;
29+
ROM3: start = $838000, size = $8000, fill = yes, fillval = $ff;
30+
ROM4: start = $848000, size = $8000, fill = yes, fillval = $ff;
31+
32+
SMPZPAGE: start = $0002, size = $00ee;
33+
SMPMMIO: start = $00f0, size = $0010;
34+
SMPSTACK: start = $0100, size = $0100;
35+
SMPRAM: start = $0200, size = $fdc0;
36+
}
37+
38+
SEGMENTS {
39+
ZPAD: load = ZPAD, type = zp, optional = yes;
40+
ZNMI: load = ZNMI, type = zp, optional = yes;
41+
ZEROPAGE: load = ZPAGE, type = zp, optional = yes;
42+
BSS: load = LORAM, type = bss, optional = yes;
43+
LORAM: load = LORAM, type = bss, optional = yes;
44+
HIRAM: load = HIRAM, type = bss, optional = yes;
45+
EXRAM: load = EXRAM, type = bss, optional = yes;
46+
47+
CODE: load = ROM0, type = ro;
48+
RODATA: load = ROM0, type = ro;
49+
LIBSFX: load = ROM0, type = ro, optional = yes;
50+
LIBSFX_PKG: load = ROM0, type = ro, optional = yes;
51+
LIBSFX_SMP: load = ROM0, type = rw, run = SMPZPAGE, optional = yes, define = yes;
52+
SMPCODE: load = ROM0, type = rw, run = SMPRAM, optional = yes, define = yes;
53+
HEADER: load = ROM0, type = ro, start = $80ffb0;
54+
VECTORS: load = ROM0, type = ro, start = $80ffe0;
55+
56+
SPCHeader: load = SPCHeader, type = ro;
57+
ROM1: load = ROM1, type = ro, optional = yes;
58+
ROM2: load = ROM2, type = ro, optional = yes;
59+
ROM3: load = ROM3, type = ro, optional = yes;
60+
ROM4: load = ROM4, type = ro, optional = yes;
61+
62+
SMPZPAGE: load = SMPZPAGE, type = zp, optional = yes;
63+
SMPRAM: load = SMPRAM, type = bss, optional = yes;
64+
}
65+
66+
# Group: Map.cfg
67+
#
68+
# Map.cfg is the configuration file passed to the ld65 linker.
69+
#
70+
# It consists of two main parts∶
71+
# * MEMORY describes the memory layout of the SNES and the cartridge
72+
# * SEGMENTS assigns segments to the memory areas specified
73+
#
74+
# In include/Configurations there are several template configuration
75+
# files for different ROM sizes and memory layouts.
76+
#
77+
# For full documentation of ld65 and its configuration refer to
78+
# the <official documentation at https://cc65.github.io/doc/ld65.html>.

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,17 @@
1-
# SPC_Loader
1+
# SPC Loader
2+
3+
Simple SNES Rom that transfers the SPC file stored at `$FF00` into the SNES APU RAM and plays it. Shows relevant metadata and SPC communication ports while playing. Designed so you do not need to reassemble or modify the SPC file to change the tune. Just copy the contents of the SPC file to $FF00 of the ROM and you're good.
4+
5+
6+
![screenshot](https://i.imgur.com/tS4KzFw.png)
7+
8+
## Assembling
9+
1. You need a Working libSFX installation (and Cygwin on Windows)
10+
2. git clone this repository
11+
3. edit the Makefile to adjust the libSFX path to where you set it up
12+
4. `make`
13+
5. loader.sfc is your output
14+
6. you can replace the loaded SPC file by either replacing `Data/placeholder.spc` or by copying the full contents of your SPC file to the ROM location `$FF00`.
15+
16+
## Releases
17+
Of course you don't need to assemble it to get your specific SPC file to work, that's the whole point. You can download the template ROM from the Releases tab of this repo. Just patch in your SPC file at `$FF00`.

libSFX.cfg

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
; libSFX Configuration
2+
; David Lindecrantz <[email protected]>
3+
4+
;-------------------------------------------------------------------------------
5+
;ROM Header
6+
7+
;Title (21 chars)
8+
; "123456789012345678901"
9+
define "ROM_TITLE", "BC SPC LOADER v0.1 "
10+
11+
;ROM map mode (4 bits)
12+
;Make sure that Map.cfg corresponds to the map mode selected
13+
; Common modes:
14+
; $0 = Mode 20/30 (32k/bank Mode 20 "LoROM" mapping)
15+
; $1 = Mode 21/31 (64k/bank Mode 21 "HiROM" mapping)
16+
ROM_MAPMODE = $0
17+
18+
;ROM Size
19+
;Make sure that Map.cfg corresponds to the ROM size selected
20+
; $07 = 1 Mbit (128 kB)
21+
; $08 = 2 Mbit (256 kB)
22+
; $09 = 4 Mbit (512 kB)
23+
; $0a = 8 Mbit (1 MB)
24+
; $0b = 16 Mbit (2 MB)
25+
; $0c = 32 Mbit (4 MB)
26+
ROM_ROMSIZE = $09

spc_loader.s

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
.include "libSFX.i"
2+
3+
4+
;Very hacky, really not proud of this code. Written in ~30 minutes
5+
6+
;Offsets of several registers and strings present in the SPC header.
7+
;See http://vspcplay.raphnet.net/spc_file_format.txt for format specifications
8+
9+
SPCHEADER = $81FF25 ;CPU Registers
10+
SPCTITLE = $81FF2E
11+
SPCGAME = $81FF4E
12+
SPCDUMPER = $81FF6E
13+
SPCARTIST = $81FFB1
14+
SPCLENGTH = $81FFA9
15+
16+
17+
Main:
18+
19+
;VRAM destination addresses
20+
VRAM_MAP_LOC = $0000
21+
VRAM_TILES_LOC = $8000
22+
23+
;Just uploading the tileset, palette and tilemap (logo.bin) to VRAM
24+
VRAM_memcpy VRAM_TILES_LOC, Tiles, sizeof_Tiles
25+
VRAM_memcpy VRAM_MAP_LOC, logomap, sizeof_logomap
26+
CGRAM_memcpy $0, Palette, sizeof_Palette
27+
28+
29+
;This copies the first 32kb, second 32kb, DSP Registers and CPU Registers from the ROM to WRAM.
30+
;Normally this could be handled by the SPC_Play macro included in libSFX,
31+
;But that routine expects DSP and CPU registers to be right after eachother which is not the case for SPC files
32+
;Since i wanted to make it so you can drop in the SPC file unmodified without reassembling, i had to do some manual work.
33+
;This very closely mimics the libSFX macro though.
34+
WRAM_memcpy SFX_SPC_IMAGE, LOWB, $8000 ;Low 32k
35+
WRAM_memcpy SFX_SPC_IMAGE + $8000, HIGHB, $8000 ;High 32k
36+
WRAM_memcpy SFX_DSP_STATE, DSPSTATE, $80 ;DSP Registers
37+
WRAM_memcpy SFX_DSP_STATE+$80, SPCHEADER, $7 ;CPU Registers
38+
39+
;This calls the libSFX copy and exec routine
40+
RW_push set:a8i16
41+
jsl SFX_SMP_execspc
42+
RW_pull
43+
44+
45+
;The next few lines each read a string of metadata from rom, prepares it for VRAM and then copies it to VRAM.
46+
;Horribly inefficient, but it only does this once when starting so whatever.
47+
WRAM_memcpy $00, SPCTITLE, $20
48+
jsl preparetext
49+
VRAM_memcpy $150, $100, $30
50+
51+
WRAM_memcpy $00, SPCGAME, $20
52+
jsl preparetext
53+
VRAM_memcpy $1D0, $100, $30
54+
55+
WRAM_memcpy $00, SPCARTIST, $20
56+
jsl preparetext
57+
VRAM_memcpy $250, $100, $30
58+
59+
WRAM_memcpy $00, SPCDUMPER, $20
60+
jsl preparetext
61+
VRAM_memcpy $2D0, $100, $20
62+
63+
WRAM_memcpy $00, SPCLENGTH, $20
64+
jsl preparetext
65+
VRAM_memcpy $350, $100, $6
66+
67+
;The screen setup is straight up copied from the Hello example of libSFX. Couldn't be bothered tbh.
68+
;Set up screen mode
69+
lda #bgmode(BG_MODE_1, BG3_PRIO_NORMAL, BG_SIZE_8X8, BG_SIZE_8X8, BG_SIZE_8X8, BG_SIZE_8X8)
70+
sta BGMODE
71+
lda #bgsc(VRAM_MAP_LOC, SC_SIZE_32X32)
72+
sta BG1SC
73+
ldx #bgnba(VRAM_TILES_LOC, 0, 0, 0)
74+
stx BG12NBA
75+
lda #tm(ON, OFF, OFF, OFF, OFF)
76+
sta TM
77+
78+
;Turn on screen
79+
lda #inidisp(ON, DISP_BRIGHTNESS_MAX)
80+
sta SFX_inidisp
81+
VBL_on
82+
83+
84+
VBL_set handler ;Sets up the VBlank interrupt handler that copies the SPC port values to VRAM every frame.
85+
86+
: wai
87+
bra :-
88+
89+
90+
handler:
91+
92+
;In desperate need for a loop. Basically does the same exact thing 4 times. Once for each byte
93+
;Basically it explodes a byte into 2 nibbles which then corospond to the tiles $0-F. Also every second VRAM byte is 0 to not mess with palette and stuff.
94+
lda $2140 ;Port 0
95+
and #$F0 ;Only the high nibble
96+
lsr a ;shift right 4 times to put high nibble in low nibble
97+
lsr a
98+
lsr a
99+
lsr a
100+
sta $100 ;store to RAM to get copied to VRAM later
101+
102+
lda $2140;Still port 0
103+
and #$0F ;Only the low nibble
104+
sta $102 ;No need to shift it since it's already in the low nibble. Also stored in WRAM for upload
105+
106+
107+
lda #$20 ;Tile for a blank space
108+
sta $104 ;Space inbetween 2 shown bytes
109+
110+
;And now basically the whole thing 3 more times. Did i mention i could've looped this. oh well.
111+
112+
lda $2141
113+
and #$F0
114+
lsr a
115+
lsr a
116+
lsr a
117+
lsr a
118+
sta $106
119+
120+
lda $2141
121+
and #$0F
122+
sta $108
123+
124+
125+
lda #$20
126+
sta $10a
127+
128+
lda $2142
129+
and #$F0
130+
lsr a
131+
lsr a
132+
lsr a
133+
lsr a
134+
sta $10c
135+
136+
lda $2142
137+
and #$0F
138+
sta $10e
139+
140+
141+
lda #$20
142+
sta $110
143+
144+
lda $2143
145+
and #$F0
146+
lsr a
147+
lsr a
148+
lsr a
149+
lsr a
150+
sta $112
151+
152+
lda $2143
153+
and #$0F
154+
sta $114
155+
156+
VRAM_memcpy $4D5, $100, $16 ;Aaaaaaaaaand ship it off to VRAM and return.
157+
rtl
158+
159+
160+
;oof where do i even start on this one...
161+
;This is the routine that prepares the text for VRAM
162+
;VRAM doesn't accept straight up bytes for the tilemap. The tilemap data is interleaved with palette, priority and flip data for each tile.
163+
; Some of the bits in the second byte are also used to select tiles higher than $FF. Just read up in fullsnes, not that difficult.
164+
; For this it basically means i have to add a $00 byte after each character byte. I probably massively overcomplicated this.
165+
166+
preparetext:
167+
ldx #$00 ;Sets the X index to 0
168+
phx ;Pushes the X index to the stack. The index will later be used both for source and destination with some switcharoo trickery.
169+
170+
ptloop: ;Processing loop starts here
171+
LDA $00, X ;Loads the current byte at index $00+X
172+
cmp #$00 ;Compares it with $00. $00 is used in SPC files as a placeholder if the string is shorter than the full 32b.
173+
;In my tileset that's not a blank time (0 tile from the hex font)
174+
bne skip0 ;So if it detects that it's a $00 character...
175+
LDA #$20 ;It replaces it with a blank $20 tile.
176+
skip0: ;Otherwise it just skips this.
177+
inx ;Alright loading the byte into A complete, source X index gets incremented.
178+
txy ;Temporarily puts X into Y, for safe keeping.
179+
plx ;Pulls X from the stack (this is where the destination X index was, or $00 in the first iteration)
180+
phy ;Puts Y on the stack (This gets pulled as X from the stack later) We basically do a switcharoo. Pull one x from the stack and push the other onto it.
181+
STA $100, X ;Store byte in A into the destination address
182+
STZ $101, X ;Store zero into the byte right after it, for obvious reasons.
183+
inx ;Increment X twice because we just wrote 2 bytes.
184+
inx
185+
txy ;Puts X into Y, same as before
186+
plx ;Pulls source X from the stack
187+
phy ;Pushes Y (destination X) to the stack. same thing as above.
188+
cpx #$20 ;Check if we copied 32bytes
189+
bne ptloop ;If no, go back to the beginning of the loop
190+
plx ;Otherwise, pull the leftover X from the Stack to make room for the return address that's still on the stack
191+
rtl ;Return
192+
193+
194+
195+
;Import graphics stuff.
196+
incbin logomap, "Data/logo.bin"
197+
incbin Palette, "Data/font.png.palette"
198+
incbin Tiles, "Data/font.png.tiles"
199+
200+
;Import placeholder music
201+
.segment "SPCHeader"
202+
.incbin "Data/placeholder.spc", $0, $100
203+
.SEGMENT "ROM2"
204+
LOWB: .incbin "Data/placeholder.spc", $100, $8000
205+
.SEGMENT "ROM3"
206+
HIGHB: .incbin "Data/placeholder.spc", $8100, $8000
207+
.SEGMENT "ROM4"
208+
DSPSTATE: .incbin "Data/placeholder.spc", $10100
209+
210+

0 commit comments

Comments
 (0)