| 9120e874 Andy McCall |
1 |
using System; |
| 2 |
using System.Text.Json.Nodes; |
| 3 |
using System.Threading; |
| 4 |
using System.Threading.Tasks; |
| 5 |
|
| 6 |
namespace X16Debug; |
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
public static class InteractiveMode |
| 12 |
{ |
| 13 |
|
| 14 |
private static X16DebugClient? _activeClient; |
| 15 |
|
| 16 |
public static async Task RunAsync(X16DebugClient client) |
| 17 |
{ |
| 18 |
_activeClient = client; |
| 19 |
client.OnEvent += OnEvent; |
| 20 |
|
| 21 |
PrintBanner(); |
| 22 |
|
| 23 |
while (true) |
| 24 |
{ |
| 25 |
Console.Write("> "); |
| 26 |
string? input = Console.ReadLine(); |
| 27 |
if (input == null) break; |
| 28 |
input = input.Trim(); |
| 29 |
if (string.IsNullOrEmpty(input)) continue; |
| 30 |
|
| 31 |
|
| 32 |
if (input.Length == 1) |
| 33 |
{ |
| 34 |
switch (input[0]) |
| 35 |
{ |
| 36 |
case 's': await DoStep(client); continue; |
| 37 |
case 'c': await DoContinue(client); continue; |
| 38 |
case 'b': await DoBreak(client); continue; |
| 39 |
case 'r': await DoRegs(client); continue; |
| 40 |
case 't': await DoStatus(client); continue; |
| 41 |
case 'l': await DoBpList(client); continue; |
| 42 |
case 'x': await DoBpClear(client); continue; |
| 43 |
case 'q': return; |
| 44 |
case '?': |
| 45 |
case 'h': PrintHelp(); continue; |
| 46 |
} |
| 47 |
} |
| 48 |
|
| 49 |
|
| 50 |
var parts = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); |
| 51 |
string cmd = parts[0].ToLower(); |
| 52 |
string[] args = parts.Length > 1 ? parts[1..] : Array.Empty<string>(); |
| 53 |
|
| 54 |
switch (cmd) |
| 55 |
{ |
| 56 |
case "step": |
| 57 |
case "s": |
| 58 |
await DoStep(client); |
| 59 |
break; |
| 60 |
case "continue": |
| 61 |
case "c": |
| 62 |
await DoContinue(client); |
| 63 |
break; |
| 64 |
case "break": |
| 65 |
case "b": |
| 66 |
await DoBreak(client); |
| 67 |
break; |
| 68 |
case "status": |
| 69 |
case "t": |
| 70 |
await DoStatus(client); |
| 71 |
break; |
| 72 |
case "reset": |
| 73 |
await DoReset(client); |
| 74 |
break; |
| 75 |
case "regs": |
| 76 |
case "r": |
| 77 |
await DoRegs(client); |
| 78 |
break; |
| 79 |
case "setreg": |
| 80 |
case "p": |
| 81 |
if (args.Length < 2) |
| 82 |
Console.WriteLine("Usage: setreg <reg> <hex_value>"); |
| 83 |
else |
| 84 |
await Commands.RunOneShotAsync(client, "setreg", args); |
| 85 |
break; |
| 86 |
case "mem": |
| 87 |
case "m": |
| 88 |
if (args.Length < 1) |
| 89 |
Console.WriteLine("Usage: mem <addr>-<end> or mem <addr> <len>"); |
| 90 |
else |
| 91 |
await Commands.RunOneShotAsync(client, "mem", args); |
| 92 |
break; |
| 93 |
case "setmem": |
| 94 |
if (args.Length < 2) |
| 95 |
Console.WriteLine("Usage: setmem <addr> <hex_data>"); |
| 96 |
else |
| 97 |
await Commands.RunOneShotAsync(client, "setmem", args); |
| 98 |
break; |
| 99 |
case "vram": |
| 100 |
case "v": |
| 101 |
if (args.Length < 1) |
| 102 |
Console.WriteLine("Usage: vram <addr>-<end> or vram <addr> <len>"); |
| 103 |
else |
| 104 |
await Commands.RunOneShotAsync(client, "vram", args); |
| 105 |
break; |
| 106 |
case "setvram": |
| 107 |
if (args.Length < 2) |
| 108 |
Console.WriteLine("Usage: setvram <addr> <hex_data>"); |
| 109 |
else |
| 110 |
await Commands.RunOneShotAsync(client, "setvram", args); |
| 111 |
break; |
| 112 |
case "joy": |
| 113 |
case "j": |
| 114 |
await Commands.RunOneShotAsync(client, "joy", args); |
| 115 |
break; |
| 116 |
case "bp": |
| 117 |
await Commands.RunOneShotAsync(client, "bp", args); |
| 118 |
break; |
| 119 |
case "quit": |
| 120 |
case "q": |
| 121 |
return; |
| 122 |
case "help": |
| 123 |
case "?": |
| 124 |
PrintHelp(); |
| 125 |
break; |
| 126 |
default: |
| 127 |
Console.WriteLine($"Unknown command: {cmd}. Type 'h' for help."); |
| 128 |
break; |
| 129 |
} |
| 130 |
} |
| 131 |
|
| 132 |
client.OnEvent -= OnEvent; |
| 133 |
_activeClient = null; |
| 134 |
} |
| 135 |
|
| 136 |
static void OnEvent(JsonNode evt) |
| 137 |
{ |
| 138 |
string eventType = evt["event"]?.GetValue<string>() ?? "unknown"; |
| 139 |
|
| 140 |
if (eventType == "stopped") |
| 141 |
{ |
| 142 |
var body = evt["body"]; |
| 143 |
string reason = body?["reason"]?.GetValue<string>() ?? "unknown"; |
| 144 |
|
| 145 |
Console.WriteLine(); |
| 146 |
Console.WriteLine($"! STOPPED ({reason})"); |
| 147 |
|
| 148 |
|
| 149 |
if (_activeClient != null) |
| 150 |
{ |
| 151 |
_ = Task.Run(async () => |
| 152 |
{ |
| 153 |
try |
| 154 |
{ |
| 155 |
await Commands.PrintRegistersFromServer(_activeClient); |
| 156 |
} |
| 157 |
catch { } |
| 158 |
Console.Write("> "); |
| 159 |
}); |
| 160 |
} |
| 161 |
else |
| 162 |
{ |
| 163 |
Console.Write("> "); |
| 164 |
} |
| 165 |
} |
| 166 |
else if (eventType == "output") |
| 167 |
{ |
| 168 |
var body = evt["body"]; |
| 169 |
string? output = body?["output"]?.GetValue<string>(); |
| 170 |
if (output != null) |
| 171 |
{ |
| 172 |
Console.Write(output); |
| 173 |
} |
| 174 |
} |
| 175 |
else |
| 176 |
{ |
| 177 |
Console.WriteLine($"\n! Event: {eventType}"); |
| 178 |
Console.Write("> "); |
| 179 |
} |
| 180 |
} |
| 181 |
|
| 182 |
static async Task DoStep(X16DebugClient client) |
| 183 |
{ |
| 184 |
try |
| 185 |
{ |
| 186 |
var r = await client.StepInAsync(); |
| 187 |
if (r["success"]?.GetValue<bool>() != true) |
| 188 |
Console.WriteLine($"Error: {r["message"]}"); |
| 189 |
|
| 190 |
} |
| 191 |
catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } |
| 192 |
} |
| 193 |
|
| 194 |
static async Task DoContinue(X16DebugClient client) |
| 195 |
{ |
| 196 |
try |
| 197 |
{ |
| 198 |
var r = await client.ContinueAsync(); |
| 199 |
if (r["success"]?.GetValue<bool>() == true) |
| 200 |
Console.WriteLine("Running..."); |
| 201 |
else |
| 202 |
Console.WriteLine($"Error: {r["message"]}"); |
| 203 |
} |
| 204 |
catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } |
| 205 |
} |
| 206 |
|
| 207 |
static async Task DoBreak(X16DebugClient client) |
| 208 |
{ |
| 209 |
try |
| 210 |
{ |
| 211 |
var r = await client.PauseAsync(); |
| 212 |
if (r["success"]?.GetValue<bool>() == true) |
| 213 |
Console.WriteLine("Pause requested..."); |
| 214 |
else |
| 215 |
Console.WriteLine($"Error: {r["message"]}"); |
| 216 |
|
| 217 |
} |
| 218 |
catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } |
| 219 |
} |
| 220 |
|
| 221 |
static async Task DoStatus(X16DebugClient client) |
| 222 |
{ |
| 223 |
try |
| 224 |
{ |
| 225 |
await Commands.RunOneShotAsync(client, "status", Array.Empty<string>()); |
| 226 |
} |
| 227 |
catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } |
| 228 |
} |
| 229 |
|
| 230 |
static async Task DoReset(X16DebugClient client) |
| 231 |
{ |
| 232 |
try |
| 233 |
{ |
| 234 |
var r = await client.EvaluateAsync("reset"); |
| 235 |
if (r["success"]?.GetValue<bool>() == true) |
| 236 |
{ |
| 237 |
Console.WriteLine("Reset OK"); |
| 238 |
try { await Commands.PrintRegistersFromServer(client); } |
| 239 |
catch { } |
| 240 |
} |
| 241 |
else |
| 242 |
Console.WriteLine($"Error: {r["message"]}"); |
| 243 |
} |
| 244 |
catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } |
| 245 |
} |
| 246 |
|
| 247 |
static async Task DoRegs(X16DebugClient client) |
| 248 |
{ |
| 249 |
try |
| 250 |
{ |
| 251 |
await Commands.PrintRegistersFromServer(client); |
| 252 |
} |
| 253 |
catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } |
| 254 |
} |
| 255 |
|
| 256 |
static async Task DoBpList(X16DebugClient client) |
| 257 |
{ |
| 258 |
await Commands.RunOneShotAsync(client, "bp", new[] { "list" }); |
| 259 |
} |
| 260 |
|
| 261 |
static async Task DoBpClear(X16DebugClient client) |
| 262 |
{ |
| 263 |
await Commands.RunOneShotAsync(client, "bp", new[] { "clear" }); |
| 264 |
} |
| 265 |
|
| 266 |
static void PrintBanner() |
| 267 |
{ |
| 268 |
Console.WriteLine("╔═══════════════════════════════════════╗"); |
| 269 |
Console.WriteLine("║ X16 Debug Client (DAP) - Connected ║"); |
| 270 |
Console.WriteLine("╠═══════════════════════════════════════╣"); |
| 271 |
Console.WriteLine("║ [s] Step [c] Continue ║"); |
| 272 |
Console.WriteLine("║ [b] Break [r] Registers ║"); |
| 273 |
Console.WriteLine("║ [m] Memory [v] VRAM ║"); |
| 274 |
Console.WriteLine("║ [j] Joystick [p] Set register ║"); |
| 275 |
Console.WriteLine("║ [t] Status [l] List BPs ║"); |
| 276 |
Console.WriteLine("║ [x] Clear BPs [q] Quit ║"); |
| 277 |
Console.WriteLine("╠═══════════════════════════════════════╣"); |
| 278 |
Console.WriteLine("║ Type full command or ? for help ║"); |
| 279 |
Console.WriteLine("╚═══════════════════════════════════════╝"); |
| 280 |
} |
| 281 |
|
| 282 |
static void PrintHelp() |
| 283 |
{ |
| 284 |
Console.WriteLine(); |
| 285 |
Console.WriteLine("Commands:"); |
| 286 |
Console.WriteLine(" s, step Step one instruction"); |
| 287 |
Console.WriteLine(" c, continue Continue execution"); |
| 288 |
Console.WriteLine(" b, break Pause execution"); |
| 289 |
Console.WriteLine(" r, regs Show registers"); |
| 290 |
Console.WriteLine(" t, status Show emulator status"); |
| 291 |
Console.WriteLine(" reset Reset CPU"); |
| 292 |
Console.WriteLine(" m, mem <range> Read memory (e.g., mem 1030-1040, mem 0800 10)"); |
| 293 |
Console.WriteLine(" setmem <addr> <hex> Write memory (e.g., setmem 0400 DEADBEEF)"); |
| 294 |
Console.WriteLine(" v, vram <range> Read VERA video RAM (e.g., vram 1B000 20)"); |
| 295 |
Console.WriteLine(" setvram <addr> <hex> Write VRAM (e.g., setvram 1B000 DEADBEEF)"); |
| 296 |
Console.WriteLine(" j, joy <port> [btns] Drive a SNES controller port 1-4"); |
| 297 |
Console.WriteLine(" p, setreg <r> <val> Set register (e.g., setreg a FF)"); |
| 298 |
Console.WriteLine(" bp add <addr> Add breakpoint"); |
| 299 |
Console.WriteLine(" bp remove <addr> Remove breakpoint"); |
| 300 |
Console.WriteLine(" l, bp list List breakpoints"); |
| 301 |
Console.WriteLine(" x, bp clear Clear all breakpoints"); |
| 302 |
Console.WriteLine(" q, quit Disconnect and exit"); |
| 303 |
Console.WriteLine(" ?, help Show this help"); |
| 304 |
Console.WriteLine(); |
| 305 |
Console.WriteLine("VRAM is VERA's 17-bit video RAM. It is not in the CPU map, so `mem 1B000`"); |
| 306 |
Console.WriteLine("and `vram 1B000` are two different memories at the same number:"); |
| 307 |
Console.WriteLine(" vram 1B000 20 Dump 32 bytes of the text screen"); |
| 308 |
Console.WriteLine(" setvram 1F9C0 DEAD Poke two bytes into sprite attributes"); |
| 309 |
Console.WriteLine(); |
| 310 |
Console.WriteLine("Joysticks need no gamepad and no -joy1..-joy4 flag. The button list is the"); |
| 311 |
Console.WriteLine("complete held state, so anything left off it is released:"); |
| 312 |
Console.WriteLine(" joy 1 Show what port 1 is reporting"); |
| 313 |
Console.WriteLine(" joy 1 right a Hold Right and A"); |
| 314 |
Console.WriteLine(" joy 1 start Hold only Start; Right and A let go"); |
| 315 |
Console.WriteLine(" joy 1 none Connected with nothing held (JOY(1) = $00)"); |
| 316 |
Console.WriteLine(" joy 1 release Empty the port again (JOY(1) = $FF)"); |
| 317 |
Console.WriteLine(" buttons: up down left right a b x y start select l r"); |
| 318 |
Console.WriteLine(); |
| 319 |
} |
| 320 |
} |