Initial commit

Bare-bones functionality so far.  Lists blocks in a rom with minimal
information.
This commit is contained in:
Zorchenhimer 2025-12-06 14:15:08 -05:00
commit 93c056db49
Signed by: Zorchenhimer
GPG Key ID: 70A1AB767AAB9C20
9 changed files with 1140 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin/
*.fds

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
CMDS= \
bin/fdslist
all: bin $(CMDS)
bin:
mkdir bin
bin/%: cmd/%.go *.go
go build -o $@ $<

194
blocks.go Normal file
View File

@ -0,0 +1,194 @@
package fds
import (
"fmt"
)
type BlockType byte
const (
BtInfo BlockType = 0x01
BtFileAmount BlockType = 0x02
BtFileHeader BlockType = 0x03
BtFileData BlockType = 0x04
)
func (bt BlockType) String() string {
switch bt {
case BtInfo:
return "BlockInfo"
case BtFileAmount:
return "BlockAmount"
case BtFileHeader:
return "BlockFileHeader"
case BtFileData:
return "BlockFileData"
}
return "Unknown block"
}
type DiskBlock interface {
Type() BlockType
String() string
}
// Block 1
type BlockInfo struct {
// always 0x01
BlockCode byte
// literal ASCII: "*NINTENDO-HVC*"
DiskVerification [14]byte
Licensee uint8
// 3 byte ASCII code
GameName [3]byte
// 0x20: " " normal disk
// 0x45: "E" Event
// 0x4A: "J" Unknown
// 0x52: "R" Reduction in price via advert (lmao what?)
GameType uint8
GameVersion uint8
SideNumber uint8
DiskNumber uint8
// 0x01 for FMC blue-disk
// 0x00 otherwise
DiskType uint8
Unknown_18 uint8 // unknown; 0x00 in all known games
BoodReadFileCode uint8
Unknown_1A [5]byte // unknown; 0xFF x5
// BCD YMD, but 1-indexed from Japanese calendar era (fuck you, lmao).
ManufacturingDate [3]byte
CountryCode uint8 // 0x49 = japan
Unknown_23 uint8 // region code?
Unknown_24 uint8 // location/site?
Unknown_25 [2]byte // raw: 0x00 0x22
Unknown_27 [5]byte // game info representation?
// BCD YMD (same as above). Disk Writer kiosk write date?
RewrittenDate [3]byte
Unknown_2F uint8
Unknown_30 uint8 // raw: 0x80
DiskWriterSerial uint16
Unknown_33 uint8 // raw: 0x07
// BCD format. 0x00 == original
RewriteCount byte
// 0x00 = SideA; 0x01 = SideB
ActualDiskSide uint8
// 0x00 yellow
// 0xFF blue
// 0xFE prototype/internal (white or pink)
DiskTypeOther uint8
DiskVersion uint8
// CRC-16/KERMIT. apparently this will work: github.com/sigurn/crc16
CRC uint16 // omitted from FDS files (fucking whyyyyyyy)
}
func (b *BlockInfo) Type() BlockType {
return BtInfo
}
func (b *BlockInfo) String() string {
return fmt.Sprintf("FdsBlockInfo Licensee:%02X GameName:%q GameType:%c",
b.Licensee,
b.GameName,
b.GameType,
)
}
const Template_FdsBlockInfo string = `Licensee: {{.Licensee}}
{{.LicEn}}
{{.LicJp}}
Name: {{.GameName}} Version: {{.GameVersion}} Type: {{.GameType}}`
func (b *BlockInfo) Info() string {
return "yup"
}
//func (b *BlockInfo) Info() string {
// return fmt.Sprintf("Licensee:%02X\n %s\n %s\nGameName: %s",
// b.Licensee,
// LicenseeEnName(b.Licensee),
// LicenseeJpName(b.Licensee),
// )
//}
type BlockFileAmount struct {
BlockCode byte // raw: 0x02
FileAmount uint8
CRC uint16 // omitted from FDS files (fucking whyyyyyyy)
}
func (b *BlockFileAmount) Type() BlockType {
return BtFileAmount
}
func (b *BlockFileAmount) String() string {
return fmt.Sprintf("BlockFileAmount FileAmount:%d", b.FileAmount)
}
type BlockFileHeader struct {
BlockCode byte // raw: 0x03
FileNumber uint8
FileId uint8
FileName [8]byte
FileAddress uint16 // destination when loading
FileSize uint16
// 0x00: Program (PRAM)
// 0x01: Character (CRAM)
// 0x02: Nametable (VRAM)
FileType uint8
CRC uint16 // omitted from FDS files (fucking whyyyyyyy)
}
func (b *BlockFileHeader) Type() BlockType {
return BtFileHeader
}
func (b *BlockFileHeader) String() string {
return fmt.Sprintf("BlockFileHeader FileNumber:%d FileId:%d FileName:%q FileAddress:$%04X FileSize:%d FileType:%d",
b.FileNumber,
b.FileId,
string(b.FileName[:]),
b.FileAddress,
b.FileSize,
b.FileType,
)
}
type BlockFileData struct {
BlockCode byte // raw: 0x04
// Disk data (FdsFileHeader.FileSize length)
Data []byte
CRC uint16 // omitted from FDS files (fucking whyyyyyyy)
}
func (b *BlockFileData) Type() BlockType {
return BtFileData
}
func (b *BlockFileData) String() string {
return "BlockFileData"
}

79
cmd/fdslist.go Normal file
View File

@ -0,0 +1,79 @@
package main
import (
"fmt"
"os"
//"encoding/binary"
//"bufio"
//"bytes"
//"io"
"path/filepath"
"github.com/alexflint/go-arg"
"git.zorchenhimer.com/go-fds"
)
type Arguments struct {
Input string `arg:"positional,required"`
}
func main() {
args := &Arguments{}
arg.MustParse(args)
err := run(args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(args *Arguments) error {
file, err := os.Open(args.Input)
if err != nil {
return err
}
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")
if err != nil {
return err
}
for _, block := range rom.Blocks {
fmt.Println(block)
}
return nil
}

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module git.zorchenhimer.com/go-fds
go 1.25.4
require (
github.com/alexflint/go-arg v1.6.0
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
)
require github.com/alexflint/go-scalar v1.2.0 // indirect

15
go.sum Normal file
View File

@ -0,0 +1,15 @@
github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo=
github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I=
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

599
licensees.go Normal file
View File

@ -0,0 +1,599 @@
package fds
func LicenseeEnName(id byte) string {
lic, ok := Licensees[id]
if !ok {
return "????"
}
return lic.English
}
func LicenseeJpName(id byte) string {
lic, ok := Licensees[id]
if !ok {
return "????"
}
return lic.Japanese
}
type Licensee struct {
Id byte
English string
Japanese string
}
var Licensees = map[byte]Licensee{
0x00: Licensee{ Id:0x00,
English:"<unlicensed>",
Japanese:"<非公認>" },
0x01: Licensee{ Id:0x01,
English:"Nintendo",
Japanese:"任天堂" },
0x07: Licensee{ Id:0x07,
English:"Nomura Securities? (unverified)",
Japanese:"野村證券? (未確認)" },
0x08: Licensee{ Id:0x08,
English:"Capcom",
Japanese:"カプコン" },
0x09: Licensee{ Id:0x09,
English:"Hot-B",
Japanese:"ホット・ビィ" },
0x0A: Licensee{ Id:0x0A,
English:"Jaleco",
Japanese:"ジャレコ" },
0x0B: Licensee{ Id:0x0B,
English:"Coconuts Japan Entertainment",
Japanese:"ココナッツジャパンエンターテイメント" },
0x13: Licensee{ Id:0x13,
English:"Electronic Arts (Japan)",
Japanese:"エレクトロニック・アーツ (日本)" },
0x18: Licensee{ Id:0x18,
English:"Hudson Soft",
Japanese:"ハドソン" },
0x21: Licensee{ Id:0x21,
English:"Tokai Engineering",
Japanese:"東海エンジニアリング" },
0x28: Licensee{ Id:0x28,
English:"Kemco (Japan)",
Japanese:"ケムコ (日本)" },
0x29: Licensee{ Id:0x29,
English:"SETA (Japan)",
Japanese:"セタ (日本)" },
0x2B: Licensee{ Id:0x2B,
English:"Tamtex",
Japanese:"タムテックス" },
0x35: Licensee{ Id:0x35,
English:"Hector Playing Interface (Hect)",
Japanese:"ヘクト" },
0x3D: Licensee{ Id:0x3D,
English:"Loriciel",
Japanese:"ロリシエル" },
0x3E: Licensee{ Id:0x3E,
English:"Gremlin",
Japanese:"グレムリン" },
0x40: Licensee{ Id:0x40,
English:"Seika Corporation",
Japanese:"セイカ" },
0x41: Licensee{ Id:0x41,
English:"Ubisoft",
Japanese:"ウビソフト" },
0x46: Licensee{ Id:0x46,
English:"System 3",
Japanese:"システム 3" },
0x49: Licensee{ Id:0x49,
English:"Irem",
Japanese:"アイレム" },
0x4A: Licensee{ Id:0x4A,
English:"Gakken",
Japanese:"学習研究社" },
0x50: Licensee{ Id:0x50,
English:"Absolute",
Japanese:"アブソールト" },
0x51: Licensee{ Id:0x51,
English:"Acclaim (NA)",
Japanese:"アクレイム (北米)" },
0x52: Licensee{ Id:0x52,
English:"Activision",
Japanese:"アクティビシォン" },
0x53: Licensee{ Id:0x53,
English:"American Sammy",
Japanese:"アメリカン サミー" },
0x54: Licensee{ Id:0x54,
English:"GameTek",
Japanese:"ガメテック" },
0x55: Licensee{ Id:0x55,
English:"Hi Tech Expressions",
Japanese:"ハイ テック エクスープレシオンス" },
0x56: Licensee{ Id:0x56,
English:"LJN",
Japanese:"LJN" },
0x57: Licensee{ Id:0x57,
English:"Matchbox Toys",
Japanese:"マッチョボックス トイズ" },
0x58: Licensee{ Id:0x58,
English:"Mattel",
Japanese:"マッテル" },
0x59: Licensee{ Id:0x59,
English:"Milton Bradley",
Japanese:"ミルトン ブラッドリー" },
0x5A: Licensee{ Id:0x5A,
English:"Mindscape / Software Toolworks",
Japanese:"マインドスケップ / ソフトワー トゥールウアクス" },
0x5B: Licensee{ Id:0x5B,
English:"SETA (NA)",
Japanese:"セタ (北米)" },
0x5C: Licensee{ Id:0x5C,
English:"Taxan",
Japanese:"タクサン" },
0x5D: Licensee{ Id:0x5D,
English:"Tradewest",
Japanese:"トレードワェスト" },
0x5E: Licensee{ Id:0x5E,
English:"INTV Corporation",
Japanese:"INTV" },
0x60: Licensee{ Id:0x60,
English:"Titus",
Japanese:"タイタス" },
0x61: Licensee{ Id:0x61,
English:"Virgin Games",
Japanese:"ヴァージン" },
0x67: Licensee{ Id:0x67,
English:"Ocean",
Japanese:"オシャン" },
0x69: Licensee{ Id:0x69,
English:"Electronic Arts (NA)",
Japanese:"エレクトロニック・アーツ (北米)" },
0x6B: Licensee{ Id:0x6B,
English:"Beam Software",
Japanese:"ビーム" },
0x6E: Licensee{ Id:0x6E,
English:"Elite Systems",
Japanese:"エリット" },
0x6F: Licensee{ Id:0x6F,
English:"Electro Brain",
Japanese:"エレクトロ ブレイン" },
0x70: Licensee{ Id:0x70,
English:"Infogrames",
Japanese:"インフォグレムス" },
0x72: Licensee{ Id:0x72,
English:"JVC",
Japanese:"JVC" },
0x73: Licensee{ Id:0x73,
English:"Parker Brothers",
Japanese:"パーカー ブロザーズ" },
0x75: Licensee{ Id:0x75,
English:"The Sales Curve / SCi",
Japanese:"ザ・セルズ・カーヴ / SCi" },
0x78: Licensee{ Id:0x78,
English:"THQ",
Japanese:"THQ" },
0x79: Licensee{ Id:0x79,
English:"Accolade",
Japanese:"アッコレド" },
0x7A: Licensee{ Id:0x7A,
English:"Triffix",
Japanese:"トリフィクス" },
0x7C: Licensee{ Id:0x7C,
English:"Microprose Software",
Japanese:"マイクロプロス" },
0x7F: Licensee{ Id:0x7F,
English:"Kemco (NA)",
Japanese:"ケムコ (北米)" },
0x80: Licensee{ Id:0x80,
English:"Misawa Entertainment",
Japanese:"ミサワエンターテインメント" },
0x83: Licensee{ Id:0x83,
English:"G. Amusements Co.",
Japanese:"ジー・アミューズメント" },
0x85: Licensee{ Id:0x85,
English:"G.O 1",
Japanese:"ジー・オー・ワン" },
0x86: Licensee{ Id:0x86,
English:"Tokuma Shoten Intermedia",
Japanese:"徳間書店インターメディア" },
0x89: Licensee{ Id:0x89,
English:"Nihon Maicom Kaihatsu (NMK)",
Japanese:"NMK" },
0x8B: Licensee{ Id:0x8B,
English:"BulletProof Software (BPS)",
Japanese:"BPS" },
0x8C: Licensee{ Id:0x8C,
English:"VIC Tokai",
Japanese:"ビック東海" },
0x8D: Licensee{ Id:0x8D,
English:"Sanritsu",
Japanese:"サンリツ" },
0x8E: Licensee{ Id:0x8E,
English:"Character Soft",
Japanese:"キャラクターソフト" },
0x8F: Licensee{ Id:0x8F,
English:"I'Max",
Japanese:"アイマックス" },
0x94: Licensee{ Id:0x94,
English:"Toaplan",
Japanese:"株式会社東亜プラン" },
0x95: Licensee{ Id:0x95,
English:"Varie",
Japanese:"バリエ" },
0x96: Licensee{ Id:0x96,
English:"Yonezawa Party Room 21 / S'Pal",
Japanese:"ヨネザワPR21 / エスパル" },
0x99: Licensee{ Id:0x99,
English:"Pack-In-Video",
Japanese:"パックインビデオ" },
0x9A: Licensee{ Id:0x9A,
English:"Nihon Bussan",
Japanese:"日本物産" },
0x9B: Licensee{ Id:0x9B,
English:"Tecmo",
Japanese:"テクモ" },
0x9C: Licensee{ Id:0x9C,
English:"Imagineer",
Japanese:"イマジニア" },
0x9E: Licensee{ Id:0x9E,
English:"Face",
Japanese:"フェイス" },
0xA2: Licensee{ Id:0xA2,
English:"Scorpion Soft",
Japanese:"スコーピオンソフト" },
0xA3: Licensee{ Id:0xA3,
English:"Broderbund",
Japanese:"ブロダーブンド" },
0xA4: Licensee{ Id:0xA4,
English:"Konami",
Japanese:"コナミ" },
0xA5: Licensee{ Id:0xA5,
English:"K. Amusement Leasing Co. (KAC)",
Japanese:"ケイ・アミューズメントリース" },
0xA6: Licensee{ Id:0xA6,
English:"Kawada Co., Ltd.",
Japanese:"河田" },
0xA7: Licensee{ Id:0xA7,
English:"Takara",
Japanese:"タカラ" },
0xA8: Licensee{ Id:0xA8,
English:"Royal Industries",
Japanese:"ロイヤル工業" },
0xA9: Licensee{ Id:0xA9,
English:"Tecnos",
Japanese:"テクノス" },
0xAA: Licensee{ Id:0xAA,
English:"Victor Musical Industries",
Japanese:"ビクター音楽産業" },
0xAB: Licensee{ Id:0xAB,
English:"Hi-Score Media Work",
Japanese:"ハイスコアメディアワーク" },
0xAC: Licensee{ Id:0xAC,
English:"Toei Animation",
Japanese:"東映動画" },
0xAD: Licensee{ Id:0xAD,
English:"Toho (Japan)",
Japanese:"東宝 (日本)" },
0xAE: Licensee{ Id:0xAE,
English:"TSS",
Japanese:"ティーエスエス" },
0xAF: Licensee{ Id:0xAF,
English:"Namco",
Japanese:"ナムコ" },
0xB0: Licensee{ Id:0xB0,
English:"Acclaim (Japan)",
Japanese:"アクレイム" },
0xB1: Licensee{ Id:0xB1,
English:"ASCII Corporation / Nexoft",
Japanese:"アスキー / Nexoft" },
0xB2: Licensee{ Id:0xB2,
English:"Bandai",
Japanese:"バンダイ" },
0xB3: Licensee{ Id:0xB3,
English:"Soft Pro Inc.",
Japanese:"ソフトプロ" },
0xB4: Licensee{ Id:0xB4,
English:"Enix",
Japanese:"エニックス" },
0xB5: Licensee{ Id:0xB5,
English:"dB-SOFT",
Japanese:"デービーソフト" },
0xB6: Licensee{ Id:0xB6,
English:"HAL Laboratory",
Japanese:"HAL研究所" },
0xB7: Licensee{ Id:0xB7,
English:"SNK",
Japanese:"SNK" },
0xB9: Licensee{ Id:0xB9,
English:"Pony Canyon",
Japanese:"ポニーキャニオン" },
0xBA: Licensee{ Id:0xBA,
English:"Culture Brain",
Japanese:"カルチャーブレーン" },
0xBB: Licensee{ Id:0xBB,
English:"Sunsoft",
Japanese:"サンソフト" },
0xBC: Licensee{ Id:0xBC,
English:"Toshiba EMI",
Japanese:"東芝EMI" },
0xBD: Licensee{ Id:0xBD,
English:"CBS/Sony Group",
Japanese:"CBS・ソニー" },
0xBF: Licensee{ Id:0xBF,
English:"Sammy Corporation",
Japanese:"サミー" },
0xC0: Licensee{ Id:0xC0,
English:"Taito",
Japanese:"タイトー" },
0xC1: Licensee{ Id:0xC1,
English:"Sunsoft / Ask Co., Ltd.",
Japanese:"サンソフト / アスク講談社" },
0xC2: Licensee{ Id:0xC2,
English:"Kemco",
Japanese:"ケムコ" },
0xC3: Licensee{ Id:0xC3,
English:"Square / Disk Original Group (DOG)",
Japanese:"スクウェア / ディスク・オリジナル・グループ" },
0xC4: Licensee{ Id:0xC4,
English:"Tokuma Shoten",
Japanese:"徳間書店" },
0xC5: Licensee{ Id:0xC5,
English:"Data East",
Japanese:"データイースト" },
0xC6: Licensee{ Id:0xC6,
English:"Tonkin House / Tokyo Shoseki",
Japanese:"トンキンハウス / 東京書籍" },
0xC7: Licensee{ Id:0xC7,
English:"East Cube / Toho (NA)",
Japanese:"イーストキューブ / 東宝 (北米)" },
0xC8: Licensee{ Id:0xC8,
English:"Koei",
Japanese:"コーエー" },
0xC9: Licensee{ Id:0xC9,
English:"UPL",
Japanese:"ユーピーエル" },
0xCA: Licensee{ Id:0xCA,
English:"Konami / Ultra / Palcom",
Japanese:"コナミ" },
0xCB: Licensee{ Id:0xCB,
English:"NTVIC / VAP",
Japanese:"バップ" },
0xCC: Licensee{ Id:0xCC,
English:"Use Co., Ltd.",
Japanese:"ユース" },
0xCD: Licensee{ Id:0xCD,
English:"Meldac",
Japanese:"メルダック" },
0xCE: Licensee{ Id:0xCE,
English:"Pony Canyon / FCI",
Japanese:"ポニーキャニオン" },
0xCF: Licensee{ Id:0xCF,
English:"Angel",
Japanese:"エンジェル" },
0xD0: Licensee{ Id:0xD0,
English:"Disco",
Japanese:"ディスコ" },
0xD1: Licensee{ Id:0xD1,
English:"Sofel",
Japanese:"ソフエル" },
0xD2: Licensee{ Id:0xD2,
English:"Bothtec, Inc. / Quest",
Japanese:"ボーステック / クエスト" },
0xD3: Licensee{ Id:0xD3,
English:"Sigma Enterprises",
Japanese:"シグマ商事" },
0xD4: Licensee{ Id:0xD4,
English:"Ask Corp.",
Japanese:"アスク講談社" },
0xD5: Licensee{ Id:0xD5,
English:"Kyugo Trading Co.",
Japanese:"九娯貿易" },
0xD6: Licensee{ Id:0xD6,
English:"Naxat Soft / Kaga Tech",
Japanese:"ナグザットソフト / 加賀テック" },
0xD8: Licensee{ Id:0xD8,
English:"Status",
Japanese:"ステイタス" },
0xD9: Licensee{ Id:0xD9,
English:"Banpresto",
Japanese:"バンプレスト" },
0xDA: Licensee{ Id:0xDA,
English:"Tomy",
Japanese:"トミー" },
0xDB: Licensee{ Id:0xDB,
English:"Hiro Co., Ltd.",
Japanese:"ヒロ" },
0xDD: Licensee{ Id:0xDD,
English:"Nippon Computer Systems (NCS) / Masaya Games",
Japanese:"日本コンピュータシステム / メサイヤゲームス" },
0xDE: Licensee{ Id:0xDE,
English:"Human Creative",
Japanese:"ヒューマン" },
0xDF: Licensee{ Id:0xDF,
English:"Altron",
Japanese:"アルトロン" },
0xE0: Licensee{ Id:0xE0,
English:"K.K. DCE",
Japanese:"K.K. DCE" },
0xE1: Licensee{ Id:0xE1,
English:"Towa Chiki",
Japanese:"トーワチキ" },
0xE2: Licensee{ Id:0xE2,
English:"Yutaka",
Japanese:"ユタカ" },
0xE3: Licensee{ Id:0xE3,
English:"Kaken Corporation",
Japanese:"科学技研株式会社" },
0xE5: Licensee{ Id:0xE5,
English:"Epoch",
Japanese:"エポック" },
0xE7: Licensee{ Id:0xE7,
English:"Athena",
Japanese:"アテナ" },
0xE8: Licensee{ Id:0xE8,
English:"Asmik",
Japanese:"アスミック" },
0xE9: Licensee{ Id:0xE9,
English:"Natsume",
Japanese:"ナツメ" },
0xEA: Licensee{ Id:0xEA,
English:"King Records",
Japanese:"キングレコード" },
0xEB: Licensee{ Id:0xEB,
English:"Atlus",
Japanese:"アトラス" },
0xEC: Licensee{ Id:0xEC,
English:"Sony Music Entertainment",
Japanese:"ソニー・ミュージックエンタテインメント" },
0xED: Licensee{ Id:0xED,
English:"Pixel Corporation",
Japanese:"ピクセル" },
0xEE: Licensee{ Id:0xEE,
English:"Information Global Service (IGS)",
Japanese:"アイ・ジー・エス" },
0xEF: Licensee{ Id:0xEF,
English:"Fujimic",
Japanese:"フジミック" },
0xF0: Licensee{ Id:0xF0,
English:"A-Wave",
Japanese:"エイ・ウェーブ" },
}

216
reader.go Normal file
View File

@ -0,0 +1,216 @@
package fds
import (
//"os"
"fmt"
"io"
"bufio"
"bytes"
"encoding/binary"
"github.com/sigurn/crc16"
)
func ReadRom(r io.Reader, IsFds bool) (*Rom, 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)
if err != nil {
return nil, fmt.Errorf("Error reading header: %w", err)
}
}
rom.Blocks, err = ReadBlocks(reader, IsFds)
return rom, err
}
func ReadBlocks(r io.Reader, IsFds bool) ([]DiskBlock, error) {
blocks := []DiskBlock{}
dataSize := -1
readBytes := 0
reader := bufio.NewReader(r)
for {
raw, err := reader.Peek(1)
if err != nil {
if err == io.EOF {
break
}
return blocks, fmt.Errorf("error peeking block type: %w", err)
}
var readLen int
var block DiskBlock
idByte := raw[0]
switch idByte {
case 0x01:
block = &BlockInfo{}
case 0x02:
block = &BlockFileAmount{}
case 0x03:
block = &BlockFileHeader{}
case 0x04:
block = &BlockFileData{}
// variable length
readLen = dataSize+3
case 0x00:
reader.Discard(1)
continue
default:
fmt.Printf("readBytes: %d ($%04X)\n", readBytes, readBytes)
return blocks, fmt.Errorf("Invalid block type: $%02X", idByte)
}
//fmt.Println("")
if idByte != 0x04 {
readLen = binary.Size(block)
}
//fmt.Println("readLen:;", readLen)
// FDS doesn't have the two byte CRC in the data
if IsFds {
readLen -=2
}
//fmt.Println("readLen::", readLen)
buf := make([]byte, readLen)
n, err := io.ReadFull(reader, buf)
if err != nil {
return blocks, fmt.Errorf("Error reading block id 0x%02X: %w", idByte, err)
}
readBytes += n
//var sum uint16
if IsFds {
//sum = crc16.Checksum(buf, crc16.MakeTable(crc16.CRC16_KERMIT))
//fmt.Printf("checksum: %04X\n", sum)
buf = append(buf, []byte{0x00, 0x00}...)
}
//fmt.Printf("idByte:$%02X\nreadLen:%d\nlen(buf):%d\nblocksize:%d\n", idByte, readLen, len(buf), binary.Size(block))
if idByte != 0x04 {
_, err = binary.Decode(buf, binary.LittleEndian, block)
if err != nil {
return blocks, fmt.Errorf("Error decoding block id 0x%02X: %w", idByte, err)
}
if idByte == 0x03 {
fh := block.(*BlockFileHeader)
dataSize = int(fh.FileSize)
} else {
dataSize = -1
}
} else {
db := block.(*BlockFileData)
db.BlockCode = buf[0]
if !IsFds {
db.CRC = uint16(buf[len(buf)-2]) | (uint16(buf[len(buf)-2])<<8)
readLen -= 2
} else {
db.CRC = crc16.Checksum(buf[:len(buf)-2], crc16.MakeTable(crc16.CRC16_KERMIT))
}
db.Data = buf[1:readLen]
//fmt.Println("len(db.Data)", len(db.Data))
block = db
//fmt.Printf("Block:%#v\n", block)
}
blocks = append(blocks, block)
}
return blocks, nil
}
//func ReadBlocksFds(r io.Reader) ([]FdsBlock, error) {
// reader := bufio.NewReader(r)
//
// var dataSize int
// blocks := []FdsBlock{}
// for {
// typ, err := reader.Peek(1)
// if err != nil {
// if err == io.EOF {
// break
// } else {
// return nil, err
// }
// }
//
// if len(typ) != 1 {
// return nil, fmt.Errorf("typ read length error")
// }
// //fmt.Printf("typ: %v\n", typ)
//
// if typ[0] == 0x00 {
// //fmt.Println("00 'block'")
// break
// }
//
// var block FdsBlock
// switch BlockType(typ[0]) {
// case BlockInfo:
// block = &FdsBlockInfo{}
// case BlockFileAmount:
// block = &FdsFileAmount{}
// case BlockFileHeader:
// block = &FdsFileHeader{}
// case BlockFileData:
// block = &FdsFileData{}
// default:
// return nil, fmt.Errorf("invalid block type: %02X", typ[0])
// }
//
// if block.Type() != BlockFileData {
// err = binary.Read(reader, binary.LittleEndian, block)
// if err != nil {
// return nil, fmt.Errorf("decode error: %w", err)
// }
// } else {
// fb := block.(*FdsFileData)
// fb.BlockCode = byte(BlockFileData)
// reader.Discard(1)
//
// if dataSize == 0 {
// return nil, fmt.Errorf("FileData without size")
// }
// fmt.Println("dataSize:", dataSize)
//
// fb.Data = make([]byte, dataSize)
// _, err = reader.Read(fb.Data)
// if err != nil {
// return nil, fmt.Errorf("data read err: %w", err)
// }
// dataSize = 0
// }
//
// switch block.Type() {
// case BlockFileHeader:
// bh := block.(*FdsFileHeader)
// dataSize = int(bh.FileSize)
// }
//
// fmt.Println(block)
// blocks = append(blocks, block)
// }
//
// return blocks, nil
//}

14
rom.go Normal file
View File

@ -0,0 +1,14 @@
package fds
type Rom struct {
Header *FdsHeader
Blocks []DiskBlock
}
type FdsHeader struct {
Magic [4]byte
Sides uint8
_ [11]byte // padding
}
var FdsHeaderMagic []byte = []byte{0x46, 0x44, 0x53, 0x1A}