feat(cli): add CLI entry point
This commit is contained in:
1 parent
99458b9d0b
commit
996bcedd0b
1 file changed
+133
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Chip8.Core;
|
||||
using Chip8.Core.Audio;
|
||||
using Chip8.Core.Export;
|
||||
|
||||
namespace Chip8.Cli;
|
||||
|
||||
static class Program
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0 || args[0] == "-h" || args[0] == "--help")
|
||||
{
|
||||
printUsage();
|
||||
return args.Length == 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "asm":
|
||||
return AsmCommand.run(args[1..]);
|
||||
case "disasm":
|
||||
return DisasmCommand.run(args[1..]);
|
||||
case "debug":
|
||||
return TuiDebugger.run(args[1..]);
|
||||
}
|
||||
|
||||
Options options;
|
||||
try
|
||||
{
|
||||
options = Options.parse(args);
|
||||
}
|
||||
catch (ArgumentException error)
|
||||
{
|
||||
Console.Error.WriteLine($"error: {error.Message}");
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!File.Exists(options.romPath))
|
||||
{
|
||||
Console.Error.WriteLine($"error: ROM not found: {options.romPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
byte[] rom = File.ReadAllBytes(options.romPath);
|
||||
var machine = new Machine(options.buildQuirks(), options.seed, options.buildPlatform());
|
||||
machine.loadRom(rom);
|
||||
|
||||
if (options.headless || options.dumpPpmPath is not null || options.dumpWavPath is not null)
|
||||
{
|
||||
return runHeadless(machine, options);
|
||||
}
|
||||
|
||||
RaylibFrontend.run(machine, options);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int runHeadless(Machine machine, Options options)
|
||||
{
|
||||
AudioTrack? track = null;
|
||||
if (options.dumpWavPath is not null)
|
||||
{
|
||||
int frames = Math.Max(1, options.cycles / options.instructionsPerFrame);
|
||||
track = new AudioRecorder().record(machine, frames, options.instructionsPerFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int cycle = 0; cycle < options.cycles; cycle++)
|
||||
{
|
||||
machine.step();
|
||||
if (cycle % options.instructionsPerFrame == options.instructionsPerFrame - 1)
|
||||
{
|
||||
machine.tickTimers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (machine.platform == Platform.Chip8)
|
||||
{
|
||||
Console.Write(AsciiScreen.renderFramed(machine.display));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(AsciiScreen.render(machine.video));
|
||||
}
|
||||
|
||||
int litPixels = machine.platform == Platform.Chip8
|
||||
? AsciiScreen.litPixelCount(machine.display)
|
||||
: AsciiScreen.litPixelCount(machine.video);
|
||||
Console.WriteLine(
|
||||
$"rom={Path.GetFileName(options.romPath)} cycles={options.cycles} " +
|
||||
$"litPixels={litPixels} soundActive={machine.soundActive}");
|
||||
|
||||
if (options.dumpPpmPath is not null)
|
||||
{
|
||||
PpmImage.writeFile(options.dumpPpmPath, PpmImage.fromMachine(machine, options.buildPalette(), options.scale));
|
||||
Console.WriteLine($"wrote frame to {options.dumpPpmPath}");
|
||||
}
|
||||
|
||||
if (options.dumpWavPath is not null && track is not null)
|
||||
{
|
||||
WavFile.writeFile(options.dumpWavPath, track.toSamples(), AudioRecorder.defaultSampleRate);
|
||||
Console.WriteLine($"wrote {track.durationSeconds:0.00}s of audio to {options.dumpWavPath}");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void printUsage()
|
||||
{
|
||||
Console.WriteLine("chip8-sharp - CHIP-8 / SUPER-CHIP / XO-CHIP emulator");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("usage: chip8 <rom> [options]");
|
||||
Console.WriteLine(" chip8 asm <source.asm> [-o out.ch8] [--listing]");
|
||||
Console.WriteLine(" chip8 disasm <rom.ch8> [--base 0x200]");
|
||||
Console.WriteLine(" chip8 debug <rom.ch8> [--platform NAME]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(" --headless run without a window, dump the framebuffer as ASCII");
|
||||
Console.WriteLine(" --cycles N instructions to run in headless mode (default 2000)");
|
||||
Console.WriteLine(" --ipf N instructions per 60Hz frame (default 11)");
|
||||
Console.WriteLine(" --scale N window / PPM pixel scale (default 14)");
|
||||
Console.WriteLine(" --seed N deterministic RNG seed");
|
||||
Console.WriteLine(" --platform NAME chip8 (default), superchip, or xochip");
|
||||
Console.WriteLine(" --quirks NAME auto (default), modern, cosmac, superchip, xochip");
|
||||
Console.WriteLine(" --palette NAME octo (default), mono, grayscale, extended16");
|
||||
Console.WriteLine(" --ppm PATH export the final frame as a PPM image");
|
||||
Console.WriteLine(" --wav PATH export the buzzer audio as a WAV file");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("keys: 1234/QWER/ASDF/ZXCV map to the CHIP-8 hex pad");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user