yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom_service_impl.cc
Go to the documentation of this file.
2
3#ifdef YAZE_WITH_GRPC
4
5#include <chrono>
6
7#include "absl/strings/str_format.h"
9#include "rom/rom.h"
10#include "util/json.h"
11#include "zelda3/dungeon/room.h"
12#include "zelda3/game_data.h"
15
16// Proto namespace alias for convenience
17namespace rom_svc = ::yaze::proto;
18
19namespace yaze {
20
21namespace net {
22
23namespace {
24
25std::string GenerateProposalId() {
26 auto now = std::chrono::system_clock::now();
27 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
28 now.time_since_epoch())
29 .count();
30 return absl::StrFormat("proposal_%lld", ms);
31}
32
33std::string BuildRoomRawJson(const zelda3::Room& room) {
34 nlohmann::json payload = nlohmann::json::object();
35 payload["room_id"] = room.id();
36 payload["layout"] = room.layout_id();
37 payload["floor1"] = room.floor1();
38 payload["floor2"] = room.floor2();
39 payload["blockset"] = room.blockset();
40 payload["spriteset"] = room.spriteset();
41 payload["palette"] = room.palette();
42 payload["effect"] = static_cast<int>(room.effect());
43 payload["tag1"] = static_cast<int>(room.tag1());
44 payload["tag2"] = static_cast<int>(room.tag2());
45
46 auto objects = nlohmann::json::array();
47 for (const auto& obj : room.GetTileObjects()) {
48 nlohmann::json entry = {
49 {"id", static_cast<int>(obj.id_)},
50 {"x", obj.x()},
51 {"y", obj.y()},
52 {"size", obj.size()},
53 {"layer", obj.GetLayerValue()},
54 {"size_x_bits", obj.size_x_bits_},
55 {"size_y_bits", obj.size_y_bits_},
56 };
57 objects.push_back(entry);
58 }
59 payload["objects"] = objects;
60
61 auto doors = nlohmann::json::array();
62 for (const auto& door : room.GetDoors()) {
63 nlohmann::json entry = {
64 {"byte1", door.byte1},
65 {"byte2", door.byte2},
66 {"position", door.position},
67 {"direction", static_cast<int>(door.direction)},
68 {"type", static_cast<int>(door.type)},
69 };
70 doors.push_back(entry);
71 }
72 payload["doors"] = doors;
73
74 auto sprites = nlohmann::json::array();
75 for (const auto& sprite : room.GetSprites()) {
76 nlohmann::json entry = {
77 {"id", sprite.id()},
78 {"x", sprite.x()},
79 {"y", sprite.y()},
80 {"subtype", sprite.subtype()},
81 {"layer", sprite.layer()},
82 };
83 sprites.push_back(entry);
84 }
85 payload["sprites"] = sprites;
86
87 payload["encoded_objects"] = room.EncodeObjects();
88 payload["encoded_sprites"] = room.EncodeSprites();
89
90 return payload.dump();
91}
92
93absl::Status LoadGameDataForRom(Rom* rom, zelda3::GameData* data,
94 const zelda3::LoadOptions& options) {
95 if (!rom || !rom->is_loaded()) {
96 return absl::FailedPreconditionError("ROM not loaded");
97 }
98 if (!data) {
99 return absl::InvalidArgumentError("GameData is null");
100 }
101 data->set_rom(rom);
102 return zelda3::LoadGameData(*rom, *data, options);
103}
104
105absl::Status LoadMetadataForRom(Rom* rom, zelda3::GameData* data) {
106 if (!rom || !rom->is_loaded()) {
107 return absl::FailedPreconditionError("ROM not loaded");
108 }
109 if (!data) {
110 return absl::InvalidArgumentError("GameData is null");
111 }
112 data->set_rom(rom);
113 return zelda3::LoadMetadata(*rom, *data);
114}
115
116} // namespace
117
118RomServiceImpl::RomServiceImpl(RomGetter rom_getter,
119 RomVersionManager* version_manager,
120 ProposalApprovalManager* approval_manager)
121 : rom_getter_(rom_getter),
122 version_mgr_(version_manager),
123 approval_mgr_(approval_manager) {}
124
125void RomServiceImpl::SetConfig(const Config& config) {
126 config_ = config;
127}
128
129grpc::Status RomServiceImpl::ReadBytes(grpc::ServerContext* context,
130 const rom_svc::ReadBytesRequest* request,
131 rom_svc::ReadBytesResponse* response) {
132 Rom* rom = rom_getter_();
133 if (!rom || !rom->is_loaded()) {
134 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
135 "ROM not loaded");
136 }
137
138 uint32_t offset = request->offset();
139 uint32_t length = request->length();
140
141 // Validate range
142 if (offset + length > rom->size()) {
143 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
144 absl::StrFormat("Read beyond ROM: 0x%X+%d > %d", offset,
145 length, rom->size()));
146 }
147
148 // Read data
149 const auto* data = rom->data() + offset;
150 response->set_data(data, length);
151
152 return grpc::Status::OK;
153}
154
155grpc::Status RomServiceImpl::WriteBytes(
156 grpc::ServerContext* context, const rom_svc::WriteBytesRequest* request,
157 rom_svc::WriteBytesResponse* response) {
158 Rom* rom = rom_getter_();
159 if (!rom || !rom->is_loaded()) {
160 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
161 "ROM not loaded");
162 }
163
164 uint32_t offset = request->offset();
165 const std::string& data = request->data();
166
167 // Validate range
168 if (offset + data.size() > rom->size()) {
169 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
170 absl::StrFormat("Write beyond ROM: 0x%X+%zu > %d",
171 offset, data.size(), rom->size()));
172 }
173
174 if (config_.require_approval_for_writes || request->require_approval()) {
175 if (!approval_mgr_) {
176 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
177 "Approval manager not initialized");
178 }
179
180 std::string description = request->require_approval()
181 ? "WriteBytes (proposal)"
182 : "WriteBytes (approval required)";
183 if (!request->data().empty()) {
184 description = absl::StrFormat("WriteBytes at 0x%X (%zu bytes)", offset,
185 request->data().size());
186 }
187
188 nlohmann::json proposal_data = {{"description", description},
189 {"type", "write_bytes"},
190 {"offset", offset},
191 {"size", request->data().size()}};
192 std::vector<uint8_t> bytes(request->data().begin(), request->data().end());
193 proposal_data["data"] = bytes;
194
195 std::string proposal_id = GenerateProposalId();
196 auto status = approval_mgr_->SubmitProposal(proposal_id, "grpc",
197 description, proposal_data);
198 if (!status.ok()) {
199 return grpc::Status(grpc::StatusCode::INTERNAL,
200 std::string(status.message()));
201 }
202
203 response->set_success(true);
204 response->set_proposal_id(proposal_id);
205 return grpc::Status::OK;
206 }
207
208 // Create auto-snapshot if enabled
209 auto status = MaybeCreateSnapshot(absl::StrFormat(
210 "Auto-snapshot before write at 0x%X (%zu bytes)", offset, data.size()));
211 if (!status.ok()) {
212 return grpc::Status(
213 grpc::StatusCode::INTERNAL,
214 "Failed to create safety snapshot: " + std::string(status.message()));
215 }
216
217 // Perform the write
218 std::vector<uint8_t> bytes(data.begin(), data.end());
219 auto write_status =
220 rom->WriteVector(static_cast<int>(offset), std::move(bytes));
221 if (!write_status.ok()) {
222 return grpc::Status(grpc::StatusCode::INTERNAL,
223 std::string(write_status.message()));
224 }
225 response->set_success(true);
226
227 return grpc::Status::OK;
228}
229
230grpc::Status RomServiceImpl::GetRomInfo(
231 grpc::ServerContext* context, const rom_svc::GetRomInfoRequest* request,
232 rom_svc::GetRomInfoResponse* response) {
233 Rom* rom = rom_getter_();
234 if (!rom || !rom->is_loaded()) {
235 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
236 "ROM not loaded");
237 }
238
239 response->set_title(rom->title());
240 response->set_size(rom->size());
241
242 auto metadata = std::make_unique<zelda3::GameData>(rom);
243 if (LoadMetadataForRom(rom, metadata.get()).ok()) {
244 response->set_version(metadata->version == zelda3_version::JP ? "JP" : "US");
245 }
246 response->set_is_expanded(rom->size() > 0x200000);
247
248 return grpc::Status::OK;
249}
250
251grpc::Status RomServiceImpl::ReadOverworldMap(
252 grpc::ServerContext* context,
253 const rom_svc::ReadOverworldMapRequest* request,
254 rom_svc::ReadOverworldMapResponse* response) {
255 Rom* rom = rom_getter_();
256 if (!rom || !rom->is_loaded()) {
257 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
258 "ROM not loaded");
259 }
260
261 uint32_t map_id = request->map_id();
262 if (map_id >= 160) {
263 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
264 "Invalid map_id (0-159)");
265 }
266
267 auto metadata = std::make_unique<zelda3::GameData>(rom);
268 auto metadata_status = LoadMetadataForRom(rom, metadata.get());
269 if (!metadata_status.ok()) {
270 return grpc::Status(grpc::StatusCode::INTERNAL,
271 std::string(metadata_status.message()));
272 }
273
274 // Instantiate Overworld parser (heavy: loads/decompresses all maps).
275 zelda3::Overworld overworld(rom, metadata.get());
276 auto status = overworld.Load(rom);
277 if (!status.ok()) {
278 return grpc::Status(grpc::StatusCode::INTERNAL,
279 "Failed to load Overworld: " +
280 std::string(status.message()));
281 }
282
283 int world = 0;
284 int local_id = map_id;
285 if (map_id >= 128) {
286 world = 2;
287 local_id -= 128;
288 } else if (map_id >= 64) {
289 world = 1;
290 local_id -= 64;
291 }
292
293 // Access tile data (map tiles are stored as [x][y])
294 auto& blockset = overworld.GetMapTiles(world);
295
296 // Calculate global coordinates
297 // All worlds (Light, Dark, Special) are laid out in 8-map columns
298 int maps_per_row = 8;
299 int map_col = local_id % maps_per_row;
300 int map_row = local_id / maps_per_row;
301 int global_start_x = map_col * 32;
302 int global_start_y = map_row * 32;
303
304 response->set_map_id(map_id);
305
306 if (blockset.empty() || blockset[0].empty()) {
307 return grpc::Status(grpc::StatusCode::INTERNAL,
308 "Overworld tiles not initialized");
309 }
310
311 // Validate bounds (outer = x, inner = y)
312 if (global_start_x + 32 > static_cast<int>(blockset.size()) ||
313 global_start_y + 32 > static_cast<int>(blockset[0].size())) {
314 return grpc::Status(
315 grpc::StatusCode::INTERNAL,
316 absl::StrFormat("Map bounds error: %d (local %d) -> (%d, %d)", map_id,
317 local_id, global_start_x, global_start_y));
318 }
319
320 // Flatten 32x32 area from global grid
321 for (int y = 0; y < 32; ++y) {
322 for (int x = 0; x < 32; ++x) {
323 response->add_tile16_data(
324 blockset[global_start_x + x][global_start_y + y]);
325 }
326 }
327
328 return grpc::Status::OK;
329}
330
331grpc::Status RomServiceImpl::WriteOverworldTile(
332 grpc::ServerContext* context,
333 const rom_svc::WriteOverworldTileRequest* request,
334 rom_svc::WriteOverworldTileResponse* response) {
335 Rom* rom = rom_getter_();
336 if (!rom || !rom->is_loaded()) {
337 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
338 "ROM not loaded");
339 }
340
341 uint32_t map_id = request->map_id();
342 uint32_t x = request->x();
343 uint32_t y = request->y();
344 uint32_t tile_id = request->tile16_id();
345
346 if (map_id >= 160 || x >= 32 || y >= 32) {
347 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE, "Invalid coordinates");
348 }
349
350 if (config_.require_approval_for_writes || request->require_approval()) {
351 if (!approval_mgr_) {
352 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
353 "Approval manager not initialized");
354 }
355
356 std::string description = request->description().empty()
357 ? absl::StrFormat(
358 "Update OW tile: Map %d (%d,%d) -> %d",
359 map_id, x, y, tile_id)
360 : request->description();
361 nlohmann::json proposal_data = {{"description", description},
362 {"type", "overworld_tile_write"},
363 {"map_id", map_id},
364 {"x", x},
365 {"y", y},
366 {"tile16_id", tile_id}};
367
368 std::string proposal_id = GenerateProposalId();
369 auto status = approval_mgr_->SubmitProposal(proposal_id, "grpc",
370 description, proposal_data);
371 if (!status.ok()) {
372 return grpc::Status(grpc::StatusCode::INTERNAL,
373 std::string(status.message()));
374 }
375
376 response->set_success(true);
377 response->set_proposal_id(proposal_id);
378 return grpc::Status::OK;
379 }
380
381 auto metadata = std::make_unique<zelda3::GameData>(rom);
382 auto metadata_status = LoadMetadataForRom(rom, metadata.get());
383 if (!metadata_status.ok()) {
384 return grpc::Status(grpc::StatusCode::INTERNAL,
385 std::string(metadata_status.message()));
386 }
387
388 // Load Overworld
389 zelda3::Overworld overworld(rom, metadata.get());
390 auto status = overworld.Load(rom);
391 if (!status.ok()) {
392 return grpc::Status(grpc::StatusCode::INTERNAL,
393 "Failed to load Overworld");
394 }
395
396 int world = 0;
397 int local_id = map_id;
398 if (map_id >= 128) {
399 world = 2;
400 local_id -= 128;
401 } else if (map_id >= 64) {
402 world = 1;
403 local_id -= 64;
404 }
405
406 // Calculate global coordinates for SetTile
407 int maps_per_row = 8;
408 int map_col = local_id % maps_per_row;
409 int map_row = local_id / maps_per_row;
410 int global_x = (map_col * 32) + x;
411 int global_y = (map_row * 32) + y;
412
413 // Update tile in the map tiles (map tiles are stored as [x][y])
414 auto& blockset = overworld.GetMapTiles(world);
415 if (blockset.empty() || blockset[0].empty()) {
416 return grpc::Status(grpc::StatusCode::INTERNAL,
417 "Overworld tiles not initialized");
418 }
419 if (global_x >= static_cast<int>(blockset.size()) ||
420 global_y >= static_cast<int>(blockset[0].size())) {
421 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
422 "Global coordinates out of range");
423 }
424 blockset[global_x][global_y] = static_cast<uint16_t>(tile_id);
425
426 // Create snapshot
427 auto snap_status = MaybeCreateSnapshot(
428 absl::StrFormat("Update OW tile: Map %d (%d,%d) -> %d", map_id, x, y, tile_id));
429 if (!snap_status.ok()) {
430 return grpc::Status(grpc::StatusCode::INTERNAL, "Snapshot failed");
431 }
432
433 // Save back to ROM
434 status = overworld.Save(rom);
435 if (!status.ok()) {
436 return grpc::Status(grpc::StatusCode::INTERNAL,
437 "Failed to save Overworld: " + std::string(status.message()));
438 }
439
440 response->set_success(true);
441 return grpc::Status::OK;
442}
443
444grpc::Status RomServiceImpl::ReadDungeonRoom(
445 grpc::ServerContext* context,
446 const rom_svc::ReadDungeonRoomRequest* request,
447 rom_svc::ReadDungeonRoomResponse* response) {
448 Rom* rom = rom_getter_();
449 if (!rom || !rom->is_loaded()) {
450 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
451 }
452
453 uint32_t room_id = request->room_id();
454 if (room_id >= 296) {
455 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE, "Invalid room_id");
456 }
457
458 auto game_data = std::make_unique<zelda3::GameData>(rom);
459 zelda3::LoadOptions options;
460 options.load_graphics = true;
461 options.load_palettes = true;
462 options.load_gfx_groups = true;
463 options.populate_metadata = true;
464 options.expand_rom = false;
465 auto game_status = LoadGameDataForRom(rom, game_data.get(), options);
466 if (!game_status.ok()) {
467 return grpc::Status(grpc::StatusCode::INTERNAL,
468 std::string(game_status.message()));
469 }
470
471 // Load Room
472 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
473 room.SetGameData(game_data.get());
474 room.LoadObjects();
475 room.LoadSprites();
476 room.RenderRoomGraphics();
477
478 response->set_room_id(room_id);
479
480 // Get tile indices from BG1 buffer
481 // Note: Dungeon rooms can be large, but BG buffer is fixed size?
482 // BackgroundBuffer default is 512x512 pixels (64x64 tiles)
483 // Check room dimensions? For now assuming standard buffer
484 const auto& buffer = room.bg1_buffer().buffer();
485
486 for (uint16_t tile : buffer) {
487 response->add_tile16_data(tile);
488 }
489
490 const std::string raw_json = BuildRoomRawJson(room);
491 if (!raw_json.empty()) {
492 response->set_raw_data(raw_json);
493 }
494
495 return grpc::Status::OK;
496}
497
498grpc::Status RomServiceImpl::WriteDungeonTile(
499 grpc::ServerContext* context,
500 const rom_svc::WriteDungeonTileRequest* request,
501 rom_svc::WriteDungeonTileResponse* response) {
502 if (!approval_mgr_) {
503 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
504 "Approval manager not initialized");
505 }
506
507 std::string description = request->description().empty()
508 ? absl::StrFormat(
509 "Dungeon tile write: Room %d (%d,%d) -> %d",
510 request->room_id(), request->x(),
511 request->y(), request->tile16_id())
512 : request->description();
513 nlohmann::json proposal_data = {{"description", description},
514 {"type", "dungeon_tile_write"},
515 {"room_id", request->room_id()},
516 {"x", request->x()},
517 {"y", request->y()},
518 {"tile16_id", request->tile16_id()}};
519
520 std::string proposal_id = GenerateProposalId();
521 auto status = approval_mgr_->SubmitProposal(proposal_id, "grpc",
522 description, proposal_data);
523 if (!status.ok()) {
524 return grpc::Status(grpc::StatusCode::INTERNAL,
525 std::string(status.message()));
526 }
527
528 response->set_success(true);
529 response->set_proposal_id(proposal_id);
530 return grpc::Status::OK;
531}
532
533grpc::Status RomServiceImpl::ReadSprite(
534 grpc::ServerContext* context, const rom_svc::ReadSpriteRequest* request,
535 rom_svc::ReadSpriteResponse* response) {
536 Rom* rom = rom_getter_();
537 if (!rom || !rom->is_loaded()) {
538 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, "ROM not loaded");
539 }
540
541 uint32_t sprite_id = request->sprite_id();
542 if (sprite_id >= 256) {
543 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE, "Invalid sprite_id (0-255)");
544 }
545
546 // ALttP Sprite Property Tables (PC Offsets)
547 const uint32_t kSpriteHPTable = 0x6B173;
548 const uint32_t kSpriteDamageTable = 0x6B266;
549 const uint32_t kSpritePaletteTable = 0x6B35B;
550 const uint32_t kSpritePropertiesTable = 0x6B450; // 4 bytes per sprite
551
552 response->set_sprite_id(sprite_id);
553
554 auto read_byte = [&](uint32_t offset, uint8_t* out) -> absl::Status {
555 auto val = rom->ReadByte(static_cast<int>(offset));
556 if (!val.ok()) {
557 return val.status();
558 }
559 *out = *val;
560 return absl::OkStatus();
561 };
562
563 std::vector<uint8_t> data;
564 data.reserve(7);
565 uint8_t value = 0;
566 auto status = read_byte(kSpriteHPTable + sprite_id, &value);
567 if (!status.ok()) {
568 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
569 std::string(status.message()));
570 }
571 data.push_back(value);
572 status = read_byte(kSpriteDamageTable + sprite_id, &value);
573 if (!status.ok()) {
574 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
575 std::string(status.message()));
576 }
577 data.push_back(value);
578 status = read_byte(kSpritePaletteTable + sprite_id, &value);
579 if (!status.ok()) {
580 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
581 std::string(status.message()));
582 }
583 data.push_back(value);
584 for (int i = 0; i < 4; ++i) {
585 status =
586 read_byte(kSpritePropertiesTable + (sprite_id * 4) + i, &value);
587 if (!status.ok()) {
588 return grpc::Status(grpc::StatusCode::OUT_OF_RANGE,
589 std::string(status.message()));
590 }
591 data.push_back(value);
592 }
593
594 response->set_sprite_data(data.data(), data.size());
595
596 return grpc::Status::OK;
597}
598
599grpc::Status RomServiceImpl::SubmitRomProposal(
600 grpc::ServerContext* context,
601 const rom_svc::SubmitRomProposalRequest* request,
602 rom_svc::SubmitRomProposalResponse* response) {
603 if (!approval_mgr_) {
604 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
605 "Approval manager not initialized");
606 }
607
608 std::string description = request->description();
609 if (description.empty()) {
610 description = "ROM proposal";
611 }
612 std::string username = request->username().empty() ? "grpc" : request->username();
613
614 nlohmann::json proposal_data = {{"description", description}};
615
616 if (request->has_write_bytes()) {
617 const auto& write = request->write_bytes();
618 proposal_data["type"] = "write_bytes";
619 proposal_data["offset"] = write.offset();
620 proposal_data["size"] = write.data().size();
621 std::vector<uint8_t> bytes(write.data().begin(), write.data().end());
622 proposal_data["data"] = bytes;
623 } else if (request->has_overworld_tile()) {
624 const auto& write = request->overworld_tile();
625 proposal_data["type"] = "overworld_tile_write";
626 proposal_data["map_id"] = write.map_id();
627 proposal_data["x"] = write.x();
628 proposal_data["y"] = write.y();
629 proposal_data["tile16_id"] = write.tile16_id();
630 } else if (request->has_dungeon_tile()) {
631 const auto& write = request->dungeon_tile();
632 proposal_data["type"] = "dungeon_tile_write";
633 proposal_data["room_id"] = write.room_id();
634 proposal_data["x"] = write.x();
635 proposal_data["y"] = write.y();
636 proposal_data["tile16_id"] = write.tile16_id();
637 } else {
638 return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
639 "No proposal payload provided");
640 }
641
642 std::string proposal_id = GenerateProposalId();
643 auto status = approval_mgr_->SubmitProposal(proposal_id, username, description,
644 proposal_data);
645 if (!status.ok()) {
646 return grpc::Status(grpc::StatusCode::INTERNAL,
647 std::string(status.message()));
648 }
649
650 response->set_success(true);
651 response->set_proposal_id(proposal_id);
652 return grpc::Status::OK;
653}
654grpc::Status RomServiceImpl::GetProposalStatus(
655 grpc::ServerContext* context,
656 const rom_svc::GetProposalStatusRequest* request,
657 rom_svc::GetProposalStatusResponse* response) {
658 if (!approval_mgr_) {
659 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
660 "Approval manager not initialized");
661 }
662
663 auto status_or = approval_mgr_->GetProposalStatus(request->proposal_id());
664 if (!status_or.ok()) {
665 return grpc::Status(grpc::StatusCode::NOT_FOUND,
666 std::string(status_or.status().message()));
667 }
668
669 const auto& status = *status_or;
670 response->set_proposal_id(status.proposal_id);
671 response->set_status(status.status);
672
673 int approvals = 0;
674 int rejections = 0;
675 for (const auto& [user, approved] : status.votes) {
676 response->add_voters(user);
677 if (approved) {
678 approvals++;
679 } else {
680 rejections++;
681 }
682 }
683 response->set_approval_count(approvals);
684 response->set_rejection_count(rejections);
685 return grpc::Status::OK;
686}
687grpc::Status RomServiceImpl::CreateSnapshot(
688 grpc::ServerContext* context, const rom_svc::CreateSnapshotRequest* request,
689 rom_svc::CreateSnapshotResponse* response) {
690 if (!version_mgr_) {
691 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
692 "Version manager not initialized");
693 }
694
695 auto result = version_mgr_->CreateSnapshot(
696 request->description(),
697 request->username().empty() ? "grpc" : request->username(),
698 request->is_checkpoint());
699 if (!result.ok()) {
700 response->set_success(false);
701 response->set_error(std::string(result.status().message()));
702 return grpc::Status::OK;
703 }
704
705 response->set_success(true);
706 response->set_snapshot_id(*result);
707 return grpc::Status::OK;
708}
709grpc::Status RomServiceImpl::RestoreSnapshot(
710 grpc::ServerContext* context,
711 const rom_svc::RestoreSnapshotRequest* request,
712 rom_svc::RestoreSnapshotResponse* response) {
713 if (!version_mgr_) {
714 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
715 "Version manager not initialized");
716 }
717
718 auto status = version_mgr_->RestoreSnapshot(request->snapshot_id());
719 if (!status.ok()) {
720 response->set_success(false);
721 response->set_error(std::string(status.message()));
722 return grpc::Status::OK;
723 }
724
725 response->set_success(true);
726 return grpc::Status::OK;
727}
728grpc::Status RomServiceImpl::ListSnapshots(
729 grpc::ServerContext* context, const rom_svc::ListSnapshotsRequest* request,
730 rom_svc::ListSnapshotsResponse* response) {
731 if (!version_mgr_) {
732 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
733 "Version manager not initialized");
734 }
735
736 auto snapshots = version_mgr_->GetSnapshots(false);
737 uint32_t max_results = request->max_results();
738 if (max_results > 0 && snapshots.size() > max_results) {
739 snapshots.resize(max_results);
740 }
741
742 for (const auto& snapshot : snapshots) {
743 auto* info = response->add_snapshots();
744 info->set_snapshot_id(snapshot.snapshot_id);
745 info->set_description(snapshot.description);
746 info->set_username(snapshot.creator);
747 info->set_timestamp(snapshot.timestamp);
748 info->set_is_checkpoint(snapshot.is_checkpoint);
749 info->set_is_safe_point(snapshot.is_safe_point);
750 info->set_size_bytes(snapshot.rom_data.size());
751 }
752
753 return grpc::Status::OK;
754}
755
756grpc::Status RomServiceImpl::ValidateRomLoaded() {
757 Rom* rom = rom_getter_();
758 if (!rom || !rom->is_loaded()) {
759 return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
760 "ROM not loaded");
761 }
762 return grpc::Status::OK;
763}
764
765absl::Status RomServiceImpl::MaybeCreateSnapshot(
766 const std::string& description) {
767 if (!config_.enable_version_management || !version_mgr_) {
768 return absl::OkStatus();
769 }
770 return version_mgr_->CreateSnapshot(description, "gRPC", false).status();
771}
772
773} // namespace net
774
775} // namespace yaze
776
777#endif // YAZE_WITH_GRPC
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:408
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
Definition rom.cc:548
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
auto title() const
Definition rom.h:137
Rom * rom()
Get the current ROM instance.
::yaze::zelda3::GameData * game_data()
Get the current game data instance.
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
Definition game_data.cc:123
absl::Status LoadMetadata(const Rom &rom, GameData &data)
Definition game_data.cc:182