yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
emulator_service_impl.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <thread>
6#include <unordered_set>
7
8#include "absl/strings/escaping.h"
9#include "absl/strings/str_format.h"
15#include "app/emu/emulator.h"
16#include "app/emu/input/input_backend.h" // Required for SnesButton enum
18
19namespace yaze::agent {
20
21namespace {
22// Helper to convert our gRPC Button enum to the emulator's SnesButton enum
25 switch (button) {
26 case A:
27 return SnesButton::A;
28 case B:
29 return SnesButton::B;
30 case X:
31 return SnesButton::X;
32 case Y:
33 return SnesButton::Y;
34 case L:
35 return SnesButton::L;
36 case R:
37 return SnesButton::R;
38 case SELECT:
39 return SnesButton::SELECT;
40 case START:
41 return SnesButton::START;
42 case UP:
43 return SnesButton::UP;
44 case DOWN:
45 return SnesButton::DOWN;
46 case LEFT:
47 return SnesButton::LEFT;
48 case RIGHT:
49 return SnesButton::RIGHT;
50 default:
51 return SnesButton::B; // Default fallback
52 }
53}
54} // namespace
55
58
59// --- Lifecycle ---
60
61grpc::Status EmulatorServiceImpl::Start(grpc::ServerContext* context,
62 const Empty* request,
63 CommandResponse* response) {
64 if (!emulator_)
65 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
66 "Emulator not initialized.");
68 response->set_success(true);
69 response->set_message("Emulator started.");
70 return grpc::Status::OK;
71}
72
73grpc::Status EmulatorServiceImpl::Stop(grpc::ServerContext* context,
74 const Empty* request,
75 CommandResponse* response) {
76 if (!emulator_)
77 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
78 "Emulator not initialized.");
79 emulator_->set_running(false);
80 response->set_success(true);
81 response->set_message("Emulator stopped.");
82 return grpc::Status::OK;
83}
84
85grpc::Status EmulatorServiceImpl::Pause(grpc::ServerContext* context,
86 const Empty* request,
87 CommandResponse* response) {
88 if (!emulator_)
89 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
90 "Emulator not initialized.");
91 emulator_->set_running(false);
92 response->set_success(true);
93 response->set_message("Emulator paused.");
94 return grpc::Status::OK;
95}
96
97grpc::Status EmulatorServiceImpl::Resume(grpc::ServerContext* context,
98 const Empty* request,
99 CommandResponse* response) {
100 if (!emulator_)
101 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
102 "Emulator not initialized.");
103 emulator_->set_running(true);
104 response->set_success(true);
105 response->set_message("Emulator resumed.");
106 return grpc::Status::OK;
107}
108
109grpc::Status EmulatorServiceImpl::Reset(grpc::ServerContext* context,
110 const Empty* request,
111 CommandResponse* response) {
112 if (!emulator_)
113 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
114 "Emulator not initialized.");
115 emulator_->snes().Reset(true); // Hard reset
116 response->set_success(true);
117 response->set_message("Emulator reset.");
118 return grpc::Status::OK;
119}
120
121// --- Input Control ---
122
123grpc::Status EmulatorServiceImpl::PressButtons(grpc::ServerContext* context,
124 const ButtonRequest* request,
125 CommandResponse* response) {
126 if (!emulator_)
127 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
128 "Emulator not initialized.");
129 auto& input_manager = emulator_->input_manager();
130 for (int i = 0; i < request->buttons_size(); i++) {
131 input_manager.PressButton(
132 ToSnesButton(static_cast<Button>(request->buttons(i))));
133 }
134 std::this_thread::sleep_for(std::chrono::milliseconds(50));
135 for (int i = 0; i < request->buttons_size(); i++) {
136 input_manager.ReleaseButton(
137 ToSnesButton(static_cast<Button>(request->buttons(i))));
138 }
139 response->set_success(true);
140 return grpc::Status::OK;
141}
142
143grpc::Status EmulatorServiceImpl::ReleaseButtons(grpc::ServerContext* context,
144 const ButtonRequest* request,
145 CommandResponse* response) {
146 if (!emulator_)
147 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
148 "Emulator not initialized.");
149 auto& input_manager = emulator_->input_manager();
150 for (int i = 0; i < request->buttons_size(); i++) {
151 input_manager.ReleaseButton(
152 ToSnesButton(static_cast<Button>(request->buttons(i))));
153 }
154 response->set_success(true);
155 return grpc::Status::OK;
156}
157
158grpc::Status EmulatorServiceImpl::HoldButtons(grpc::ServerContext* context,
159 const ButtonHoldRequest* request,
160 CommandResponse* response) {
161 if (!emulator_)
162 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
163 "Emulator not initialized.");
164 auto& input_manager = emulator_->input_manager();
165 for (int i = 0; i < request->buttons_size(); i++) {
166 input_manager.PressButton(
167 ToSnesButton(static_cast<Button>(request->buttons(i))));
168 }
169 std::this_thread::sleep_for(
170 std::chrono::milliseconds(request->duration_ms()));
171 for (int i = 0; i < request->buttons_size(); i++) {
172 input_manager.ReleaseButton(
173 ToSnesButton(static_cast<Button>(request->buttons(i))));
174 }
175 response->set_success(true);
176 return grpc::Status::OK;
177}
178
179// --- State Inspection ---
180
181grpc::Status EmulatorServiceImpl::GetGameState(grpc::ServerContext* context,
182 const GameStateRequest* request,
183 GameStateResponse* response) {
185 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
186 "SNES is not initialized.");
187 }
188 auto& memory = emulator_->snes().memory();
189
190 response->set_game_mode(memory.ReadByte(0x7E0010));
191 response->set_link_state(memory.ReadByte(0x7E005D));
192 response->set_link_pos_x(memory.ReadWord(0x7E0020));
193 response->set_link_pos_y(memory.ReadWord(0x7E0022));
194 response->set_link_health(memory.ReadByte(0x7EF36D));
195
196 for (const auto& mem_req : request->memory_reads()) {
197 auto* mem_resp = response->add_memory_responses();
198 mem_resp->set_address(mem_req.address());
199 std::vector<uint8_t> data(mem_req.size());
200 for (uint32_t i = 0; i < mem_req.size(); ++i) {
201 data[i] = memory.ReadByte(mem_req.address() + i);
202 }
203 mem_resp->set_data(data.data(), data.size());
204 }
205
206#ifdef YAZE_WITH_GRPC
207 if (request->include_screenshot()) {
208 auto screenshot = yaze::test::CaptureHarnessScreenshot();
209 if (screenshot.ok()) {
210 // Read the screenshot file and convert to PNG data
211 std::ifstream file(screenshot->file_path, std::ios::binary);
212 if (file.good()) {
213 std::string png_data((std::istreambuf_iterator<char>(file)),
214 std::istreambuf_iterator<char>());
215 response->set_screenshot_png(png_data);
216 }
217 }
218 }
219#endif
220
221 return grpc::Status::OK;
222}
223
224grpc::Status EmulatorServiceImpl::ReadMemory(grpc::ServerContext* context,
225 const MemoryRequest* request,
226 MemoryResponse* response) {
228 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
229 "SNES is not initialized.");
230 }
231 auto& memory = emulator_->snes().memory();
232 response->set_address(request->address());
233 std::vector<uint8_t> data(request->size());
234 for (uint32_t i = 0; i < request->size(); ++i) {
235 data[i] = memory.ReadByte(request->address() + i);
236 }
237 response->set_data(data.data(), data.size());
238 return grpc::Status::OK;
239}
240
241grpc::Status EmulatorServiceImpl::WriteMemory(grpc::ServerContext* context,
242 const MemoryWriteRequest* request,
243 CommandResponse* response) {
245 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
246 "SNES is not initialized.");
247 }
248 auto& memory = emulator_->snes().memory();
249 const std::string& data = request->data();
250 for (uint32_t i = 0; i < data.size(); ++i) {
251 memory.WriteByte(request->address() + i, static_cast<uint8_t>(data[i]));
252 }
253 response->set_success(true);
254 response->set_message(absl::StrFormat("Wrote %d bytes to 0x%X.", data.size(),
255 request->address()));
256 return grpc::Status::OK;
257}
258
259// --- Advanced Debugging ---
260
261// Helper to convert proto breakpoint type to manager type
264 switch (proto_type) {
265 case EXECUTE:
266 return BreakpointManager::Type::EXECUTE;
267 case READ:
268 return BreakpointManager::Type::READ;
269 case WRITE:
270 return BreakpointManager::Type::WRITE;
271 case ACCESS:
272 return BreakpointManager::Type::ACCESS;
273 case CONDITIONAL:
274 return BreakpointManager::Type::CONDITIONAL;
275 default:
276 return BreakpointManager::Type::EXECUTE;
277 }
278}
279
280// Helper to convert proto CPU type to manager CPU type
283 switch (proto_cpu) {
284 case CPU_65816:
285 return BreakpointManager::CpuType::CPU_65816;
286 case SPC700:
287 return BreakpointManager::CpuType::SPC700;
288 default:
289 return BreakpointManager::CpuType::CPU_65816;
290 }
291}
292
293// Helper to convert manager type back to proto
296 switch (type) {
297 case BreakpointManager::Type::EXECUTE:
298 return EXECUTE;
299 case BreakpointManager::Type::READ:
300 return READ;
301 case BreakpointManager::Type::WRITE:
302 return WRITE;
303 case BreakpointManager::Type::ACCESS:
304 return ACCESS;
305 case BreakpointManager::Type::CONDITIONAL:
306 return CONDITIONAL;
307 default:
308 return EXECUTE;
309 }
310}
311
312// Helper to convert manager CPU type back to proto
315 switch (cpu) {
316 case BreakpointManager::CpuType::CPU_65816:
317 return CPU_65816;
318 case BreakpointManager::CpuType::SPC700:
319 return SPC700;
320 default:
321 return CPU_65816;
322 }
323}
324
326 grpc::ServerContext* context, const BreakpointRequest* request,
327 BreakpointResponse* response) {
328 if (!emulator_) {
329 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
330 "Emulator not initialized.");
331 }
332
333 auto& bp_manager = emulator_->breakpoint_manager();
334 uint32_t id = bp_manager.AddBreakpoint(
335 request->address(), ToBreakpointType(request->type()),
336 ToCpuType(request->cpu()), request->condition(), request->description());
337
338 response->set_success(true);
339 response->set_breakpoint_id(id);
340 response->set_message(
341 absl::StrFormat("Breakpoint %d added at 0x%06X", id, request->address()));
342 return grpc::Status::OK;
343}
344
346 grpc::ServerContext* context, const BreakpointIdRequest* request,
347 CommandResponse* response) {
348 if (!emulator_) {
349 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
350 "Emulator not initialized.");
351 }
352
353 emulator_->breakpoint_manager().RemoveBreakpoint(request->breakpoint_id());
354 response->set_success(true);
355 response->set_message(
356 absl::StrFormat("Breakpoint %d removed", request->breakpoint_id()));
357 return grpc::Status::OK;
358}
359
361 grpc::ServerContext* context, const Empty* request,
362 BreakpointListResponse* response) {
363 if (!emulator_) {
364 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
365 "Emulator not initialized.");
366 }
367
368 auto breakpoints = emulator_->breakpoint_manager().GetAllBreakpoints();
369 for (const auto& bp : breakpoints) {
370 auto* info = response->add_breakpoints();
371 info->set_id(bp.id);
372 info->set_address(bp.address);
373 info->set_type(ToProtoBreakpointType(bp.type));
374 info->set_cpu(ToProtoCpuType(bp.cpu));
375 info->set_enabled(bp.enabled);
376 info->set_condition(bp.condition);
377 info->set_description(bp.description);
378 info->set_hit_count(bp.hit_count);
379 }
380
381 return grpc::Status::OK;
382}
383
385 grpc::ServerContext* context, const BreakpointStateRequest* request,
386 CommandResponse* response) {
387 if (!emulator_) {
388 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
389 "Emulator not initialized.");
390 }
391
392 emulator_->breakpoint_manager().SetEnabled(request->breakpoint_id(),
393 request->enabled());
394 response->set_success(true);
395 response->set_message(
396 absl::StrFormat("Breakpoint %d %s", request->breakpoint_id(),
397 request->enabled() ? "enabled" : "disabled"));
398 return grpc::Status::OK;
399}
400
401// Watchpoints - Note: Emulator needs WatchpointManager integration first
403 grpc::ServerContext* context, const WatchpointRequest* request,
404 WatchpointResponse* response) {
405 // TODO: Integrate WatchpointManager into Emulator class
406 return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
407 "Watchpoints require WatchpointManager integration");
408}
409
411 grpc::ServerContext* context, const WatchpointIdRequest* request,
412 CommandResponse* response) {
413 return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
414 "Watchpoints require WatchpointManager integration");
415}
416
418 grpc::ServerContext* context, const Empty* request,
419 WatchpointListResponse* response) {
420 return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
421 "Watchpoints require WatchpointManager integration");
422}
423
425 grpc::ServerContext* context, const WatchpointHistoryRequest* request,
426 WatchpointHistoryResponse* response) {
427 return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
428 "Watchpoints require WatchpointManager integration");
429}
430
431// Execution Control
432grpc::Status EmulatorServiceImpl::StepInstruction(grpc::ServerContext* context,
433 const Empty* request,
434 StepResponse* response) {
436 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
437 "SNES is not initialized.");
438 }
439
440 // Capture state before step
441 auto& cpu = emulator_->snes().cpu();
442 uint32_t pc_before = (cpu.PB << 16) | cpu.PC;
443
444 // Execute one instruction
446
447 // Fill response with new CPU state
448 auto* cpu_state = response->mutable_cpu_state();
449 cpu_state->set_a(cpu.A);
450 cpu_state->set_x(cpu.X);
451 cpu_state->set_y(cpu.Y);
452 cpu_state->set_sp(cpu.SP());
453 cpu_state->set_pc(cpu.PC);
454 cpu_state->set_db(cpu.DB);
455 cpu_state->set_pb(cpu.PB);
456 cpu_state->set_d(cpu.D);
457 cpu_state->set_status(cpu.status);
458 cpu_state->set_flag_n(cpu.GetNegativeFlag());
459 cpu_state->set_flag_v(cpu.GetOverflowFlag());
460 cpu_state->set_flag_d(cpu.GetDecimalFlag());
461 cpu_state->set_flag_i(cpu.GetInterruptFlag());
462 cpu_state->set_flag_z(cpu.GetZeroFlag());
463 cpu_state->set_flag_c(cpu.GetCarryFlag());
464 cpu_state->set_cycles(emulator_->GetCurrentCycle());
465
466 response->set_success(true);
467 response->set_message(absl::StrFormat("Stepped from 0x%06X to 0x%06X",
468 pc_before, (cpu.PB << 16) | cpu.PC));
469 return grpc::Status::OK;
470}
471
473 grpc::ServerContext* context, const Empty* request,
474 BreakpointHitResponse* response) {
476 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
477 "SNES is not initialized.");
478 }
479
480 // Run emulator until breakpoint is hit (max 1 million instructions to prevent
481 // hangs)
482 const int kMaxInstructions = 1000000;
483 int instruction_count = 0;
484
485 auto& bp_manager = emulator_->breakpoint_manager();
486 auto& cpu = emulator_->snes().cpu();
487
488 while (instruction_count++ < kMaxInstructions) {
489 uint32_t pc = (cpu.PB << 16) | cpu.PC;
490
491 // Check for execute breakpoint
492 if (bp_manager.ShouldBreakOnExecute(
494 response->set_hit(true);
495 response->set_message(
496 absl::StrFormat("Breakpoint hit at 0x%06X after %d instructions", pc,
497 instruction_count));
498
499 // Fill breakpoint info
500 auto* last_hit = bp_manager.GetLastHit();
501 if (last_hit) {
502 auto* bp_info = response->mutable_breakpoint();
503 bp_info->set_id(last_hit->id);
504 bp_info->set_address(last_hit->address);
505 bp_info->set_type(ToProtoBreakpointType(last_hit->type));
506 bp_info->set_cpu(ToProtoCpuType(last_hit->cpu));
507 bp_info->set_enabled(last_hit->enabled);
508 bp_info->set_condition(last_hit->condition);
509 bp_info->set_description(last_hit->description);
510 bp_info->set_hit_count(last_hit->hit_count);
511 }
512
513 // Fill CPU state
514 auto* cpu_state = response->mutable_cpu_state();
515 cpu_state->set_pc(cpu.PC);
516 cpu_state->set_pb(cpu.PB);
517 cpu_state->set_a(cpu.A);
518 cpu_state->set_x(cpu.X);
519 cpu_state->set_y(cpu.Y);
520 cpu_state->set_sp(cpu.SP());
521 cpu_state->set_db(cpu.DB);
522 cpu_state->set_d(cpu.D);
523
524 return grpc::Status::OK;
525 }
526
527 // Execute instruction
529 }
530
531 // No breakpoint hit
532 response->set_hit(false);
533 response->set_message(absl::StrFormat(
534 "No breakpoint hit after %d instructions (timeout)", kMaxInstructions));
535 return grpc::Status::OK;
536}
537
538grpc::Status EmulatorServiceImpl::StepOver(grpc::ServerContext* context,
539 const Empty* request,
540 StepResponse* response) {
542 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
543 "SNES is not initialized.");
544 }
545
546 // Initialize step controller with emulator callbacks
548
549 // Execute step over
550 auto result = step_controller_.StepOver();
551
552 // Fill response
553 auto& cpu = emulator_->snes().cpu();
554 auto* cpu_state = response->mutable_cpu_state();
555 cpu_state->set_a(cpu.A);
556 cpu_state->set_x(cpu.X);
557 cpu_state->set_y(cpu.Y);
558 cpu_state->set_sp(cpu.SP());
559 cpu_state->set_pc(cpu.PC);
560 cpu_state->set_db(cpu.DB);
561 cpu_state->set_pb(cpu.PB);
562 cpu_state->set_d(cpu.D);
563 cpu_state->set_status(cpu.status);
564 cpu_state->set_flag_n(cpu.GetNegativeFlag());
565 cpu_state->set_flag_v(cpu.GetOverflowFlag());
566 cpu_state->set_flag_d(cpu.GetDecimalFlag());
567 cpu_state->set_flag_i(cpu.GetInterruptFlag());
568 cpu_state->set_flag_z(cpu.GetZeroFlag());
569 cpu_state->set_flag_c(cpu.GetCarryFlag());
570 cpu_state->set_cycles(emulator_->GetCurrentCycle());
571
572 response->set_success(result.success);
573 response->set_message(result.message);
574 return grpc::Status::OK;
575}
576
577grpc::Status EmulatorServiceImpl::StepOut(grpc::ServerContext* context,
578 const Empty* request,
579 StepResponse* response) {
581 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
582 "SNES is not initialized.");
583 }
584
585 // Initialize step controller with emulator callbacks
587
588 // Check if we have a call stack to step out of
589 if (step_controller_.GetCallDepth() == 0) {
590 response->set_success(false);
591 response->set_message("Cannot step out - call stack is empty");
592 return grpc::Status::OK;
593 }
594
595 // Execute step out
596 auto result = step_controller_.StepOut();
597
598 // Fill response
599 auto& cpu = emulator_->snes().cpu();
600 auto* cpu_state = response->mutable_cpu_state();
601 cpu_state->set_a(cpu.A);
602 cpu_state->set_x(cpu.X);
603 cpu_state->set_y(cpu.Y);
604 cpu_state->set_sp(cpu.SP());
605 cpu_state->set_pc(cpu.PC);
606 cpu_state->set_db(cpu.DB);
607 cpu_state->set_pb(cpu.PB);
608 cpu_state->set_d(cpu.D);
609 cpu_state->set_status(cpu.status);
610 cpu_state->set_flag_n(cpu.GetNegativeFlag());
611 cpu_state->set_flag_v(cpu.GetOverflowFlag());
612 cpu_state->set_flag_d(cpu.GetDecimalFlag());
613 cpu_state->set_flag_i(cpu.GetInterruptFlag());
614 cpu_state->set_flag_z(cpu.GetZeroFlag());
615 cpu_state->set_flag_c(cpu.GetCarryFlag());
616 cpu_state->set_cycles(emulator_->GetCurrentCycle());
617
618 response->set_success(result.success);
619 response->set_message(result.message);
620 return grpc::Status::OK;
621}
622
624 auto& memory = emulator_->snes().memory();
625 auto& cpu = emulator_->snes().cpu();
626
627 // Set up memory reader
628 step_controller_.SetMemoryReader([&memory](uint32_t addr) -> uint8_t {
629 return memory.ReadByte(addr);
630 });
631
632 // Set up single step function
635 });
636
637 // Set up PC getter
638 step_controller_.SetPcGetter([&cpu]() -> uint32_t {
639 return (cpu.PB << 16) | cpu.PC;
640 });
641}
642
643// Disassembly
645 grpc::ServerContext* context, const DisassemblyRequest* request,
646 DisassemblyResponse* response) {
648 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
649 "SNES is not initialized.");
650 }
651
652 auto& cpu = emulator_->snes().cpu();
653 auto& memory = emulator_->snes().memory();
654 auto& bp_manager = emulator_->breakpoint_manager();
655
656 // Create disassembler instance
658
659 // Memory reader lambda that captures our memory reference
660 auto read_byte = [&memory](uint32_t addr) -> uint8_t {
661 return memory.ReadByte(addr);
662 };
663
664 // Get CPU flags for proper operand size handling
665 // M flag: true = 8-bit accumulator, false = 16-bit
666 // X flag: true = 8-bit index registers, false = 16-bit
667 bool m_flag = cpu.GetAccumulatorSize(); // true if 8-bit mode
668 bool x_flag = cpu.GetIndexSize(); // true if 8-bit mode
669
670 uint32_t current_address = request->start_address();
671 uint32_t instructions_added = 0;
672 const uint32_t max_instructions = std::min(request->count(), 1000u);
673
674 // Build set of breakpoint addresses for quick lookup
675 auto breakpoints =
676 bp_manager.GetBreakpoints(emu::BreakpointManager::CpuType::CPU_65816);
677 std::unordered_set<uint32_t> bp_addresses;
678 for (const auto& bp : breakpoints) {
679 if (bp.enabled && bp.type == emu::BreakpointManager::Type::EXECUTE) {
680 bp_addresses.insert(bp.address);
681 }
682 }
683
684 while (instructions_added < max_instructions) {
685 // Disassemble the instruction using our new disassembler
686 auto instruction =
687 disassembler.Disassemble(current_address, read_byte, m_flag, x_flag);
688
689 auto* line = response->add_lines();
690 line->set_address(instruction.address);
691 line->set_opcode(instruction.opcode);
692 line->set_mnemonic(instruction.mnemonic);
693 line->set_operand_str(instruction.operand_str);
694 line->set_size(instruction.size);
695 line->set_execution_count(0); // Would need DisassemblyViewer integration
696
697 // Add operand bytes
698 for (const auto& byte : instruction.operands) {
699 line->add_operands(byte);
700 }
701
702 // Check if this address has an execute breakpoint
703 line->set_is_breakpoint(bp_addresses.count(current_address) > 0);
704
705 current_address += instruction.size;
706 instructions_added++;
707 }
708
709 return grpc::Status::OK;
710}
711
713 grpc::ServerContext* context, const TraceRequest* request,
714 TraceResponse* response) {
715 // TODO: Implement execution trace (requires trace buffer in CPU)
716 return grpc::Status(grpc::StatusCode::UNIMPLEMENTED,
717 "Execution trace not yet implemented");
718}
719
720// Symbol Management
721grpc::Status EmulatorServiceImpl::LoadSymbols(grpc::ServerContext* context,
722 const SymbolFileRequest* request,
723 CommandResponse* response) {
724 std::string filepath = request->filepath();
725 if (filepath.empty()) {
726 response->set_success(false);
727 response->set_message("No filepath specified");
728 return grpc::Status::OK;
729 }
730
731 // Convert proto enum to SymbolFormat
733 switch (request->format()) {
734 case SymbolFormat::ASAR:
736 break;
737 case SymbolFormat::WLA_DX:
739 break;
740 case SymbolFormat::MESEN:
742 break;
743 default:
745 break;
746 }
747
748 // Check if it's a directory (for loading multiple ASM files)
749 std::filesystem::path path(filepath);
750 absl::Status status;
751
752 if (std::filesystem::is_directory(path)) {
753 status = symbol_provider_.LoadAsarAsmDirectory(filepath);
754 } else {
755 status = symbol_provider_.LoadSymbolFile(filepath, format);
756 }
757
758 if (status.ok()) {
759 response->set_success(true);
760 response->set_message(
761 absl::StrFormat("Loaded %zu symbols from %s",
762 symbol_provider_.GetSymbolCount(), filepath));
763 } else {
764 response->set_success(false);
765 response->set_message(std::string(status.message().data(), status.message().size()));
766 }
767
768 return grpc::Status::OK;
769}
770
772 grpc::ServerContext* context, const SymbolLookupRequest* request,
773 SymbolLookupResponse* response) {
774 std::string symbol_name = request->symbol_name();
775
776 // First try exact match
777 auto symbol = symbol_provider_.FindSymbol(symbol_name);
778 if (symbol) {
779 response->set_found(true);
780 response->set_symbol_name(symbol->name);
781 response->set_address(symbol->address);
782 response->set_type(symbol->is_local ? "local_label" : "label");
783 response->set_description(symbol->comment);
784 return grpc::Status::OK;
785 }
786
787 // Try wildcard match if the name contains wildcards
788 if (symbol_name.find('*') != std::string::npos ||
789 symbol_name.find('?') != std::string::npos) {
790 auto matches = symbol_provider_.FindSymbolsMatching(symbol_name);
791 if (!matches.empty()) {
792 // Return the first match
793 const auto& first_match = matches[0];
794 response->set_found(true);
795 response->set_symbol_name(first_match.name);
796 response->set_address(first_match.address);
797 response->set_type(first_match.is_local ? "local_label" : "label");
798 response->set_description(
799 absl::StrFormat("First of %zu matches", matches.size()));
800 return grpc::Status::OK;
801 }
802 }
803
804 response->set_found(false);
805 response->set_symbol_name(symbol_name);
806 response->set_description("Symbol not found");
807 return grpc::Status::OK;
808}
809
810grpc::Status EmulatorServiceImpl::GetSymbolAt(grpc::ServerContext* context,
811 const AddressRequest* request,
812 SymbolLookupResponse* response) {
813 uint32_t address = request->address();
814
815 // Try exact match first
816 auto symbol = symbol_provider_.GetSymbol(address);
817 if (symbol) {
818 response->set_found(true);
819 response->set_symbol_name(symbol->name);
820 response->set_address(symbol->address);
821 response->set_type(symbol->is_local ? "local_label" : "label");
822 response->set_description(symbol->comment);
823 return grpc::Status::OK;
824 }
825
826 // Try to find nearest symbol
827 auto nearest = symbol_provider_.GetNearestSymbol(address);
828 if (nearest && (address - nearest->address) <= 0x100) {
829 // Within reasonable offset range (256 bytes)
830 uint32_t offset = address - nearest->address;
831 response->set_found(true);
832 response->set_symbol_name(
833 absl::StrFormat("%s+$%X", nearest->name, offset));
834 response->set_address(address);
835 response->set_type("offset");
836 response->set_description(
837 absl::StrFormat("Offset $%X from %s", offset, nearest->name));
838 return grpc::Status::OK;
839 }
840
841 response->set_found(false);
842 response->set_address(address);
843 response->set_description(absl::StrFormat("No symbol at $%06X", address));
844 return grpc::Status::OK;
845}
846
847// Debug Session
849 grpc::ServerContext* context, const DebugSessionRequest* request,
850 DebugSessionResponse* response) {
851 if (!emulator_) {
852 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
853 "Emulator not initialized.");
854 }
855
856 // Enable debugging mode
858
859 response->set_success(true);
860 response->set_session_id(request->session_name());
861 response->set_message(
862 absl::StrFormat("Debug session '%s' created", request->session_name()));
863 return grpc::Status::OK;
864}
865
867 grpc::ServerContext* context, const Empty* request,
868 DebugStatusResponse* response) {
869 if (!emulator_) {
870 return grpc::Status(grpc::StatusCode::UNAVAILABLE,
871 "Emulator not initialized.");
872 }
873
874 response->set_is_running(emulator_->running());
875 response->set_is_paused(!emulator_->running());
876 response->set_fps(emulator_->GetCurrentFPS());
877 response->set_cycles(emulator_->GetCurrentCycle());
878
879 // Get active counts
880 auto breakpoints = emulator_->breakpoint_manager().GetAllBreakpoints();
881 uint32_t active_bp_count = 0;
882 for (const auto& bp : breakpoints) {
883 if (bp.enabled)
884 active_bp_count++;
885 }
886 response->set_active_breakpoints(active_bp_count);
887 response->set_active_watchpoints(
888 0); // TODO: When WatchpointManager is integrated
889
890 // Fill CPU state
891 auto& cpu = emulator_->snes().cpu();
892 auto* cpu_state = response->mutable_cpu_state();
893 cpu_state->set_a(cpu.A);
894 cpu_state->set_x(cpu.X);
895 cpu_state->set_y(cpu.Y);
896 cpu_state->set_sp(cpu.SP());
897 cpu_state->set_pc(cpu.PC);
898 cpu_state->set_db(cpu.DB);
899 cpu_state->set_pb(cpu.PB);
900 cpu_state->set_d(cpu.D);
901 cpu_state->set_status(cpu.status);
902 cpu_state->set_flag_n(cpu.GetNegativeFlag());
903 cpu_state->set_flag_v(cpu.GetOverflowFlag());
904 cpu_state->set_flag_d(cpu.GetDecimalFlag());
905 cpu_state->set_flag_i(cpu.GetInterruptFlag());
906 cpu_state->set_flag_z(cpu.GetZeroFlag());
907 cpu_state->set_flag_c(cpu.GetCarryFlag());
908 cpu_state->set_cycles(emulator_->GetCurrentCycle());
909
910 // Last breakpoint hit
911 auto* last_hit = emulator_->breakpoint_manager().GetLastHit();
912 if (last_hit) {
913 auto* bp_info = response->mutable_last_breakpoint_hit();
914 bp_info->set_id(last_hit->id);
915 bp_info->set_address(last_hit->address);
916 bp_info->set_type(ToProtoBreakpointType(last_hit->type));
917 bp_info->set_cpu(ToProtoCpuType(last_hit->cpu));
918 bp_info->set_enabled(last_hit->enabled);
919 bp_info->set_hit_count(last_hit->hit_count);
920 }
921
922 return grpc::Status::OK;
923}
924
925} // namespace yaze::agent
grpc::Status RemoveBreakpoint(grpc::ServerContext *context, const BreakpointIdRequest *request, CommandResponse *response) override
grpc::Status GetWatchpointHistory(grpc::ServerContext *context, const WatchpointHistoryRequest *request, WatchpointHistoryResponse *response) override
grpc::Status Stop(grpc::ServerContext *context, const Empty *request, CommandResponse *response) override
EmulatorServiceImpl(yaze::emu::Emulator *emulator)
grpc::Status PressButtons(grpc::ServerContext *context, const ButtonRequest *request, CommandResponse *response) override
grpc::Status ListWatchpoints(grpc::ServerContext *context, const Empty *request, WatchpointListResponse *response) override
grpc::Status StepOver(grpc::ServerContext *context, const Empty *request, StepResponse *response) override
grpc::Status GetSymbolAt(grpc::ServerContext *context, const AddressRequest *request, SymbolLookupResponse *response) override
grpc::Status ListBreakpoints(grpc::ServerContext *context, const Empty *request, BreakpointListResponse *response) override
grpc::Status GetExecutionTrace(grpc::ServerContext *context, const TraceRequest *request, TraceResponse *response) override
grpc::Status ResolveSymbol(grpc::ServerContext *context, const SymbolLookupRequest *request, SymbolLookupResponse *response) override
grpc::Status Pause(grpc::ServerContext *context, const Empty *request, CommandResponse *response) override
grpc::Status StepOut(grpc::ServerContext *context, const Empty *request, StepResponse *response) override
grpc::Status ReleaseButtons(grpc::ServerContext *context, const ButtonRequest *request, CommandResponse *response) override
grpc::Status RunToBreakpoint(grpc::ServerContext *context, const Empty *request, BreakpointHitResponse *response) override
grpc::Status Reset(grpc::ServerContext *context, const Empty *request, CommandResponse *response) override
grpc::Status LoadSymbols(grpc::ServerContext *context, const SymbolFileRequest *request, CommandResponse *response) override
grpc::Status WriteMemory(grpc::ServerContext *context, const MemoryWriteRequest *request, CommandResponse *response) override
grpc::Status GetDisassembly(grpc::ServerContext *context, const DisassemblyRequest *request, DisassemblyResponse *response) override
grpc::Status CreateDebugSession(grpc::ServerContext *context, const DebugSessionRequest *request, DebugSessionResponse *response) override
yaze::emu::debug::SymbolProvider symbol_provider_
grpc::Status HoldButtons(grpc::ServerContext *context, const ButtonHoldRequest *request, CommandResponse *response) override
grpc::Status ReadMemory(grpc::ServerContext *context, const MemoryRequest *request, MemoryResponse *response) override
grpc::Status AddBreakpoint(grpc::ServerContext *context, const BreakpointRequest *request, BreakpointResponse *response) override
grpc::Status Start(grpc::ServerContext *context, const Empty *request, CommandResponse *response) override
grpc::Status RemoveWatchpoint(grpc::ServerContext *context, const WatchpointIdRequest *request, CommandResponse *response) override
grpc::Status StepInstruction(grpc::ServerContext *context, const Empty *request, StepResponse *response) override
grpc::Status SetBreakpointEnabled(grpc::ServerContext *context, const BreakpointStateRequest *request, CommandResponse *response) override
yaze::emu::debug::StepController step_controller_
grpc::Status GetGameState(grpc::ServerContext *context, const GameStateRequest *request, GameStateResponse *response) override
grpc::Status AddWatchpoint(grpc::ServerContext *context, const WatchpointRequest *request, WatchpointResponse *response) override
grpc::Status GetDebugStatus(grpc::ServerContext *context, const Empty *request, DebugStatusResponse *response) override
grpc::Status Resume(grpc::ServerContext *context, const Empty *request, CommandResponse *response) override
Manages CPU and SPC700 breakpoints for debugging.
void RemoveBreakpoint(uint32_t id)
Remove a breakpoint by ID.
void SetEnabled(uint32_t id, bool enabled)
Enable or disable a breakpoint.
std::vector< Breakpoint > GetAllBreakpoints() const
Get all breakpoints.
const Breakpoint * GetLastHit() const
Get the last breakpoint that was hit.
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string &condition="", const std::string &description="")
Add a new breakpoint.
A class for emulating and debugging SNES games.
Definition emulator.h:39
BreakpointManager & breakpoint_manager()
Definition emulator.h:116
double GetCurrentFPS() const
Definition emulator.h:126
void set_debugging(bool debugging)
Definition emulator.h:120
uint64_t GetCurrentCycle()
Definition emulator.h:127
void StepSingleInstruction()
Definition emulator.h:130
input::InputManager & input_manager()
Definition emulator.h:118
void set_running(bool running)
Definition emulator.h:60
bool is_snes_initialized() const
Definition emulator.h:122
auto snes() -> Snes &
Definition emulator.h:58
auto running() const -> bool
Definition emulator.h:59
65816 CPU disassembler for debugging and ROM hacking
DisassembledInstruction Disassemble(uint32_t address, MemoryReader read_byte, bool m_flag=true, bool x_flag=true) const
Disassemble a single instruction.
void SetMemoryReader(MemoryReader reader)
void SetPcGetter(PcGetter getter)
StepResult StepOver(uint32_t max_instructions=1000000)
Step over the current instruction.
size_t GetCallDepth() const
Get the current call depth.
void SetSingleStepper(SingleStepper stepper)
StepResult StepOut(uint32_t max_instructions=1000000)
Step out of the current subroutine.
size_t GetSymbolCount() const
Get total number of loaded symbols.
absl::Status LoadAsarAsmDirectory(const std::string &directory_path)
Load symbols from a directory of ASM files.
absl::Status LoadSymbolFile(const std::string &path, SymbolFormat format=SymbolFormat::kAuto)
Load symbols from a .sym file (various formats)
std::optional< Symbol > GetNearestSymbol(uint32_t address) const
Get nearest symbol at or before an address.
std::optional< Symbol > GetSymbol(uint32_t address) const
Get full symbol info for an address.
std::optional< Symbol > FindSymbol(const std::string &name) const
Find symbol by name.
std::vector< Symbol > FindSymbolsMatching(const std::string &pattern) const
Find symbols matching a pattern (supports wildcards)
void PressButton(SnesButton button)
void ReleaseButton(SnesButton button)
emu::BreakpointManager::CpuType ToCpuType(CpuType proto_cpu)
CpuType ToProtoCpuType(emu::BreakpointManager::CpuType cpu)
BreakpointType ToProtoBreakpointType(emu::BreakpointManager::Type type)
emu::BreakpointManager::Type ToBreakpointType(BreakpointType proto_type)
SymbolFormat
Supported symbol file formats.
SnesButton
SNES controller button mapping (platform-agnostic)