10#include "absl/status/status.h"
11#include "absl/status/statusor.h"
12#include "absl/strings/str_cat.h"
13#include "imgui/imgui.h"
14#include "imgui/misc/cpp/imgui_stdlib.h"
46using ImGui::InputText;
73 panel_manager->RegisterEditorPanel(
74 std::make_unique<GraphicsSheetBrowserPanel>([
this]() {
80 panel_manager->RegisterEditorPanel(
81 std::make_unique<GraphicsPixelEditorPanel>([
this]() {
87 panel_manager->RegisterEditorPanel(
88 std::make_unique<GraphicsPaletteControlsPanel>([
this]() {
94 panel_manager->RegisterEditorPanel(
95 std::make_unique<GraphicsLinkSpritePanel>([
this]() {
101 panel_manager->RegisterEditorPanel(
102 std::make_unique<GraphicsPolyhedralPanel>([
this]() {
108 panel_manager->RegisterEditorPanel(
109 std::make_unique<GraphicsGfxGroupPanel>([
this]() {
116 panel_manager->RegisterEditorPanel(
117 std::make_unique<GraphicsPalettesetPanel>([
this]() {
124 panel_manager->RegisterEditorPanel(
125 std::make_unique<GraphicsPrototypeViewerPanel>([
this]() {
135 if (
rom()->is_loaded()) {
143 LOG_INFO(
"GraphicsEditor",
"Initializing textures for %d graphics sheets",
146 int sheets_queued = 0;
148 if (!sheets[i].is_active() || !sheets[i].surface()) {
154 if (!sheets[i].texture()) {
157 if (sheets[i].palette().empty()) {
162 sheets[i].SetPaletteWithTransparent(
163 game_data()->palette_groups.dungeon_main.palette(0), 0);
165 }
else if (i >= 113 && i <= 127) {
168 sheets[i].SetPaletteWithTransparent(
169 game_data()->palette_groups.sprites_aux1.palette(0), 0);
174 sheets[i].SetPaletteWithTransparent(
175 game_data()->palette_groups.hud.palette(0), 0);
186 LOG_INFO(
"GraphicsEditor",
"Queued texture creation for %d graphics sheets",
195 return absl::OkStatus();
200 return absl::FailedPreconditionError(
"ROM not loaded");
205 LOG_INFO(
"GraphicsEditor",
"No modified sheets to save");
206 return absl::OkStatus();
209 LOG_INFO(
"GraphicsEditor",
"Saving %zu modified graphics sheets",
213 std::set<uint16_t> saved_sheets;
214 std::vector<uint16_t> skipped_sheets;
219 auto& sheet = sheets[sheet_id];
220 if (!sheet.is_active())
continue;
224 bool compressed =
true;
227 if (sheet_id == 113 || sheet_id == 114 || sheet_id >= 218) {
232 if (sheet_id >= 115 && sheet_id <= 126) {
237 const size_t expected_size =
239 const size_t actual_size = sheet.vector().size();
240 if (actual_size < expected_size) {
243 "Skipping 2BPP sheet %02X save (expected %zu bytes, got %zu)",
244 sheet_id, expected_size, actual_size);
245 skipped_sheets.push_back(sheet_id);
252 auto version_constants = zelda3::kVersionConstantsMap.at(
game_data()->version);
254 rom_->
data(),
static_cast<uint8_t
>(sheet_id),
255 version_constants.kOverworldGfxPtr1,
256 version_constants.kOverworldGfxPtr2,
257 version_constants.kOverworldGfxPtr3,
rom_->
size());
262 constexpr size_t kDecompressedSheetSize = 0x800;
263 std::vector<uint8_t> base_data;
266 rom_->
data(), offset,
static_cast<int>(kDecompressedSheetSize), 1,
268 if (!decomp_result.ok()) {
269 return decomp_result.status();
271 base_data = std::move(*decomp_result);
275 if (!read_result.ok()) {
276 return read_result.status();
278 base_data = std::move(*read_result);
281 if (base_data.size() < snes_tile_data.size()) {
282 base_data.resize(snes_tile_data.size(), 0);
284 std::copy(snes_tile_data.begin(), snes_tile_data.end(),
287 std::vector<uint8_t> final_data;
290 int compressed_size = 0;
292 base_data.data(),
static_cast<int>(base_data.size()),
293 &compressed_size, 1);
294 final_data.assign(compressed_data.begin(),
295 compressed_data.begin() + compressed_size);
297 final_data = std::move(base_data);
301 for (
size_t i = 0; i < final_data.size(); i++) {
305 LOG_INFO(
"GraphicsEditor",
"Saved sheet %02X (%zu bytes, %s) at offset %06X",
306 sheet_id, final_data.size(), compressed ?
"compressed" :
"raw",
308 saved_sheets.insert(sheet_id);
313 if (!skipped_sheets.empty()) {
314 return absl::FailedPreconditionError(
315 absl::StrCat(
"Skipped ", skipped_sheets.size(),
316 " 2BPP sheet(s); full data unavailable."));
319 return absl::OkStatus();
330 return absl::OkStatus();
335 if (ImGui::GetIO().WantTextInput) {
340 if (ImGui::IsKeyPressed(ImGuiKey_V,
false)) {
343 if (ImGui::IsKeyPressed(ImGuiKey_B,
false)) {
346 if (ImGui::IsKeyPressed(ImGuiKey_E,
false)) {
349 if (ImGui::IsKeyPressed(ImGuiKey_G,
false) && !ImGui::GetIO().KeyCtrl) {
352 if (ImGui::IsKeyPressed(ImGuiKey_I,
false)) {
355 if (ImGui::IsKeyPressed(ImGuiKey_L,
false) && !ImGui::GetIO().KeyCtrl) {
358 if (ImGui::IsKeyPressed(ImGuiKey_R,
false) && !ImGui::GetIO().KeyCtrl) {
363 if (ImGui::IsKeyPressed(ImGuiKey_Equal,
false) ||
364 ImGui::IsKeyPressed(ImGuiKey_KeypadAdd,
false)) {
367 if (ImGui::IsKeyPressed(ImGuiKey_Minus,
false) ||
368 ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract,
false)) {
373 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_G,
false)) {
378 if (ImGui::IsKeyPressed(ImGuiKey_PageDown,
false)) {
381 if (ImGui::IsKeyPressed(ImGuiKey_PageUp,
false)) {
393 constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
394 ImGuiTableFlags_Resizable |
395 ImGuiTableFlags_SizingStretchSame;
400 ImGui::TableSetupColumn(
"Tilemaps and Objects (SCR, PNL, OBJ)",
401 ImGuiTableColumnFlags_WidthFixed);
425 ImGui::Image((ImTextureID)(intptr_t)
gfx_sheets_[i].texture(),
427 if ((i + 1) % 4 != 0) {
455 if (ImGui::Button(
"Open CGX")) {
463 if (ImGui::Button(
"Copy CGX Path")) {
467 if (ImGui::Button(
"Load CGX Data")) {
479 return absl::OkStatus();
485 if (ImGui::Button(
"Open SCR")) {
495 if (ImGui::Button(
"Load Scr Data")) {
510 return absl::OkStatus();
518 if (ImGui::Button(
"Open COL")) {
527 auto col_file_palette_group_status =
529 if (col_file_palette_group_status.ok()) {
541 if (ImGui::Button(
"Copy Col Path")) {
545 if (
rom()->is_loaded()) {
549 IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
557 return absl::OkStatus();
566 if (ImGui::Button(
"Open OBJ")) {
575 return absl::OkStatus();
584 if (ImGui::Button(
"Open Tilemap")) {
599 return absl::OkStatus();
608 if (ImGui::Button(
"Open BIN")) {
616 if (Button(
"Copy File Path")) {
623 if (Button(
"Decompress BIN")) {
625 return absl::InvalidArgumentError(
626 "Please select a file before decompressing.");
631 return absl::OkStatus();
636 if (Button(
"Paste From Clipboard")) {
637 const char* text = ImGui::GetClipboardText();
639 const auto clipboard_data =
640 std::vector<uint8_t>(text, text + strlen(text));
641 ImGui::MemFree((
void*)text);
651 if (Button(
"Decompress Clipboard Data")) {
655 status_ = absl::InvalidArgumentError(
656 "Please paste data into the clipboard before "
661 return absl::OkStatus();
666 if (Button(
"Decompress Super Donkey Full")) {
668 return absl::InvalidArgumentError(
669 "Please select `super_donkey_1.bin` before "
674 ImGui::SetItemTooltip(
675 "Requires `super_donkey_1.bin` to be imported under the "
676 "BIN import section.");
677 return absl::OkStatus();
681 std::string title =
"Memory Editor";
687 return absl::OkStatus();
713 return absl::OkStatus();
720 std::stoi(offset,
nullptr, 16);
722 auto decompressed_data,
734 return absl::FailedPreconditionError(
"GameData not available");
749 std::stoi(offset,
nullptr, 16);
751 auto decompressed_data,
777 return absl::OkStatus();
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
absl::Status WriteByte(int addr, uint8_t value)
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
zelda3::GameData * game_data() const
EditorDependencies dependencies_
std::set< uint16_t > modified_sheets
uint16_t current_sheet_id
bool HasUnsavedChanges() const
Check if any sheets have unsaved changes.
void SetTool(PixelTool tool)
Set the current editing tool.
void ClearModifiedSheets()
Clear modification tracking (after save)
std::vector< uint8_t > scr_data_
GraphicsEditorState state_
absl::Status Save() override
uint64_t clipboard_offset_
gfx::PaletteGroup col_file_palette_group_
std::string col_file_path_
std::string cgx_file_path_
absl::Status DrawMemoryEditor()
absl::Status Load() override
std::unique_ptr< GfxGroupEditor > gfx_group_panel_
zelda3::GameData * game_data_
std::unique_ptr< PaletteControlsPanel > palette_controls_panel_
void Initialize() override
std::vector< uint8_t > decoded_cgx_
std::string scr_file_path_
absl::Status DecompressSuperDonkey()
gfx::SnesPalette col_file_palette_
std::string col_file_name_
void HandleEditorShortcuts()
absl::Status DrawCgxImport()
std::vector< uint8_t > cgx_data_
std::unique_ptr< PixelEditorPanel > pixel_editor_panel_
std::unique_ptr< PolyhedralEditorPanel > polyhedral_panel_
std::unique_ptr< PalettesetEditorPanel > paletteset_panel_
std::string obj_file_path_
void DrawPrototypeViewer()
absl::Status DrawFileImport()
std::vector< uint8_t > import_data_
uint64_t current_palette_index_
std::vector< SDL_Color > decoded_col_
absl::Status DrawTilemapImport()
gfx::SnesPalette z3_rom_palette_
absl::Status Update() override
absl::Status DrawScrImport()
std::string tilemap_file_path_
std::vector< uint8_t > extra_cgx_data_
std::array< gfx::Bitmap, zelda3::kNumGfxSheets > gfx_sheets_
gui::Canvas import_canvas_
std::vector< uint8_t > decoded_scr_data_
std::string cgx_file_name_
std::unique_ptr< LinkSpritePanel > link_sprite_panel_
absl::Status DrawObjImport()
absl::Status DrawClipboardImport()
uint64_t num_sheets_to_load_
absl::Status DecompressImportData(int size)
absl::Status DrawExperimentalFeatures()
absl::Status DrawPaletteControls()
std::unique_ptr< SheetBrowserPanel > sheet_browser_panel_
std::string scr_file_name_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Represents a bitmap image optimized for SNES ROM hacking.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
RAII timer for automatic timing management.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
#define BEGIN_TABLE(l, n, f)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
#define CLEAR_AND_RETURN_STATUS(status)
#define HOVER_HINT(string)
const std::string kSuperDonkeySprites[]
const std::string kSuperDonkeyTiles[]
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uint8_t *data, int offset, int size, int mode, size_t rom_size)
Decompresses a buffer of data using the LC_LZ2 algorithm.
constexpr int kNintendoMode1
absl::Status LoadScr(std::string_view filename, uint8_t input_value, std::vector< uint8_t > &map_data)
Load Scr file (screen data)
std::vector< SDL_Color > DecodeColFile(const std::string_view filename)
Decode color file.
constexpr int kTilesheetHeight
constexpr int kTilesheetWidth
absl::Status LoadCgx(uint8_t bpp, std::string_view filename, std::vector< uint8_t > &cgx_data, std::vector< uint8_t > &cgx_loaded, std::vector< uint8_t > &cgx_header)
Load Cgx file (graphical content)
constexpr const char * kPaletteGroupAddressesKeys[]
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector< uint8_t > &map_bitmap_data, std::vector< uint8_t > &map_data, std::vector< uint8_t > &cgx_loaded)
Draw screen tilemap with graphical data.
constexpr int kTilesheetDepth
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
std::vector< uint8_t > IndexedToSnesSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
std::vector< uint8_t > HyruleMagicCompress(uint8_t const *const src, int const oldsize, int *const size, int const flag)
absl::StatusOr< PaletteGroup > CreatePaletteGroupFromColFile(std::vector< SnesColor > &palette_rows)
std::vector< SnesColor > GetColFileData(uint8_t *data)
void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id)
bool InputHex(const char *label, uint64_t *data)
void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics, gfx::SnesPalette &palette)
void TextWithSeparators(const absl::string_view &text)
constexpr uint32_t kNumGfxSheets
uint32_t GetGraphicsAddress(const uint8_t *data, uint8_t addr, uint32_t ptr1, uint32_t ptr2, uint32_t ptr3, size_t rom_size)
Gets the graphics address for a sheet index.
#define RETURN_IF_ERROR(expr)
PanelManager * panel_manager
PaletteGroup overworld_animated
PaletteGroup * get_group(const std::string &group_name)
gfx::PaletteGroupMap palette_groups