feat(cli): add headless CLI runner with PNG/ASCII output
This commit is contained in:
1 parent
d663232c9d
commit
3547f0400b
1 file changed
+114
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using GameBoy;
|
||||
using GameBoy.Util;
|
||||
|
||||
namespace GameBoy.Cli
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private const int MaxSerialFrames = 4000;
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.Error.WriteLine("usage: GameBoy.Cli <rom.gb> [--frames N] [--serial] [--png path] [--ascii] [--ascii-out path]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
string romPath = args[0];
|
||||
int frames = 60;
|
||||
bool waitSerial = false;
|
||||
bool ascii = false;
|
||||
string? pngPath = null;
|
||||
string? asciiPath = null;
|
||||
|
||||
for (int i = 1; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "--frames":
|
||||
frames = int.Parse(args[++i]);
|
||||
break;
|
||||
case "--serial":
|
||||
waitSerial = true;
|
||||
break;
|
||||
case "--png":
|
||||
pngPath = args[++i];
|
||||
break;
|
||||
case "--ascii":
|
||||
ascii = true;
|
||||
break;
|
||||
case "--ascii-out":
|
||||
asciiPath = args[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(romPath))
|
||||
{
|
||||
Console.Error.WriteLine($"rom not found: {romPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var emulator = new Emulator(File.ReadAllBytes(romPath));
|
||||
Console.WriteLine($"loaded {emulator.Cartridge.Title} ({emulator.Cartridge.MapperName}, type 0x{emulator.Cartridge.CartridgeType:X2})");
|
||||
|
||||
if (waitSerial)
|
||||
RunUntilSerial(emulator);
|
||||
else
|
||||
for (int f = 0; f < frames; f++)
|
||||
emulator.RunFrame();
|
||||
|
||||
Console.WriteLine($"ran {emulator.TotalCycles} cycles");
|
||||
|
||||
if (waitSerial || emulator.SerialOutput.Length > 0)
|
||||
Console.WriteLine($"serial: {emulator.SerialOutput.Trim()}");
|
||||
|
||||
if (pngPath != null)
|
||||
{
|
||||
PngImage.WriteGrayscale(pngPath, Emulator.ScreenWidth, Emulator.ScreenHeight, emulator.Framebuffer);
|
||||
Console.WriteLine($"framebuffer written to {pngPath}");
|
||||
}
|
||||
|
||||
if (ascii || asciiPath != null)
|
||||
{
|
||||
string art = AsciiRender(emulator.Framebuffer);
|
||||
if (asciiPath != null)
|
||||
File.WriteAllText(asciiPath, art);
|
||||
if (ascii)
|
||||
Console.WriteLine(art);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void RunUntilSerial(Emulator emulator)
|
||||
{
|
||||
for (int f = 0; f < MaxSerialFrames; f++)
|
||||
{
|
||||
emulator.RunFrame();
|
||||
string output = emulator.SerialOutput;
|
||||
if (output.Contains("Passed") || output.Contains("Failed"))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static string AsciiRender(byte[] framebuffer)
|
||||
{
|
||||
char[] ramp = { ' ', '.', '+', '#' };
|
||||
var builder = new System.Text.StringBuilder();
|
||||
for (int y = 0; y < Emulator.ScreenHeight; y += 2)
|
||||
{
|
||||
for (int x = 0; x < Emulator.ScreenWidth; x += 2)
|
||||
{
|
||||
byte shade = framebuffer[y * Emulator.ScreenWidth + x];
|
||||
builder.Append(ramp[shade & 0x03]);
|
||||
}
|
||||
builder.Append('\n');
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user