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"
47using ImGui::InputText;
74 panel_manager->RegisterEditorPanel(
75 std::make_unique<GraphicsSheetBrowserPanel>([
this]() {
81 panel_manager->RegisterEditorPanel(
82 std::make_unique<GraphicsPixelEditorPanel>([
this]() {
88 panel_manager->RegisterEditorPanel(
89 std::make_unique<GraphicsPaletteControlsPanel>([
this]() {
95 panel_manager->RegisterEditorPanel(
96 std::make_unique<GraphicsLinkSpritePanel>([
this]() {
102 panel_manager->RegisterEditorPanel(
103 std::make_unique<GraphicsGfxGroupPanel>([
this]() {
110 panel_manager->RegisterEditorPanel(
111 std::make_unique<GraphicsPalettesetPanel>([
this]() {
125 if (
rom()->is_loaded()) {
133 LOG_INFO(
"GraphicsEditor",
"Initializing textures for %d graphics sheets",
136 int sheets_queued = 0;
138 if (!sheets[i].is_active() || !sheets[i].surface()) {
144 if (!sheets[i].texture()) {
147 if (sheets[i].palette().empty()) {
152 sheets[i].SetPaletteWithTransparent(
153 game_data()->palette_groups.dungeon_main.palette(0), 0);
155 }
else if (i >= 113 && i <= 127) {
158 sheets[i].SetPaletteWithTransparent(
159 game_data()->palette_groups.sprites_aux1.palette(0), 0);
164 sheets[i].SetPaletteWithTransparent(
165 game_data()->palette_groups.hud.palette(0), 0);
176 LOG_INFO(
"GraphicsEditor",
"Queued texture creation for %d graphics sheets",
180 return absl::OkStatus();
185 return absl::FailedPreconditionError(
"ROM not loaded");
190 LOG_INFO(
"GraphicsEditor",
"No modified sheets to save");
191 return absl::OkStatus();
194 LOG_INFO(
"GraphicsEditor",
"Saving %zu modified graphics sheets",
198 std::set<uint16_t> saved_sheets;
199 std::vector<uint16_t> skipped_sheets;
204 auto& sheet = sheets[sheet_id];
205 if (!sheet.is_active())
continue;
209 bool compressed =
true;
212 if (sheet_id == 113 || sheet_id == 114 || sheet_id >= 218) {
217 if (sheet_id >= 115 && sheet_id <= 126) {
222 const size_t expected_size =
224 const size_t actual_size = sheet.vector().size();
225 if (actual_size < expected_size) {
228 "Skipping 2BPP sheet %02X save (expected %zu bytes, got %zu)",
229 sheet_id, expected_size, actual_size);
230 skipped_sheets.push_back(sheet_id);
237 auto version_constants =
238 zelda3::kVersionConstantsMap.at(
game_data()->version);
241 version_constants.kOverworldGfxPtr1);
244 version_constants.kOverworldGfxPtr2);
247 version_constants.kOverworldGfxPtr3);
249 rom_->
data(),
static_cast<uint8_t
>(sheet_id), gfx_ptr1, gfx_ptr2,
255 constexpr size_t kDecompressedSheetSize = 0x800;
256 std::vector<uint8_t> base_data;
259 rom_->
data(), offset,
static_cast<int>(kDecompressedSheetSize), 1,
261 if (!decomp_result.ok()) {
262 return decomp_result.status();
264 base_data = std::move(*decomp_result);
268 if (!read_result.ok()) {
269 return read_result.status();
271 base_data = std::move(*read_result);
274 if (base_data.size() < snes_tile_data.size()) {
275 base_data.resize(snes_tile_data.size(), 0);
277 std::copy(snes_tile_data.begin(), snes_tile_data.end(),
280 std::vector<uint8_t> final_data;
283 int compressed_size = 0;
285 base_data.data(),
static_cast<int>(base_data.size()),
286 &compressed_size, 1);
287 final_data.assign(compressed_data.begin(),
288 compressed_data.begin() + compressed_size);
290 final_data = std::move(base_data);
294 for (
size_t i = 0; i < final_data.size(); i++) {
298 LOG_INFO(
"GraphicsEditor",
"Saved sheet %02X (%zu bytes, %s) at offset %06X",
299 sheet_id, final_data.size(), compressed ?
"compressed" :
"raw",
301 saved_sheets.insert(sheet_id);
306 if (!skipped_sheets.empty()) {
307 return absl::FailedPreconditionError(
308 absl::StrCat(
"Skipped ", skipped_sheets.size(),
309 " 2BPP sheet(s); full data unavailable."));
312 return absl::OkStatus();
323 return absl::OkStatus();
336 if (ImGui::GetIO().WantTextInput) {
341 if (ImGui::IsKeyPressed(ImGuiKey_V,
false)) {
344 if (ImGui::IsKeyPressed(ImGuiKey_B,
false)) {
347 if (ImGui::IsKeyPressed(ImGuiKey_E,
false)) {
350 if (ImGui::IsKeyPressed(ImGuiKey_G,
false) && !ImGui::GetIO().KeyCtrl) {
353 if (ImGui::IsKeyPressed(ImGuiKey_I,
false)) {
356 if (ImGui::IsKeyPressed(ImGuiKey_L,
false) && !ImGui::GetIO().KeyCtrl) {
359 if (ImGui::IsKeyPressed(ImGuiKey_R,
false) && !ImGui::GetIO().KeyCtrl) {
364 if (ImGui::IsKeyPressed(ImGuiKey_Equal,
false) ||
365 ImGui::IsKeyPressed(ImGuiKey_KeypadAdd,
false)) {
368 if (ImGui::IsKeyPressed(ImGuiKey_Minus,
false) ||
369 ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract,
false)) {
374 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_G,
false)) {
379 if (ImGui::IsKeyPressed(ImGuiKey_PageDown,
false)) {
382 if (ImGui::IsKeyPressed(ImGuiKey_PageUp,
false)) {
394 constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
395 ImGuiTableFlags_Resizable |
396 ImGuiTableFlags_SizingStretchSame;
401 ImGui::TableSetupColumn(
"Tilemaps and Objects (SCR, PNL, OBJ)",
402 ImGuiTableColumnFlags_WidthFixed);
426 ImGui::Image((ImTextureID)(intptr_t)
gfx_sheets_[i].texture(),
428 if ((i + 1) % 4 != 0) {
456 if (ImGui::Button(
"Open CGX")) {
464 if (ImGui::Button(
"Copy CGX Path")) {
468 if (ImGui::Button(
"Load CGX Data")) {
480 return absl::OkStatus();
486 if (ImGui::Button(
"Open SCR")) {
496 if (ImGui::Button(
"Load Scr Data")) {
511 return absl::OkStatus();
519 if (ImGui::Button(
"Open COL")) {
528 auto col_file_palette_group_status =
530 if (col_file_palette_group_status.ok()) {
542 if (ImGui::Button(
"Copy Col Path")) {
546 if (
rom()->is_loaded()) {
550 IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
558 return absl::OkStatus();
567 if (ImGui::Button(
"Open OBJ")) {
576 return absl::OkStatus();
585 if (ImGui::Button(
"Open Tilemap")) {
600 return absl::OkStatus();
609 if (ImGui::Button(
"Open BIN")) {
617 if (Button(
"Copy File Path")) {
624 if (Button(
"Decompress BIN")) {
626 return absl::InvalidArgumentError(
627 "Please select a file before decompressing.");
632 return absl::OkStatus();
637 if (Button(
"Paste From Clipboard")) {
638 const char* text = ImGui::GetClipboardText();
640 const auto clipboard_data =
641 std::vector<uint8_t>(text, text + strlen(text));
642 ImGui::MemFree((
void*)text);
652 if (Button(
"Decompress Clipboard Data")) {
656 status_ = absl::InvalidArgumentError(
657 "Please paste data into the clipboard before "
662 return absl::OkStatus();
667 if (Button(
"Decompress Super Donkey Full")) {
669 return absl::InvalidArgumentError(
670 "Please select `super_donkey_1.bin` before "
675 ImGui::SetItemTooltip(
676 "Requires `super_donkey_1.bin` to be imported under the "
677 "BIN import section.");
678 return absl::OkStatus();
682 std::string title =
"Memory Editor";
688 return absl::OkStatus();
714 return absl::OkStatus();
721 std::stoi(offset,
nullptr, 16);
723 auto decompressed_data,
735 return absl::FailedPreconditionError(
"GameData not available");
750 std::stoi(offset,
nullptr, 16);
752 auto decompressed_data,
778 return absl::OkStatus();
801 const std::string& label,
802 double duration_secs) {
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())
static RomSettings & Get()
uint32_t GetAddressOr(const std::string &key, uint32_t default_value) const
UndoManager undo_manager_
zelda3::GameData * game_data() const
EditorDependencies dependencies_
void HighlightTile(uint16_t sheet_id, uint16_t tile_index, const std::string &label="", double duration_secs=3.0)
Highlight a tile in the current sheet for quick visual focus.
std::set< uint16_t > modified_sheets
uint16_t current_sheet_id
bool HasUnsavedChanges() const
Check if any sheets have unsaved changes.
void SelectSheet(uint16_t sheet_id)
Select a sheet for editing.
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
void HighlightTile(uint16_t sheet_id, uint16_t tile_index, const std::string &label="", double duration_secs=3.0)
uint64_t clipboard_offset_
gfx::PaletteGroup col_file_palette_group_
void SelectSheet(uint16_t sheet_id)
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_
absl::Status Redo() override
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 Undo() override
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_
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
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)
constexpr char kOverworldGfxPtr3[]
constexpr char kOverworldGfxPtr1[]
constexpr char kOverworldGfxPtr2[]
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< const 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