diff --git a/README.md b/README.md index c0befdf..77cddd7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Client (PC-software) for Famicom Dumper/Programmer +[![Build test](https://github.com/ClusterM/famicom-dumper-client/actions/workflows/build-test.yml/badge.svg)](https://github.com/ClusterM/famicom-dumper-client/actions/workflows/build-test.yml) This is the client for the Famicom Dumper/Programmer hardware: - [https://github.com/ClusterM/famicom-dumper-writer](https://github.com/ClusterM/famicom-dumper-writer) - my own dumper project @@ -27,7 +28,7 @@ It can be used to: It's a command-line application. -Usage: **famicom-dumper \ [options]** +Usage: `famicom-dumper \ [options]` Available commands: - **list-mappers** - list available mappers to dump @@ -79,156 +80,156 @@ When you specify a mapper number or name, the application compiles the scripts t Mapper scripts are written in C# language. Each script must contain class (any name allowed) that impliments [IMapper](FamicomDumper/IMapper.cs) interface. ```C# - public interface IMapper - { - /// - /// Name of the mapper - /// - string Name { get; } - - /// - /// Number of the mapper to spore in the iNES header (-1 if none) - /// - int Number { get; } - - /// - /// Number of submapper (0 if none) - /// - byte Submapper { get; } - - /// - /// Name of the mapper to store in UNIF container (null if none) - /// - string UnifName { get; } - - /// - /// Default PRG size to dump (in bytes) - /// - int DefaultPrgSize { get; } - - /// - /// Default CHR size to dump (in bytes) - /// - int DefaultChrSize { get; } - - /// - /// This method will be called to dump PRG - /// - /// FamicomDumperConnection object to access cartridge - /// This list must be filled with dumped PRG data - /// Size of PRG to dump requested by user (in bytes) - void DumpPrg(IFamicomDumperConnection dumper, List data, int size = 0); - - /// - /// This method will be called to dump CHR - /// - /// FamicomDumperConnection object to access cartridge - /// This list must be filled with dumped CHR data - /// Size of CHR to dump requested by user (in bytes) - void DumpChr(IFamicomDumperConnection dumper, List data, int size = 0); - - /// - /// This method will be called to enable PRG RAM - /// - /// - void EnablePrgRam(IFamicomDumperConnection dumper); - - /// - /// This method must return mirroring type, it can call dumper.GetMirroring() if it's fixed - /// - /// FamicomDumperConnection object to access cartridge - /// - NesFile.MirroringType GetMirroring(IFamicomDumperConnection dumper); - } +public interface IMapper +{ + /// + /// Name of the mapper + /// + string Name { get; } + + /// + /// Number of the mapper to spore in the iNES header (-1 if none) + /// + int Number { get; } + + /// + /// Number of submapper (0 if none) + /// + byte Submapper { get; } + + /// + /// Name of the mapper to store in UNIF container (null if none) + /// + string UnifName { get; } + + /// + /// Default PRG size to dump (in bytes) + /// + int DefaultPrgSize { get; } + + /// + /// Default CHR size to dump (in bytes) + /// + int DefaultChrSize { get; } + + /// + /// This method will be called to dump PRG + /// + /// FamicomDumperConnection object to access cartridge + /// This list must be filled with dumped PRG data + /// Size of PRG to dump requested by user (in bytes) + void DumpPrg(IFamicomDumperConnection dumper, List data, int size = 0); + + /// + /// This method will be called to dump CHR + /// + /// FamicomDumperConnection object to access cartridge + /// This list must be filled with dumped CHR data + /// Size of CHR to dump requested by user (in bytes) + void DumpChr(IFamicomDumperConnection dumper, List data, int size = 0); + + /// + /// This method will be called to enable PRG RAM + /// + /// + void EnablePrgRam(IFamicomDumperConnection dumper); + + /// + /// This method must return mirroring type, it can call dumper.GetMirroring() if it's fixed + /// + /// FamicomDumperConnection object to access cartridge + /// + NesFile.MirroringType GetMirroring(IFamicomDumperConnection dumper); +} ``` FamicomDumperConnection implements [IFamicomDumperConnection](FamicomDumperConnection/IFamicomDumperConnection.cs) interface: ```C# - public interface IFamicomDumperConnection : IDisposable - { - /// - /// Simulate reset (M2 goes to Z-state for a second) - /// - void Reset(); - - /// - /// Read single byte from CPU (PRG) bus - /// - /// Address to read from - /// Data from CPU (PRG) bus - byte ReadCpu(ushort address); - - /// - /// Read data from CPU (PRG) bus - /// - /// Address to read from - /// Number of bytes to read - /// Data from CPU (PRG) bus - byte[] ReadCpu(ushort address, int length); - - /// - /// Read single byte from PPU (CHR) bus - /// - /// Address to read from - /// Data from PPU (CHR) bus - byte ReadPpu(ushort address); - - /// - /// Read data from PPU (CHR) bus - /// - /// Address to read from - /// Number of bytes to read - /// Data from PPU (CHR) bus - byte[] ReadPpu(ushort address, int length); - - /// - /// Write data to CPU (PRG) bus - /// - /// Address to write to - /// Data to write, address will be incremented after each byte - void WriteCpu(ushort address, params byte[] data); - - /// - /// Write data to PPU (CHR) bus - /// - /// Address to write to - /// Data to write, address will be incremented after each byte - void WritePpu(ushort address, params byte[] data); - - /// - /// Read Famicom Disk System blocks - /// - /// First block number to read (zero-based) - /// Maximum number of blocks to read - /// Array of Famicom Disk System blocks - public (byte[] Data, bool CrcOk, bool EndOfHeadMeet)[] ReadFdsBlocks(byte startBlock = 0, byte maxBlockCount = byte.MaxValue); - - /// - /// Write blocks to Famicom Disk System card - /// - /// Block numbers to write (zero-based) - /// Raw blocks data - void WriteFdsBlocks(byte[] blockNumbers, byte[][] blocks); - - /// - /// Write single block to Famicom Disk System card - /// - /// Block number to write (zero-based) - /// Raw block data - void WriteFdsBlocks(byte blockNumber, byte[] block); - - /// - /// Read raw mirroring values (CIRAM A10 pin states for different states of PPU A10 and A11) - /// - /// Values of CIRAM A10 pin for $2000-$23FF, $2400-$27FF, $2800-$2BFF and $2C00-$2FFF - bool[] GetMirroringRaw(); - - /// - /// Read decoded current mirroring mode - /// - /// Current mirroring - NesFile.MirroringType GetMirroring(); - } +public interface IFamicomDumperConnection : IDisposable +{ + /// + /// Simulate reset (M2 goes to Z-state for a second) + /// + void Reset(); + + /// + /// Read single byte from CPU (PRG) bus + /// + /// Address to read from + /// Data from CPU (PRG) bus + byte ReadCpu(ushort address); + + /// + /// Read data from CPU (PRG) bus + /// + /// Address to read from + /// Number of bytes to read + /// Data from CPU (PRG) bus + byte[] ReadCpu(ushort address, int length); + + /// + /// Read single byte from PPU (CHR) bus + /// + /// Address to read from + /// Data from PPU (CHR) bus + byte ReadPpu(ushort address); + + /// + /// Read data from PPU (CHR) bus + /// + /// Address to read from + /// Number of bytes to read + /// Data from PPU (CHR) bus + byte[] ReadPpu(ushort address, int length); + + /// + /// Write data to CPU (PRG) bus + /// + /// Address to write to + /// Data to write, address will be incremented after each byte + void WriteCpu(ushort address, params byte[] data); + + /// + /// Write data to PPU (CHR) bus + /// + /// Address to write to + /// Data to write, address will be incremented after each byte + void WritePpu(ushort address, params byte[] data); + + /// + /// Read Famicom Disk System blocks + /// + /// First block number to read (zero-based) + /// Maximum number of blocks to read + /// Array of Famicom Disk System blocks + (byte[] Data, bool CrcOk, bool EndOfHeadMeet)[] ReadFdsBlocks(byte startBlock = 0, byte maxBlockCount = byte.MaxValue); + + /// + /// Write blocks to Famicom Disk System card + /// + /// Block numbers to write (zero-based) + /// Raw blocks data + void WriteFdsBlocks(byte[] blockNumbers, byte[][] blocks); + + /// + /// Write single block to Famicom Disk System card + /// + /// Block number to write (zero-based) + /// Raw block data + void WriteFdsBlocks(byte blockNumber, byte[] block); + + /// + /// Read raw mirroring values (CIRAM A10 pin states for different states of PPU A10 and A11) + /// + /// Values of CIRAM A10 pin for $2000-$23FF, $2400-$27FF, $2800-$2BFF and $2C00-$2FFF + bool[] GetMirroringRaw(); + + /// + /// Read decoded current mirroring mode + /// + /// Current mirroring + NesFile.MirroringType GetMirroring(); +} ``` Check "mappers" directory for examples. @@ -236,15 +237,67 @@ Check "mappers" directory for examples. ## Other scripts -You can run custom C# scripts to interact with dumper and cartridge. It's usefull for reverse engineering. Each script must contain class (any name allowed) that contains **void Run(IFamicomDumperConnection dumper)** method. This method will be executed if --csfile option is specified. Also you can use [NesFile](https://github.com/ClusterM/nes-containers/blob/master/NesFile.cs) and [UnifFile](https://github.com/ClusterM/nes-containers/blob/master/UnifFile.cs) containers. +You can create and run custom C# scripts to interact with dumper and cartridge. It's usefull for reverse engineering. Each script must contain class (any name allowed) that contains **Run** method. You can specify those parameters in any order: + +* `IFamicomDumperConnection dumper` - dumper object used to access dumper +* `string filename` - filename specified by --file argument +* `IMapper mapper` - mapper object compiled from mapper script specified by --mapper argument +* `int prgSize` - PRG size specified by --prg-size argument (parsed) +* `int chrSize` - CHR size specified by --chr-size argument (parsed) +* `string unifName` - string specified by --unif-name argument +* `string unifAuthor` - string specified by --unif-author argument +* `bool battery` - true if --battery argument is specified +* `string[] args` - additional command line arguments + +You can specify additional arguments this way: >famicom-dumper script --cs-file DemoScript.cs - argument1 argument2 argument3 + +Always define default value if parameter is optional. The only exception is the "**mapper**" parameter, the "NROM" mapper will be used by default. Also **args** will be a zero-length array if additional arguments are not specified. + +Example: +```C# +class DemoScript // Class name doesn't matter +{ + void Run(IFamicomDumperConnection dumper, string[] args, IMapper mapper, int prgSize = 128 * 1024, int chrSize = -1) + { + if (mapper.Number >= 0) + Console.WriteLine($"Using mapper: #{mapper.Number} ({mapper.Name})"); + else + Console.WriteLine($"Using mapper: {mapper.Name}"); + + if (chrSize < 0) + { + // Oh no, CHR size is not specified! Lets use mapper's default + chrSize = mapper.DefaultChrSize; + } + + Console.WriteLine($"PRG size: {prgSize}"); + Console.WriteLine($"CHR size: {chrSize}"); + + if (args.Any()) + Console.WriteLine("Additional command line arguments: " + string.Join(", ", args)); + + // You can use other methods + Reset(dumper); + } + + void Reset(IFamicomDumperConnection dumper) + { + Console.Write("Reset... "); + dumper.Reset(); + Console.WriteLine("OK"); + } +} +``` + +Script will be compiled and **Run** method will be executed if **--cs-file** option is specified. Also you can use [NesFile](https://github.com/ClusterM/nes-containers/blob/master/NesFile.cs) and [UnifFile](https://github.com/ClusterM/nes-containers/blob/master/UnifFile.cs) containers. You can run script alone like this: ``` -famicom-dumper script --csfile DemoScript.cs +famicom-dumper script --cs-file DemoScript.cs ``` Or execute it before main action like this: ``` -famicom-dumper dump --mapper MMC3 --file game.nes --csfile DemoScript.cs +famicom-dumper dump --mapper MMC3 --file game.nes --cs-file DemoScript.cs ``` So you can write your own code to interact with dumper object and read/write data from/to cartridge. This dumper object can be even on another PC (read below)! Check "scripts" directory for example scripts. @@ -257,7 +310,7 @@ You can start this application as gRPC server on one PC: famicom-dumper server --port COM14 ``` -And dump cartridge over network using another PC: +And dump cartridge over the network using another PC: ``` famicom-dumper dump --mapper CNROM --file game.nes --host example.com ``` @@ -300,7 +353,7 @@ Saving to game.nes... OK Dump 32K of PRG and 8K of CHR as simple NROM cartridge but execute C# script first: ~~~~ ->famicom-dumper dump --port COM14 --mapper 0 --prg-size 32K --chr-size 8K --file game.nes --csfile init.cs" +>famicom-dumper dump --port COM14 --mapper 0 --prg-size 32K --chr-size 8K --file game.nes --cs-file init.cs" Dumper initialization... OK Compiling init.cs... Running init.Run()... @@ -393,7 +446,7 @@ Number of non-hidden files: 15 Please remove disk card... OK Please set disk card, side #2... ... -Saing to game.fds... OK +Saving to game.fds... OK ~~~~ Write Famicom Disk System card and verify written data: @@ -435,7 +488,7 @@ Listening port 9999, press Ctrl-C to stop Connect to remote dumper using TCP port 9999 and execute C# script: ~~~~ -famicom-dumper script --csfile DemoScript.cs --host clusterrr.com --tcp-port 9999 +famicom-dumper script --cs-file DemoScript.cs --host clusterrr.com --tcp-port 9999 Dumper initialization... OK Compiling DemoScript.cs... Running DemoScript.Run()...