284 lines
5.6 KiB
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()
|
|
}
|