18 : rom_(rom), game_data_(game_data) {}
24 return absl::FailedPreconditionError(
"ROM not loaded");
36 if (!data_status.ok()) {
43 snes_ = std::make_unique<emu::Snes>();
44 const std::vector<uint8_t>& rom_data =
rom_->
vector();
45 snes_->Init(rom_data);
55 return absl::OkStatus();
60 return absl::FailedPreconditionError(
"Service not initialized");
68 return absl::FailedPreconditionError(
"Service not initialized");
71 switch (request.
type) {
86 return absl::InvalidArgumentError(
"Unknown render target type");
91 const std::vector<RenderRequest>& requests) {
92 std::vector<RenderResult> results;
93 results.reserve(requests.size());
95 for (
const auto& request : requests) {
96 auto result =
Render(request);
98 results.push_back(std::move(*result));
102 error_result.
error = std::string(result.status().message());
103 results.push_back(std::move(error_result));
141 if (!handler_result.ok()) {
143 result.
error = std::string(handler_result.status().message());
146 int handler_addr = *handler_result;
149 int tilemap_pos = (req.
y * 0x80) + (req.
x * 2);
155 result.
error = std::string(status.message());
185 return absl::FailedPreconditionError(
"GameData not available");
189 if (palette_id >= dungeon_main_pal_group.size()) {
192 auto palette = dungeon_main_pal_group[palette_id];
207 auto status = drawer.
DrawObject(obj, bg1_buffer, bg2_buffer, dungeon_main_pal_group);
210 result.
error = std::string(status.message());
223 const auto& bg2_pixels = bg2_buffer.
bitmap().data();
224 const auto& bg1_pixels = bg1_buffer.
bitmap().data();
228 int src_idx = y * 512 + x;
231 uint8_t pixel = bg1_pixels[src_idx];
233 pixel = bg2_pixels[src_idx];
237 if (pixel > 0 && pixel < palette.size()) {
238 auto color = palette[pixel];
239 rgba_pixels[dst_idx + 0] = color.rgb().x;
240 rgba_pixels[dst_idx + 1] = color.rgb().y;
241 rgba_pixels[dst_idx + 2] = color.rgb().z;
242 rgba_pixels[dst_idx + 3] = 255;
245 rgba_pixels[dst_idx + 3] = 0;
263 result.
error =
"Sprite rendering not yet implemented";
271 result.
error =
"Full room rendering not yet implemented";
277 auto& ppu =
snes_->ppu();
286 if (palette < dungeon_main_pal_group.size()) {
287 auto base_palette = dungeon_main_pal_group[palette];
288 for (
size_t i = 0; i < base_palette.size() && i < 90; ++i) {
289 ppu.cgram[i] = base_palette[i].snes();
295 for (
int i = 0; i < 30; ++i) {
296 uint32_t addr = kSpriteAuxPc + i * 2;
309 std::vector<uint8_t> linear_data(gfx_buffer.begin(), gfx_buffer.end());
313 for (
size_t i = 0; i < planar_data.size() / 2 && i < 0x8000; ++i) {
314 ppu.vram[i] = planar_data[i * 2] | (planar_data[i * 2 + 1] << 8);
318 snes_->Write(0x002105, 0x09);
319 snes_->Write(0x002107, 0x40);
320 snes_->Write(0x002108, 0x48);
321 snes_->Write(0x00210B, 0x00);
322 snes_->Write(0x00212C, 0x03);
323 snes_->Write(0x002100, 0x0F);
331 auto& ppu =
snes_->ppu();
335 if (palette_id >= 0 &&
336 palette_id <
static_cast<int>(dungeon_main_pal_group.size())) {
337 auto palette = dungeon_main_pal_group[palette_id];
338 for (
size_t i = 0; i < palette.size() && i < 90; ++i) {
339 ppu.cgram[i] = palette[i].snes();
350 for (
int i = 0; i < 11; ++i) {
353 uint8_t lo = wram_addr & 0xFF;
354 uint8_t mid = (wram_addr >> 8) & 0xFF;
355 uint8_t hi = (wram_addr >> 16) & 0xFF;
358 snes_->Write(0x7E0000 | zp_addr, lo);
359 snes_->Write(0x7E0000 | (zp_addr + 1), mid);
360 snes_->Write(0x7E0000 | (zp_addr + 2), hi);
372 auto& apu =
snes_->apu();
373 apu.out_ports_[0] = 0xAA;
374 apu.out_ports_[1] = 0xBB;
375 apu.out_ports_[2] = 0x00;
376 apu.out_ports_[3] = 0x00;
380 int object_id,
int* data_offset) {
382 uint32_t data_table_snes = 0;
383 uint32_t handler_table_snes = 0;
385 if (object_id < 0x100) {
388 }
else if (object_id < 0x200) {
400 uint32_t data_table_pc =
SnesToPc(data_table_snes);
401 uint32_t handler_table_pc =
SnesToPc(handler_table_snes);
403 if (data_table_pc + 1 >=
rom_->
size() ||
404 handler_table_pc + 1 >=
rom_->
size()) {
405 return absl::OutOfRangeError(
"Object ID out of bounds");
408 *data_offset = rom_data[data_table_pc] | (rom_data[data_table_pc + 1] << 8);
410 rom_data[handler_table_pc] | (rom_data[handler_table_pc + 1] << 8);
412 if (handler_addr == 0x0000) {
413 return absl::NotFoundError(
"Object has no drawing routine");
422 auto& cpu =
snes_->cpu();
436 const uint16_t trap_addr = 0xFF00;
437 snes_->Write(0x01FF00, 0xDB);
440 uint16_t sp = cpu.SP();
441 snes_->Write(0x010000 | sp--, 0x01);
442 snes_->Write(0x010000 | sp--, (trap_addr - 1) >> 8);
443 snes_->Write(0x010000 | sp--, (trap_addr - 1) & 0xFF);
446 cpu.PC = handler_addr;
449 int max_opcodes = 100000;
451 auto& apu =
snes_->apu();
453 while (opcodes < max_opcodes) {
454 uint32_t current_addr = (cpu.PB << 16) | cpu.PC;
455 uint8_t current_opcode =
snes_->Read(current_addr);
456 if (current_opcode == 0xDB) {
461 if ((opcodes & 0x3F) == 0) {
462 apu.out_ports_[0] = 0xAA;
463 apu.out_ports_[1] = 0xBB;
470 if (opcodes >= max_opcodes) {
471 return absl::DeadlineExceededError(
"Handler execution timeout");
474 return absl::OkStatus();
478 auto& ppu =
snes_->ppu();
481 for (uint32_t i = 0; i < 0x800; i++) {
484 ppu.vram[0x4000 + i] = lo | (hi << 8);
486 for (uint32_t i = 0; i < 0x800; i++) {
489 ppu.vram[0x4800 + i] = lo | (hi << 8);
493 ppu.HandleFrameStart();
494 for (
int line = 0; line < 224; line++) {
502 std::vector<uint8_t> rgba(256 * 224 * 4);
506 auto& ppu =
snes_->ppu();
508 for (
int y = 0; y < 224; ++y) {
509 for (
int x = 0; x < 256; ++x) {
510 int idx = (y * 256 + x) * 4;
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
absl::StatusOr< RenderResult > RenderDungeonObjectStatic(const RenderRequest &req)
zelda3::GameData * game_data_
absl::Status ExecuteHandler(int handler_addr, int data_offset, int tilemap_pos)
std::vector< uint8_t > ExtractPixelsFromPpu()
absl::Status Initialize()
void ClearTilemapBuffers()
absl::StatusOr< RenderResult > Render(const RenderRequest &request)
void InjectRoomContext(int room_id, uint8_t blockset, uint8_t palette)
absl::StatusOr< RenderResult > RenderDungeonObject(const RenderRequest &req)
absl::StatusOr< RenderResult > RenderFullRoom(const RenderRequest &req)
absl::StatusOr< std::vector< RenderResult > > RenderBatch(const std::vector< RenderRequest > &requests)
absl::StatusOr< RenderResult > RenderSprite(const RenderRequest &req)
std::unique_ptr< SaveStateManager > state_manager_
void LoadPaletteIntoCgram(int palette_id)
absl::Status GenerateBaselineStates()
std::unique_ptr< zelda3::GameData > owned_game_data_
absl::StatusOr< int > LookupHandlerAddress(int object_id, int *data_offset)
std::unique_ptr< emu::Snes > snes_
EmulatorRenderService(Rom *rom, zelda3::GameData *game_data=nullptr)
void LoadGraphicsIntoVram(uint8_t blockset)
void InitializeTilemapPointers()
void EnsureBitmapInitialized()
Draws dungeon objects to background buffers using game patterns.
void InitializeDrawRoutines()
Initialize draw routine registry Must be called before drawing objects.
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw a room object to background buffers.
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
void CopyRoomGraphicsToBuffer()
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
void SetGameData(GameData *data)
struct snes_color snes_color
SNES color in 15-bit RGB format (BGR555)
constexpr uint32_t kType3HandlerTable
constexpr uint32_t kType2HandlerTable
constexpr uint32_t kType2DataTable
constexpr uint32_t kType1DataTable
constexpr uint32_t kSpriteAuxPalettes
constexpr uint32_t kType1HandlerTable
constexpr uint32_t kType3DataTable
constexpr uint32_t kBG1TilemapBuffer
constexpr uint32_t kTilemapRowStride
constexpr uint32_t kBG2TilemapBuffer
constexpr uint8_t kTilemapPointers[]
constexpr uint32_t kTilemapBufferSize
constexpr uint32_t kRoomId
uint32_t SnesToPc(uint32_t snes_addr)
std::vector< uint8_t > ConvertLinear8bppToPlanar4bpp(const std::vector< uint8_t > &linear_data)
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
Room LoadRoomFromRom(Rom *rom, int room_id)
SNES color in 15-bit RGB format (BGR555)
std::vector< uint8_t > rgba_pixels
bool used_static_fallback
PaletteGroup dungeon_main
gfx::PaletteGroupMap palette_groups