Compare commits

..

No commits in common. "e915a88cbf60ba0812ffa45df0c2e8889671c491" and "b032b50b6aaaab423164cedb6d07c6e45cfc2550" have entirely different histories.

7 changed files with 29 additions and 1122 deletions

View File

@ -1,10 +1,12 @@
.PHONY: all .PHONY: all
all: bin/script-decode bin/sbutil bin/just-stats bin/extract-imgs all: bin/script-decode bin/sbutil bin/just-stats
bin/script-decode: script/*.go bin/script-decode: cmd/script-decode.go script/*.go
bin/sbutil: rom/*.go go build -o $@ $<
bin/just-stats: script/*.go
bin/sbutil: cmd/sbutil.go rom/*.go
bin/%: cmd/%.go go build -o $@ $<
bin/just-stats: cmd/just-stats.go script/*.go
go build -o $@ $< go build -o $@ $<

View File

@ -1,575 +0,0 @@
package main
import (
"fmt"
"os"
"image"
"image/draw"
"image/color"
"image/png"
//"strings"
"encoding/binary"
"github.com/alexflint/go-arg"
nesimg "github.com/zorchenhimer/go-retroimg"
//"github.com/zorchenhimer/go-retroimg/palette"
)
type Arguments struct {
Nametable string `arg:"--nt,required"`
Chr string `arg:"--chr,required"`
Output string `arg:"--output,required"`
IsSprite bool `arg:"--sprites"`
SpriteSheet bool `arg:"--sprite-sheet"`
NoBg bool `arg:"--no-bg"` // don't draw hot-pink background
}
var (
tileMissing *nesimg.Tile
)
func main() {
args := &Arguments{}
arg.MustParse(args)
var err error
tileMissing, err = nesimg.NewTileFromPlanes([][]byte{
{0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33},
{0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F} })
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err = run(args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(args *Arguments) error {
fmt.Println("--")
fmt.Println("Nametable:", args.Nametable)
fmt.Println("CHR:", args.Chr)
fmt.Println("Output:", args.Output)
fmt.Println("IsSprite:", args.IsSprite)
fmt.Println("SpriteSheet:", args.SpriteSheet)
fmt.Println("--")
chrFile, err := os.Open(args.Chr)
if err != nil {
return err
}
defer chrFile.Close()
raw := nesimg.NewRawChr(chrFile)
tiles, err := raw.ReadAllTiles(nesimg.BD_2bpp)
if err != nil {
return err
}
//fmt.Printf("first tile: %#v\n", tiles[0])
//tileUniform_00 := image.NewUniformPaletted(nesimg.DefaultPal_2bpp, 0)
//tileUniform_F0 := image.NewUniformPaletted(nesimg.DefaultPal_2bpp, 1)
//tileUniform_0F := image.NewUniformPaletted(nesimg.DefaultPal_2bpp, 2)
//tileUniform_FF := image.NewUniformPaletted(nesimg.DefaultPal_2bpp, 3)
tileUniform_00, err := nesimg.NewTileFromPlanes([][]byte{
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} })
if err != nil {
return err
}
tileUniform_F0, err := nesimg.NewTileFromPlanes([][]byte{
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} })
if err != nil {
return err
}
tileUniform_0F, err := nesimg.NewTileFromPlanes([][]byte{
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} })
if err != nil {
return err
}
tileUniform_FF, err := nesimg.NewTileFromPlanes([][]byte{
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} })
if err != nil {
return err
}
tiles = append([]*nesimg.Tile{
tileUniform_00,
tileUniform_F0,
tileUniform_0F,
tileUniform_FF,
}, tiles...)
ntData, err := os.ReadFile(args.Nametable)
if err != nil {
return err
}
layers, err := ReadData(ntData, tiles, args.IsSprite)
if err != nil {
return fmt.Errorf("ReadData() error: %w", err)
}
uni := image.NewUniform(color.RGBA{0xFF, 0x00, 0xFF, 0xFF})
var screen *image.RGBA
if args.SpriteSheet {
maxWidth := 0
maxHeight := 0
for _, l := range layers {
maxWidth += l.Bounds().Dx()
if l.Bounds().Dy() > maxHeight {
maxHeight = l.Bounds().Dy()
}
}
screen = image.NewRGBA(image.Rect(0, 0, maxWidth, maxHeight))
if !args.NoBg {
draw.Draw(screen, screen.Bounds(), uni, image.Pt(0, 0), draw.Over)
}
loc := image.Pt(0, 0)
for _, l := range layers {
draw.Draw(screen, l.Bounds().Add(loc), l, image.Pt(0, 0), draw.Over)
loc = loc.Add(image.Pt(l.Bounds().Dx(), 0))
}
} else {
screen = image.NewRGBA(image.Rect(0, 0, 32*8, 30*8))
if !args.NoBg {
draw.Draw(screen, screen.Bounds(), uni, image.Pt(0, 0), draw.Over)
}
for _, l := range layers {
if args.IsSprite {
draw.Draw(screen, l.Bounds().Add(l.Location), l, image.Pt(0, 0), draw.Over)
} else {
draw.Draw(screen, screen.Bounds(), l, image.Pt(0, 0), draw.Over)
}
}
}
output, err := os.Create(args.Output)
if err != nil {
return err
}
defer output.Close()
err = png.Encode(output, screen)
if err != nil {
return err
}
//img := image.NewRGBA(image.Rect(0, 0, 32*8, 30*8))
return nil
}
//type UniformPaletted struct {
// Palette color.Palette
// Index int
//}
//
//func NewUniformPaletted(palette color.Palette, idx int) *UniformPaletted {
// if idx >= len(palette) {
// panic("Index out of palette range")
// }
//
// return &UniformPaletted{
// Palette: palette,
// Index: idx,
// }
//}
//
//func (u *UniformPaletted) At(x, y int) color.Color {
// if u.Index >= len(u.Palette) {
// panic("Index out of palette range")
// }
//
// return u.Palette[u.Index]
//}
//
//func (u *UniformPaletted) ColorModel() color.Model {
// return u.Palette
//}
//
//func (u *UniformPaletted) Bounds() image.Rectangle {
// // Copied from image.Uniform.Bounds()
// return image.Rectangle{image.Point{-1e9, -1e9}, image.Point{1e9, 1e9}}
//}
func BytesToInt(raw []byte) int {
if len(raw) > 2 {
panic("only 8 and 16 bit numbers for now")
}
if len(raw) == 1 {
return int(raw[0])
}
return int(raw[1])<<8 | int(raw[0])
}
type DataHeader struct {
PaletteOffset uint16
ArgB uint16
ImageCount uint8
}
func (h DataHeader) String() string {
return fmt.Sprintf("{DataHeader PaletteOffset:$%04X ArgB:$%04X ImageCount:%d}",
h.PaletteOffset,
h.ArgB,
h.ImageCount,
)
}
type ImageHeader struct {
Width uint8
Height uint8
AttrLength uint16
// in pixels
X uint8
Y uint8
}
func (h ImageHeader) String() string {
return fmt.Sprintf("{ImageHeader Width:%d[%02X] Height:%d[%02X] AttrLength:$%04X XCoord:%d[%02X] YCoord:%d[%02X]}",
h.Width, h.Width,
h.Height, h.Height,
h.AttrLength,
h.X, h.X,
h.Y, h.Y,
)
}
func ReadData(raw []byte, tiles []*nesimg.Tile, isSprites bool) ([]*Layer, error) {
//raw, err := io.ReadAll(r)
//if err != nil {
// return nil, err
//}
if isSprites {
tiles = tiles[4:]
}
dataHeader := &DataHeader{}
_, err := binary.Decode(raw, binary.LittleEndian, dataHeader)
if err != nil {
return nil, err
}
dataHeader.PaletteOffset += 4
fmt.Println(dataHeader)
imgHeaders := []*ImageHeader{}
for i := 0; i < int(dataHeader.ImageCount); i++ {
head := &ImageHeader{}
_, err = binary.Decode(raw[4+(i*6)+1:], binary.LittleEndian, head)
if err != nil {
return nil, err
}
fmt.Println(head)
imgHeaders = append(imgHeaders, head)
}
palettes := []color.Palette{}
colorIds := [][]string{}
for i := 0; i < 4; i++ {
p := color.Palette{}
idlist := []string{}
for j := 0; j < 4; j++ {
v := raw[int(dataHeader.PaletteOffset)+(i*4)+j]
if v == 0x3D {
v = 0x0F
}
c, ok := NesColors[v]
if !ok {
return nil, fmt.Errorf("Color value $%02X invalid", v)
}
p = append(p, c)
idlist = append(idlist, fmt.Sprintf("%02X", v))
}
palettes = append(palettes, p)
colorIds = append(colorIds, idlist)
}
fmt.Println("Palettes:")
for i := 0; i < len(palettes); i++ {
fmt.Printf("%s: %v\n", colorIds[i], palettes[i])
}
nesTileSize := image.Rect(0, 0, 8, 8)
offset := 5 + len(imgHeaders) * 6
layers := []*Layer{}
for _, head := range imgHeaders {
rect := image.Rect(0, 0, 32, 30)
if isSprites {
rect = image.Rect(0, 0, int(head.Width), int(head.Height))
}
l := NewLayer(
rect,
nesTileSize,
palettes,
)
l.Transparency = isSprites
l.Location = image.Pt(int(head.X), int(head.Y))
l.IsSprite = isSprites
//tileIds := []int{}
if isSprites {
for idx, b := range raw[offset:offset+(int(head.Width)*int(head.Height))] {
id := int(uint(b)) // is this required to ignore negatives?
if id < len(tiles) {
l.Tiles[idx] = tiles[id]
}
}
} else {
for idx, b := range raw[offset:offset+(int(head.Width)*int(head.Height))] {
row := idx / int(head.Width)
col := idx % int(head.Width)
arrIdx := ((row+int(head.Y/8)) * 32) + (col+int(head.X/8))
//fmt.Printf("%d ", arrIdx)
id := int(uint(b)) // is this required to ignore negatives?
if id < len(tiles) {
l.Tiles[arrIdx] = tiles[id]
//tileIds = append(tileIds, id)
}
}
}
//for idx, t := range tileIds {
// fmt.Printf("[%d] %02X\n", idx, t)
//}
offset += int(head.Width)*int(head.Height)
if isSprites {
l.Attributes = raw[offset:offset+int(head.AttrLength)]
} else {
// FIXME: This will break on background chunks that aren't a full screen.
// Need to verify the anchor point in the firmware for this case (for
// when the tile anchor point isn't aligned to an attribute byte).
// Partial screen attributes will also be a different size than 8*8.
l.SetAttributes(raw[offset:offset+int(head.AttrLength)])
}
offset += int(head.AttrLength)
layers = append(layers, l)
}
return layers, nil
}
type Layer struct {
Tiles []*nesimg.Tile
Attributes []byte
Palettes []color.Palette
TileSize image.Rectangle
Location image.Point
Width int
Height int
Transparency bool // true for sprites
IsSprite bool
Solid bool
}
func NewLayer(layerSize image.Rectangle, tileSize image.Rectangle, palettes []color.Palette) *Layer {
return &Layer{
Tiles: make([]*nesimg.Tile, layerSize.Dx()*layerSize.Dy()),
Attributes: make([]byte, layerSize.Dx()*layerSize.Dy()),
TileSize: tileSize,
Location: image.Pt(0, 0),
Width: layerSize.Dx(),
Height: layerSize.Dy(),
Palettes: palettes,
}
}
func (l *Layer) At(x, y int) color.Color {
if l.Solid {
return color.RGBA{0x00, 0x00, 0x00, 0xFF}
}
width, height := l.TileSize.Dx(), l.TileSize.Dy()
row := y / height
col := x / width
tx := x % width
ty := y % height
tileIdx := (row*l.Width)+col
if x == 89 && y == 101 {
fmt.Printf("row:%d col:%d tx:%d ty:%d tileIdx:%d width:%d height:%d l.Width:%d l.Height:%d\n",
row, col, tx, ty, tileIdx, width, height, l.Width, l.Height,
)
}
if l.Tiles[tileIdx] == nil {
return color.RGBA{0x00, 0x00, 0x00, 0x00}
//return color.RGBA{0xFF, 0x00, 0xFF, 0xFF}
}
colorIdx := l.Tiles[tileIdx].ColorIndexAt(tx, ty)
if l.Transparency && colorIdx == 0 {
return color.RGBA{0x00, 0x00, 0x00, 0x00}
}
palIdx := l.Attributes[tileIdx]
return l.Palettes[palIdx][colorIdx]
}
func (l *Layer) Bounds() image.Rectangle {
return image.Rect(0, 0, l.TileSize.Max.X*l.Width, l.TileSize.Max.Y*l.Height)
}
func (l *Layer) ColorModel() color.Model {
return color.RGBAModel
}
func (sc *Layer) SetAttributes(data []byte) error {
if len(data) != 64 {
return fmt.Errorf("Attribute data must be 64 bytes")
}
sc.Attributes = make([]byte, 32*30)
for row := 0; row < 8; row++ {
for col := 0; col < 8; col++ {
src := row*8+col
start := (row*32)*4 + (col*4)
raw := data[src]
br := (raw >> 6) & 0x03
bl := (raw >> 4) & 0x03
tr := (raw >> 2) & 0x03
tl := raw & 0x03
//if row == 0 && col == 0 {
// fmt.Printf("br:%02X bl:%02X tr:%02X tl:%02X\n", br, bl, tr, tl)
//}
sc.Attributes[start+0+(32*0)] = tl
sc.Attributes[start+1+(32*0)] = tl
sc.Attributes[start+0+(32*1)] = tl
sc.Attributes[start+1+(32*1)] = tl
sc.Attributes[start+2+(32*0)] = tr
sc.Attributes[start+3+(32*0)] = tr
sc.Attributes[start+2+(32*1)] = tr
sc.Attributes[start+3+(32*1)] = tr
if row < 7 {
sc.Attributes[start+0+(32*2)] = bl
sc.Attributes[start+1+(32*2)] = bl
sc.Attributes[start+0+(32*3)] = bl
sc.Attributes[start+1+(32*3)] = bl
sc.Attributes[start+2+(32*2)] = br
sc.Attributes[start+3+(32*2)] = br
sc.Attributes[start+2+(32*3)] = br
sc.Attributes[start+3+(32*3)] = br
}
}
}
return nil
}
var NesColors map[byte]color.Color = map[byte]color.Color{
0x00: color.RGBA{0x66, 0x66, 0x66, 0xFF},
0x10: color.RGBA{0xAD, 0xAD, 0xAD, 0xFF},
0x20: color.RGBA{0xFF, 0xFF, 0xEF, 0xFF},
0x30: color.RGBA{0xFF, 0xFF, 0xEF, 0xFF},
0x01: color.RGBA{0x00, 0x2A, 0x88, 0xFF},
0x11: color.RGBA{0x15, 0x5F, 0xD9, 0xFF},
0x21: color.RGBA{0x64, 0xB0, 0xFF, 0xFF},
0x31: color.RGBA{0xC0, 0xDF, 0xFF, 0xFF},
0x02: color.RGBA{0x14, 0x12, 0xA7, 0xFF},
0x12: color.RGBA{0x42, 0x40, 0xFF, 0xFF},
0x22: color.RGBA{0x92, 0x90, 0xFF, 0xFF},
0x32: color.RGBA{0xD3, 0xD2, 0xFF, 0xFF},
0x03: color.RGBA{0x3B, 0x00, 0xA4, 0xFF},
0x13: color.RGBA{0x75, 0x27, 0xFE, 0xFF},
0x23: color.RGBA{0xC6, 0x76, 0xFF, 0xFF},
0x33: color.RGBA{0xE8, 0xC8, 0xFF, 0xFF},
0x04: color.RGBA{0x5C, 0x00, 0x7E, 0xFF},
0x14: color.RGBA{0xA0, 0x1A, 0xCC, 0xFF},
0x24: color.RGBA{0xF3, 0x6A, 0xFF, 0xFF},
0x34: color.RGBA{0xFB, 0xC2, 0xFF, 0xFF},
0x05: color.RGBA{0x6E, 0x00, 0x40, 0xFF},
0x15: color.RGBA{0xB7, 0x1E, 0x7B, 0xFF},
0x25: color.RGBA{0xFE, 0x6E, 0xCC, 0xFF},
0x35: color.RGBA{0xFE, 0xC4, 0xEA, 0xFF},
0x06: color.RGBA{0x6C, 0x06, 0x00, 0xFF},
0x16: color.RGBA{0xB5, 0x31, 0x20, 0xFF},
0x26: color.RGBA{0xFE, 0x81, 0x70, 0xFF},
0x36: color.RGBA{0xFE, 0xCC, 0xC5, 0xFF},
0x07: color.RGBA{0x56, 0x1D, 0x00, 0xFF},
0x17: color.RGBA{0x99, 0x4E, 0x00, 0xFF},
0x27: color.RGBA{0xEA, 0x9E, 0x22, 0xFF},
0x37: color.RGBA{0xF7, 0xD8, 0xA5, 0xFF},
0x08: color.RGBA{0x33, 0x35, 0x00, 0xFF},
0x18: color.RGBA{0x6B, 0x6D, 0x00, 0xFF},
0x28: color.RGBA{0xBC, 0xBE, 0x00, 0xFF},
0x38: color.RGBA{0xE4, 0xE5, 0x94, 0xFF},
0x09: color.RGBA{0x0B, 0x48, 0x00, 0xFF},
0x19: color.RGBA{0x38, 0x87, 0x00, 0xFF},
0x29: color.RGBA{0x88, 0xD8, 0x00, 0xFF},
0x39: color.RGBA{0xCF, 0xEF, 0x96, 0xFF},
0x0A: color.RGBA{0x00, 0x52, 0x00, 0xFF},
0x1A: color.RGBA{0x0C, 0x93, 0x00, 0xFF},
0x2A: color.RGBA{0x5C, 0xE4, 0x30, 0xFF},
0x3A: color.RGBA{0xBD, 0xF4, 0xAB, 0xFF},
0x0B: color.RGBA{0x00, 0x4F, 0x08, 0xFF},
0x1B: color.RGBA{0x00, 0x8F, 0x32, 0xFF},
0x2B: color.RGBA{0x45, 0xE0, 0x82, 0xFF},
0x3B: color.RGBA{0xB3, 0xF3, 0xCC, 0xFF},
0x0C: color.RGBA{0x00, 0x40, 0x4D, 0xFF},
0x1C: color.RGBA{0x00, 0x7C, 0x8D, 0xFF},
0x2C: color.RGBA{0x48, 0xCD, 0xDE, 0xFF},
0x3C: color.RGBA{0xB5, 0xEB, 0xF2, 0xFF},
0x0D: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x1D: color.RGBA{0x00, 0x7C, 0x8D, 0xFF},
0x2D: color.RGBA{0x4F, 0x4F, 0x4F, 0xFF},
0x3D: color.RGBA{0xB8, 0xB8, 0xB8, 0xFF},
0x0E: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x1E: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x2E: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x3E: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x0F: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x1F: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x2F: color.RGBA{0x00, 0x00, 0x00, 0xFF},
0x3F: color.RGBA{0x00, 0x00, 0x00, 0xFF},
}

9
go.mod
View File

@ -1,10 +1,7 @@
module git.zorchenhimer.com/Zorchenhimer/go-studybox module git.zorchenhimer.com/Zorchenhimer/go-studybox
go 1.24.1 go 1.22.2
require ( require github.com/alexflint/go-arg v1.4.3
github.com/alexflint/go-arg v1.5.1
github.com/zorchenhimer/go-retroimg v0.0.0-20251108020316-705ea2ebacd6
)
require github.com/alexflint/go-scalar v1.2.0 // indirect require github.com/alexflint/go-scalar v1.1.0 // indirect

17
go.sum
View File

@ -1,15 +1,16 @@
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo=
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zorchenhimer/go-retroimg v0.0.0-20251108020316-705ea2ebacd6 h1:94b43etKen/R0Ga+lxgfSMlYQicwsMvlFBizMOQ3loc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
github.com/zorchenhimer/go-retroimg v0.0.0-20251108020316-705ea2ebacd6/go.mod h1:iQUJQkvvbgycl7TS2OWdSC0+kHYypOASX129xmnv+SE= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -114,7 +114,7 @@ func decodeDelay(page *Page, data []byte, idx int) (Packet, int, error) {
count++ count++
} }
if count%2 != 0 { if count%2 != 0 {
fmt.Printf("0xAA delay packet at offset %08X has odd number of 0xAA's\n", idx+page.FileOffset) fmt.Printf("0xAA delay packet at offset %08X has odd number of 0xAA's", idx+page.FileOffset)
} }
pd := &packetDelay{ pd := &packetDelay{
Length: count, Length: count,
@ -123,10 +123,8 @@ func decodeDelay(page *Page, data []byte, idx int) (Packet, int, error) {
checksum := calcChecksum(data[idx : idx+count+3]) checksum := calcChecksum(data[idx : idx+count+3])
if checksum != 0xC5 { if checksum != 0xC5 {
// return nil, 0, fmt.Errorf("Invalid checksum for delay packet starting at offset %08X. Got %02X, expected %02X", return nil, 0, fmt.Errorf("Invalid checksum for delay packet starting at offset %08X. Got %02X, expected %02X",
// pd.address, checksum, 0xC5) pd.address, checksum, 0xC5)
fmt.Printf("WARN: Invalid checksum for delay packet starting at offset %08X. Got %02X, expected %02X\n",
pd.address, checksum, 0xC5)
} }
idx += count + 3 idx += count + 3

View File

@ -1,502 +0,0 @@
## 0x80 Play Beep
Stack Arguments: 0
Inline Arguments: 0
Vars used:
Byte_0493
Play's an audible beep on the Square 1 channel.
## 0x81 Halt
Stack Arguments: 0
Inline Arguments: 0
Vars used:
N/A
Infinite loop that does not return.
## 0x82 Tape NMI Shenanigans
Stack Arguments: 0
Inline Arguments: 0
Vars:
Byte_E0_TapeCtrl_Cache
Byte_EE
Byte_F2
Byte_0740
Byte_07EF
Byte_07F3
JSRs:
L2706_SetupNMI_ED00_LongJump
L2721_TurnOnNMI_LongJump
L2724_TurnOffNMI_LongJump
L2742
## 0x83 Tape Wait
Stack Arguments: 0
Inline Arguments: 0
Vars:
Byte_0740
JMPs to `L1329_WaitOn_EE`
## 0x84 Jump
Stack Arguments: 0
Inline Arguments: 1 Word
Vars:
Code_Pointer
Argument_A
Updates the script pointer to the inline address and continues script execution
from the new address.
## 0x85 Call
Stack Arguments: 0
Inline Arguments: 1 Word
Vars:
Code_Pointer
Argument_A
Stack_Pointer
Pushes return address to the stack and performs a Jump to the inline script
address.
## 0x86 Return
Stack Arguments: 0 (1 Word, implied)
Inline Arguments: 0
Vars:
Stack_Pointer
Code_Pointer
Remove a script address from the stack and update the `Code_Pointer` to it
before continuing execution.
## 0x87 Loop
Stack Arguments: 0 (4 implied)
Inline Arguments: 0
Args manually pulled from stack:
- Limit
- Increment
- LoopVar
- LoopEntry
ArgA `Stack_Pointer-6`
ArgB `(ArgD)`
ArgC `Stack_Pointer-2`
ArgD `Stack_Pointer-4`
ArgE `ArgA+1`
Vars:
Argument_A
Argument_B
Argument_C
Argument_D
Argument_E
Stack_Pointer
JSRs:
Handler_CB_Sum
Handler_C7_LessThan
JMPs to `L49CD_LessThan`
## 0x88 Play Sound
Stack Arguments: 32 bytes (string copied to `$0700`)
Inline Arguments: 0
Plays a short SFX defined by a string.
Vars:
Pointer_A0
Pointer_A2
Byte_0494
Byte_0495
Byte_0496
Byte_0497
Byte_0498
Byte_0499
Byte_04AC_AudioState
Byte_04AD
Vars inside `L5C18_CopyPtrA0PtrA2`:
Byte_0490_PtrA0Len
Vars inside `L5A6F_DecodeAudioString_EntryPoint`:
Pointer_A2
Byte_04AC_AudioState
Word_049A+0 (current channel ID)
Word_049A+1 (current channel mask)
Byte_0497 Byte_0498 Byte_0499
enable byte for each channel. RTS if != 1 on entry
written to with value of Byte_04B3
Byte_0494 Byte_0495 Byte_0496
Pointer_A2 low for channel. looks like it's updated after reading a
string.
Byte_049F Added to note lookup index; used with O stuff?
Byte_04B1 octave? value from Table_04A6
Byte_04B2 T value
Byte_04B3 = Byte_04B1 * Byte_04B2
Table_049C Y#
Table_04A0 V##
Table_04A3 M#
Table_04A6 ?? note related. octave? value after letter
Table_04A9 O#
Table_04AE T#
Table_B391 G??
JSRs:
L5C08_DataLen_A0
L5C18_CopyPtrA0PtrA2
L5CC5_LongDelay
L5A6F_DecodeAudioString_EntryPoint
L5A8F_DecodeAudioString (if audio is turned on)
L5C42 (for weird chars?)
Hard-coded addresses:
$0700 (Pointer_A0)
$0420 (Pointer_A2)
$0441 (Pointer_A2)
$0462 (Pointer_A2)
Copies data currently at `$0700` to three locations (`$0420`, `$0441`, `$0462`)
### String format
Three channels are encoded in this string, separated by a colon (`:`). The
string consists of letter and number pairs. The order of the individual pairs
in the overal string don't seem to matter.
M# Loop & Constant volume
number is 0 or 1.
V## Volume
number between 0 and 15, inclusive
Y# Duty
number between 0 and 3, inclusive
T# ?? $4001/$4006?
number between 1 and 9, inclusive (verify this)
O# Note related (timer low/high stuff)
number between 1 and 6, inclusive
A#-G# Notes and octaves?
number between 0 and 10, inclusive
## 0x89
Stack Arguments: 3
Inline Arguments: 0
## 0x8A Pop String to Address
Stack Arguments: 0
Inline Arguments: 1 Word
Removes 32 bytes from the stack and writes them starting to the inline address.
## 0x8B
## 0x97
Arguments: 2
ArgA
ArgB
Vars:
Byte_0740
Byte_44FE
Byte_4598 = Argument_B+0
Byte_44FD
Argument_A
Array_44FB+2 ($44FD)
JSRs:
L5592_CheckForZero
Conditionally sets up NMI stuff depending on byte $0740
If ArgA >= 3, setup some more stuff
## 0xA9
Arguments: 1
ArgA
Some sort of nametable restore operation?
Writes smaller updates to the nametable for animation purposes.
## 0x9D Something Tape (draw screen?)
Arguments: 2
ArgA -> X
AgrB
Vars:
Byte_44FE
Array_44F8, X
Array_44CF, X
Array_457A, X
If ArgB != 0
ldx ArgA
lda #1
sta Array_44F8, X
sta Array_44CF, X
lda #0
Array_457A, X
else, check for zero from $44F9 through $44FC.
if non-zero, store #1 into `Byte_FF4E` and return.
else:
ldx ArgA
lda #1
sta Array_44F8, X
lda #0
sta Byte_44F8
Then wait for `Array_44F8, X` to become zero. We're waiting for the IRQ to
finish writing data from the tape to RAM.
After this, jump into opcode `0x9E` after the argument parsing to draw a
screen.
## 0x9E Draw And Show Screen
Arguments: 2
ArgA
ArgB
If ArgB == 0, clear out a bunch of arguments and call `Handler_DD`. Clears
`Byte_44FE` afterwards and returns.
If ArgB != 0, make sure data has been loaded off the tape and is ready to draw.
This is done by checking `Word_FF49, X` for a value of 1. If 1, increment it
and store it in `Byte_44FE` before returning. Continue otherwise.
### Drawing
lda #0
sta Byte_0750
sta Byte_4579
sta Byte_457A ("Array_457A")
lda ArgA
sta Byte_4C
### Data layout
Data in RAM, starting at CPU address $5000
$5000 Word offset to palette data. Added to #$5004.
$5002 Word
$5004 Byte loop counter apparently?? adds #6 to #$5004 this many times in a
pointer. additional header data?
// generic image header data
$5005 Byte Width (data row len (tile data len))
$5006 Byte Height (row count (title count))
Data length = Width*Height (stored in Word_61 and Word_6AFE)
$5007 Word data length? offset offset to attr data?
$5008 Byte X/Y coords
$5009 Byte X/Y coords
$500B Data start
## 0xBB Push String / Push Data
Stack Arguments: 0
Inline Arguments: 32 bytes max (NULL terminated)
Push a NULL terminated string to the stack. This opperation will increment the
stack pointer by 32 bytes, always. Even if more than 32 bytes are pushed to
the stack. The code pointer is properly incremented, so long as there is a
NULL byte within 255 bytes of the start of data.
## 0xC1 Jump Switch
Stack Arguments: 1
Inline Arguments: 3+
ArgA
## 0xC5 Equal
If ArgA == ArgB
push 1 to stack
If ArgA != ArgB
push 0 to stack
## 0xC6 Not Equal
Stack Arguments: 2
Stack Result: 1
If ArgA != ArgB
push 1 to stack
If ArgA == ArgB
push 0 to stack
## 0xC7 Less Than
If ArgA < ArgB
push 1 to stack
If ArgA >= ArgB
push 0 to stack
## 0xC8 Less Than or Equal
If ArgA <= ArgB
push 1 to stack
If ArgA > ArgB
push 0 to stack
## 0xC9 Greater Than
If ArgA > ArgB
push 1 to stack
If ArgA <= ArgB
push 0 to stack
## 0xCA Greater Than or Equal To
If ArgA >= ArgB
push 1 to stack
If ArgA < ArgB
push 0 to stack
## 0xCB Add
ArgA = ArgA + ArgB
## 0xCC Subtract
ArgA = ArgA - ArgB
## 0xCD Multiply
ArgA-ArgB = ArgA * ArgB
## 0xCE Signed Divide
ArgA = ArgA / ArgB
## 0xCF Negate
ArgA = 0 - ArgA
## 0xD1 Controller Stuff
Stack Arguments: 2
Inline Arguments: 0
Vars:
Argument_C+0 = Argument_A+0
Argument_A = Word_B1 + Argument_B
Argument_B
# 0xD4 Set Cursor Location
Stack Arguments: 3
Some sort of setup for 0xFE Draw Rom Character.
Byte_0606 = ArgA
Byte_0604 = ArgB
Byte_0605 = ArgC
## 0xE0 Modulo
ArgA = ArgA % ArgB
# 0xE7 Draw Metasprite
Stack Arguments: 7
ArgA sprite ID? (read as byte, but high is used as temp) Some sort of list size or count (header count?)
This is used as a table lookup into a metasprite pointer table at $6980
ArgB (byte) X Coord
ArgC (byte) Y Coord
ArgD (byte?) Palette override. If positive, bottom two bits are used directly for palette index.
ArgE (byte) switch of some sort. sets X to $00 if zero, $20 if not zero
ArgF (byte) Sprite flip. Uses two middle bits of value (`%0001_1000`)
ArgG (byte) Extra args on HW stack??
Header data for metasprites. This location is pointed to by a table at $6980.
Width
Height
Count
Palette??
There is a table at $0140 that keeps track of sprite allocations. Each byte
corresponds to a hardware sprite and the value corresponds to a metasprite that
that hardware sprite is a part of.
# 0xFE Draw Rom Character
Stack Arguments: 4
Inline Arguments: 1 Word
Observed drawing a 1bpp kanji character taken from the SBX ROM charset.
Inline word seems to be an ID or index for the character to draw.

View File

@ -56,12 +56,10 @@ var Instructions []*Instruction = []*Instruction{
&Instruction{ 0x96, 0, 2, 0, true, "set_word_4E"}, &Instruction{ 0x96, 0, 2, 0, true, "set_word_4E"},
&Instruction{ 0x97, 2, 0, 0, false, ""}, &Instruction{ 0x97, 2, 0, 0, false, ""},
&Instruction{ 0x98, 1, 0, 0, false, ""}, &Instruction{ 0x98, 1, 0, 0, false, ""},
&Instruction{ 0x99, 1, 0, 0, false, "enable_audio"}, &Instruction{ 0x99, 1, 0, 0, false, ""},
&Instruction{ 0x9A, 0, 0, 0, false, "disable_audio"}, &Instruction{ 0x9A, 0, 0, 0, false, ""},
&Instruction{ 0x9B, 0, 0, 0, false, "halt"}, &Instruction{ 0x9B, 0, 0, 0, false, "halt"},
&Instruction{ 0x9C, 0, 0, 0, false, "toggle_44FE"}, &Instruction{ 0x9C, 0, 0, 0, false, "toggle_44FE"},
// Waits for data from the tape then jumps into 0x9E code
&Instruction{ 0x9D, 2, 0, 0, false, "something_tape"}, &Instruction{ 0x9D, 2, 0, 0, false, "something_tape"},
// Calls 0xEB draw_overlay. Draws the whole screen from data previously // Calls 0xEB draw_overlay. Draws the whole screen from data previously
@ -107,10 +105,7 @@ var Instructions []*Instruction = []*Instruction{
&Instruction{ 0xA7, 0, 0, 0, false, "call_asm"}, &Instruction{ 0xA7, 0, 0, 0, false, "call_asm"},
&Instruction{ 0xA8, 5, 0, 0, false, ""}, &Instruction{ 0xA8, 5, 0, 0, false, ""},
&Instruction{ 0xA9, 1, 0, 0, false, ""},
// Used when redrawing a portion of the screen after drawing a box or
// some other image on top of the background.
&Instruction{ 0xA9, 1, 0, 0, false, "restore_tiles"},
&Instruction{ 0xAA, 1, 0, 0, false, "long_jump"}, &Instruction{ 0xAA, 1, 0, 0, false, "long_jump"},
&Instruction{ 0xAB, 1, 0, 0, false, "long_call"}, &Instruction{ 0xAB, 1, 0, 0, false, "long_call"},
&Instruction{ 0xAC, 0, 0, 0, false, "long_return"}, &Instruction{ 0xAC, 0, 0, 0, false, "long_return"},
@ -120,10 +115,7 @@ var Instructions []*Instruction = []*Instruction{
&Instruction{ 0xB0, 1, 0, 16, false, "arg_a_to_string"}, &Instruction{ 0xB0, 1, 0, 16, false, "arg_a_to_string"},
&Instruction{ 0xB1, 1, 0, 16, false, "to_hex_string"}, &Instruction{ 0xB1, 1, 0, 16, false, "to_hex_string"},
&Instruction{ 0xB2, 0, 0, 1, false, ""},
// Reads the mic bit on $4016 and puts it on the stack
&Instruction{ 0xB2, 0, 0, 1, false, "read_mic"},
&Instruction{ 0xB3, 7, 0, 0, false, ""}, // possible 16-bit inline? &Instruction{ 0xB3, 7, 0, 0, false, ""}, // possible 16-bit inline?
// If ($471A) is > #$60, copy to ($4E). Setup for some other bullshit? // If ($471A) is > #$60, copy to ($4E). Setup for some other bullshit?
@ -204,7 +196,7 @@ var Instructions []*Instruction = []*Instruction{
&Instruction{ 0xDA, 1, 0, 16, false, "to_int_string"}, &Instruction{ 0xDA, 1, 0, 16, false, "to_int_string"},
&Instruction{ 0xDB, 3, 0, 0, false, ""}, &Instruction{ 0xDB, 3, 0, 0, false, ""},
&Instruction{ 0xDC, 5, 0, 0, false, ""}, // fucks with attribute data &Instruction{ 0xDC, 5, 0, 0, false, ""},
// ArgA, ArgB: X,Y of corner A // ArgA, ArgB: X,Y of corner A
// ArgC, ArgD: X,Y of corner B // ArgC, ArgD: X,Y of corner B
@ -214,13 +206,7 @@ var Instructions []*Instruction = []*Instruction{
&Instruction{ 0xDD, 5, 0, 0, false, "fill_box"}, &Instruction{ 0xDD, 5, 0, 0, false, "fill_box"},
&Instruction{ 0xDE, 3, 0, 0, false, ""}, &Instruction{ 0xDE, 3, 0, 0, false, ""},
&Instruction{ 0xDF, 3, 0, 0, false, ""},
// ArgA: ??
// ArgB: X (tile coords)
// ArgC: Y (tile coords)
// Draws an image on top of the background using data already
// loaded from *somewhere*. (seen using data from tape)
&Instruction{ 0xDF, 3, 0, 0, false, "draw_image"},
// Divide and return remainder // Divide and return remainder
&Instruction{ 0xE0, 2, 0, 1, false, "modulo"}, &Instruction{ 0xE0, 2, 0, 1, false, "modulo"},