From 4ab973c6b274cc0b099d1229d5e18a2b8e14fc19 Mon Sep 17 00:00:00 2001 From: Zorchenhimer Date: Sun, 7 Dec 2025 20:13:29 -0500 Subject: [PATCH] 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. --- blocks.go | 2 +- cmd/fdslist.go | 36 ++----- date.go | 53 ++++++++++ reader.go | 9 +- rom.go | 273 ++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 334 insertions(+), 39 deletions(-) create mode 100644 date.go diff --git a/blocks.go b/blocks.go index 211a609..0a79104 100644 --- a/blocks.go +++ b/blocks.go @@ -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 diff --git a/cmd/fdslist.go b/cmd/fdslist.go index c68d198..5c8b610 100644 --- a/cmd/fdslist.go +++ b/cmd/fdslist.go @@ -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 } diff --git a/date.go b/date.go new file mode 100644 index 0000000..0e47ae8 --- /dev/null +++ b/date.go @@ -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 { +//} diff --git a/reader.go b/reader.go index ef38909..d4446fd 100644 --- a/reader.go +++ b/reader.go @@ -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) { diff --git a/rom.go b/rom.go index 977e962..3b06d96 100644 --- a/rom.go +++ b/rom.go @@ -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() +}