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