go-fds/rom.go

284 lines
5.6 KiB
Go

package fds
import (
"strings"
"fmt"
"github.com/sigurn/crc16"
)
type Rom struct {
Name string // filename?
Sides []*RomSide
}
func (rom *Rom) Info() string {
builder := &strings.Builder{}
fmt.Fprintf(builder, "Name: %s", rom.Name)
//sides := []string{}
for _, side := range rom.Sides {
builder.WriteString("\n\n")
builder.WriteString(side.Info())
}
return builder.String()
}
// In the event that a disk has two headers on one physical side of disk,
// split the side into two.
type RomSide struct {
Header *RomHeader
FileCount int // more than this number are "hidden" files
Files []*RomFile
}
func (side *RomSide) Info() string {
builder := &strings.Builder{}
fmt.Fprintln(builder, side.Header.Info())
fmt.Fprintf(builder, "FileCount: listed:%d total:%d\n", side.FileCount, len(side.Files))
sideAB := "?"
if side.Header.PhysicalSide == 0x00 {
sideAB = "A"
} else if side.Header.PhysicalSide == 0x01 {
sideAB = "B"
}
fmt.Fprintf(builder, "Side%s\n", sideAB)
files := []string{}
for _, file := range side.Files {
files = append(files, file.Info())
}
builder.WriteString(strings.Join(files, "\n"))
return builder.String()
}
type RomHeader struct {
Licensee uint8
GameName string
GameType uint8
GameVersion int
SideNumber int
DiskNumber int
DiskType uint8
BootFileId int
ManufacturingDate *FdsDate
RewrittenDate *FdsDate
CountryCode uint8 // 0x49 = japan
DiskWriterSerial uint16
RewriteCount int
PhysicalSide uint8
DiskTypeOther uint8
DiskVersion uint8
}
func (header *RomHeader) Info() string {
return fmt.Sprintf("Name: %3s Licensee: $%02X - %s\nMFG Date: %s",
header.GameName,
header.Licensee,
LicenseeEnName(header.Licensee),
header.ManufacturingDate,
)
}
func RomFromBlocks(name string, blocks []DiskBlock) (*Rom, error) {
rom := &Rom{
Name: name,
Sides: []*RomSide{},
}
var side *RomSide
var file *RomFile
for _, block := range blocks {
switch block.Type() {
case BtInfo:
if file != nil {
return nil, fmt.Errorf("Expected BlockFileData, found BlockInfo")
}
blk := block.(*BlockInfo)
side = &RomSide{
Header: &RomHeader{
Licensee: blk.Licensee,
GameName: string(blk.GameName[:]),
GameType: blk.GameType,
GameVersion: int(blk.GameVersion),
SideNumber: int(blk.SideNumber),
DiskNumber: int(blk.DiskNumber),
DiskType: blk.DiskType,
BootFileId: int(blk.BootReadFileCode),
ManufacturingDate: &FdsDate{
Year: bcdToDec(blk.ManufacturingDate[0]),
Month: bcdToDec(blk.ManufacturingDate[1]),
Day: bcdToDec(blk.ManufacturingDate[2]),
},
RewrittenDate: &FdsDate{
Year: bcdToDec(blk.RewrittenDate[0]),
Month: bcdToDec(blk.RewrittenDate[1]),
Day: bcdToDec(blk.RewrittenDate[2]),
},
CountryCode: blk.CountryCode,
DiskWriterSerial: blk.DiskWriterSerial,
RewriteCount: bcdToDec(blk.RewriteCount),
PhysicalSide: blk.ActualDiskSide,
DiskTypeOther: blk.DiskTypeOther,
DiskVersion: blk.DiskVersion,
},
Files: []*RomFile{},
}
rom.Sides = append(rom.Sides, side)
case BtFileAmount:
if side == nil {
return nil, fmt.Errorf("Expected BlockInfo, found BlockFileAmount")
}
side.FileCount = int((block.(*BlockFileAmount)).FileAmount)
case BtFileHeader:
if side == nil {
return nil, fmt.Errorf("Expected BlockInfo, found BlockFileHeader")
}
if file != nil {
return nil, fmt.Errorf("Expected BlockFileData, found BlockFileHeader")
}
blk := block.(*BlockFileHeader)
file = &RomFile{
Name: BytesToAscii(blk.FileName[:]),
Number: int(blk.FileNumber),
Id: int(blk.FileId),
Type: RomFileType(blk.FileType),
Size: int(blk.FileSize),
Address: blk.FileAddress,
Hidden: (int(blk.FileNumber) >= side.FileCount),
}
side.Files = append(side.Files, file)
case BtFileData:
if side == nil {
return nil, fmt.Errorf("Expected BlockInfo, found BlockFileData")
}
if file == nil {
return nil, fmt.Errorf("Expected BlockFileHeader, found BlockFileData")
}
blk := block.(*BlockFileData)
file.Data = blk.Data
file.CRC = blk.CRC
file = nil
}
}
return rom, nil
}
type RomFileType int
const (
FT_Program RomFileType = iota
FT_Character
FT_Nametable
)
func (ft RomFileType) String() string {
switch ft {
case FT_Program:
return "FT_Program"
case FT_Character:
return "FT_Character"
case FT_Nametable:
return "FT_Nametable"
}
return "UNKNOWN"
}
func (ft RomFileType) Str() string {
switch ft {
case FT_Program:
return "PRG"
case FT_Character:
return "CHR"
case FT_Nametable:
return "NT "
}
return "UNK"
}
type RomFile struct {
Name string
Number int
Id int
Type RomFileType
Size int
Address uint16
Hidden bool
Data []byte
CRC uint16
}
func (file *RomFile) Info() string {
crcStatus := "PASS"
// block type is included in the CRC
crc := crc16.Checksum(append([]byte{0x04}, file.Data...), crc16.MakeTable(crc16.CRC16_KERMIT))
if crc != file.CRC {
crcStatus = fmt.Sprintf("FAIL [$%04X]", crc)
}
return fmt.Sprintf("%02d $%02X %q %s %5d $%04X-$%04X Hidden:%t CRC:$%04X (%s)",
file.Number,
file.Id,
file.Name,
file.Type.Str(),
file.Size,
file.Address,
file.Address+uint16(file.Size),
file.Hidden,
file.CRC,
crcStatus,
)
}
type FdsHeader struct {
Magic [4]byte
Sides uint8
_ [11]byte // padding
}
var FdsHeaderMagic []byte = []byte{0x46, 0x44, 0x53, 0x1A}
func BytesToAscii(input []byte) string {
builder := &strings.Builder{}
for _, b := range input {
if b >= ' ' && b <= '~' {
builder.WriteByte(b)
} else {
builder.WriteString(fmt.Sprintf("\\x%02X", b))
}
}
return builder.String()
}