Rework Rom struct; Print more info

- Reworked the Rom struct to make more sense in an emulation
  environment.  Currently, cannot convert this to a .fds file, but maybe
  later.
- Print info using the reworked Rom struct.  More info is printed.
This commit is contained in:
Zorchenhimer 2025-12-07 20:13:29 -05:00
parent 93c056db49
commit 4ab973c6b2
Signed by: Zorchenhimer
GPG Key ID: 70A1AB767AAB9C20
5 changed files with 334 additions and 39 deletions

View File

@ -61,7 +61,7 @@ type BlockInfo struct {
Unknown_18 uint8 // unknown; 0x00 in all known games
BoodReadFileCode uint8
BootReadFileCode uint8
Unknown_1A [5]byte // unknown; 0xFF x5

View File

@ -37,42 +37,18 @@ func run(args *Arguments) error {
}
defer file.Close()
//reader := bufio.NewReader(file)
//magic, err := reader.Peek(4)
//if err != nil {
// return fmt.Errorf("Unable to peek header: %w", err)
//}
//header := &fds.FdsHeader{}
//// "FDS" + EOF. header may be omitted.
//if bytes.Equal(magic, []byte{0x46, 0x44, 0x53, 0x1A}) {
// err = binary.Read(reader, binary.LittleEndian, header)
// if err != nil {
// return fmt.Errorf("Error reading header: %w", err)
// }
//}
//if header != nil {
// fmt.Println("Header found. sides:", header.Sides)
//} else {
// fmt.Println("No header")
//}
//blocks, err := fds.ReadBlocks(reader, true)
//if err != nil {
// return err
//}
rom, err := fds.ReadRom(file, filepath.Ext(args.Input) == ".fds")
blocks, err := fds.ReadRom(file, filepath.Ext(args.Input) == ".fds")
if err != nil {
return err
}
for _, block := range rom.Blocks {
fmt.Println(block)
rom, err := fds.RomFromBlocks(filepath.Base(args.Input), blocks)
if err != nil {
return err
}
fmt.Println(rom.Info())
return nil
}

53
date.go Normal file
View File

@ -0,0 +1,53 @@
package fds
import (
"fmt"
)
type FdsDate struct {
Year int
Month int
Day int
}
func NewFdsDate(raw []byte) (*FdsDate, error) {
if len(raw) != 3 {
return nil, fmt.Errorf("Must be three bytes")
}
if raw[1] == 0x00 || raw[1] > 0x12 || (raw[1] > 0x09 && raw[1] < 0x10) {
return nil, fmt.Errorf("Invalid month: %02X", raw[1])
}
if raw[2] == 0x00 || raw[2] > 0x31 ||
(raw[2] > 0x09 && raw[2] < 0x10) ||
(raw[2] > 0x19 && raw[2] < 0x20) ||
(raw[2] > 0x29 && raw[2] < 0x30) {
return nil, fmt.Errorf("Invalid day: %02X", raw[1])
}
return &FdsDate{
Year: bcdToDec(raw[0]),
Month: bcdToDec(raw[1]),
Day: bcdToDec(raw[2]),
}, nil
}
func bcdToDec(val byte) int {
v := int(val)
return (v>>4)*10 + (v&0x0F)
}
func (date *FdsDate) String() string {
return fmt.Sprintf("Years: %04d/%04d/%04d Month: %d Day: %d",
date.Year+1926,
date.Year+1989,
date.Year+1900,
date.Month,
date.Day,
)
}
//func (date *FdsDate) Raw() []byte {
//}

View File

@ -11,25 +11,22 @@ import (
"github.com/sigurn/crc16"
)
func ReadRom(r io.Reader, IsFds bool) (*Rom, error) {
func ReadRom(r io.Reader, IsFds bool) ([]DiskBlock, error) {
reader := bufio.NewReader(r)
magic, err := reader.Peek(4)
if err != nil {
return nil, fmt.Errorf("Unable to peek header: %w", err)
}
rom := &Rom{Header: &FdsHeader{}}
// "FDS" + EOF. header may be omitted.
if bytes.Equal(magic, FdsHeaderMagic) {
err = binary.Read(reader, binary.LittleEndian, rom.Header)
_, err = reader.Discard(binary.Size(&FdsHeader{}))
if err != nil {
return nil, fmt.Errorf("Error reading header: %w", err)
}
}
rom.Blocks, err = ReadBlocks(reader, IsFds)
return rom, err
return ReadBlocks(reader, IsFds)
}
func ReadBlocks(r io.Reader, IsFds bool) ([]DiskBlock, error) {

273
rom.go
View File

@ -1,8 +1,264 @@
package fds
import (
"strings"
"fmt"
"github.com/sigurn/crc16"
)
type Rom struct {
Header *FdsHeader
Blocks []DiskBlock
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 {
@ -12,3 +268,16 @@ type FdsHeader struct {
}
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()
}