OXYGENHUB
◆ main enhanced-x16-emulator / tools / x16dbg / InteractiveMode.cs
12.4 KiB · C# · last changed chore(): initial commit 25 days ago source blame history raw
1
using System;
2
using System.Text.Json.Nodes;
3
using System.Threading;
4
using System.Threading.Tasks;
5
6
namespace X16Debug;
7
8
/// <summary>
9
/// Interactive text-menu mode for live debugging.
10
/// </summary>
11
public static class InteractiveMode
12
{
13
    // Hold a reference to the client for the event handler to use for register fetching
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; // EOF
28
            input = input.Trim();
29
            if (string.IsNullOrEmpty(input)) continue;
30
31
            // Single-char hotkeys
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
            // Parse full commands
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
            // DAP stopped events don't include registers; fetch them asynchronously
149
            if (_activeClient != null)
150
            {
151
                _ = Task.Run(async () =>
152
                {
153
                    try
154
                    {
155
                        await Commands.PrintRegistersFromServer(_activeClient);
156
                    }
157
                    catch { /* registers may not be available yet */ }
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
            // The stopped event will arrive via OnEvent and print registers
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
            // The stopped event will arrive via OnEvent and print registers
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 { /* may not be available immediately */ }
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
}