10#include "absl/status/status.h"
11#include "absl/status/statusor.h"
12#include "absl/strings/str_cat.h"
13#include "absl/strings/str_format.h"
14#include "imgui/imgui.h"
15#include "imgui/misc/cpp/imgui_stdlib.h"
49using ImGui::InputText;
62 std::make_unique<PaletteControlsPanel>(&
state_,
rom_);
69 "Gfx Groups: blockset/roomset/spriteset selection syncs with the "
71 "editor for this ROM session (each surface has its own preview "
86 window_manager->RegisterWindowContent(
87 std::make_unique<GraphicsSheetBrowserPanel>([
this]() {
93 window_manager->RegisterWindowContent(
94 std::make_unique<GraphicsPixelEditorPanel>([
this]() {
100 window_manager->RegisterWindowContent(
101 std::make_unique<GraphicsPaletteControlsPanel>([
this]() {
107 window_manager->RegisterWindowContent(
108 std::make_unique<GraphicsLinkSpritePanel>([
this]() {
114 window_manager->RegisterWindowContent(
115 std::make_unique<GraphicsGfxGroupPanel>([
this]() {
122 window_manager->RegisterWindowContent(
123 std::make_unique<GraphicsPalettesetPanel>([
this]() {
129 window_manager->RegisterWindowContent(
130 std::make_unique<GraphicsPrototypeViewerPanel>(
133 window_manager->RegisterWindowContent(
134 std::make_unique<GraphicsPolyhedralPanel>([
this]() {
147 if (
rom()->is_loaded()) {
155 LOG_INFO(
"GraphicsEditor",
"Initializing textures for %d graphics sheets",
158 int sheets_queued = 0;
160 if (!sheets[i].is_active() || !sheets[i].surface()) {
166 if (!sheets[i].texture()) {
169 if (sheets[i].palette().empty()) {
174 game_data()->palette_groups.dungeon_main.size() > 0) {
175 sheets[i].SetPaletteWithTransparent(
176 game_data()->palette_groups.dungeon_main.palette(0), 0);
178 }
else if (i >= 113 && i <= 127) {
181 game_data()->palette_groups.sprites_aux1.size() > 0) {
182 sheets[i].SetPaletteWithTransparent(
183 game_data()->palette_groups.sprites_aux1.palette(0), 0);
188 sheets[i].SetPaletteWithTransparent(
189 game_data()->palette_groups.hud.palette(0), 0);
200 LOG_INFO(
"GraphicsEditor",
"Queued texture creation for %d graphics sheets",
204 return absl::OkStatus();
212 sheet_opts.
tooltip = absl::StrFormat(
213 "Sheet %d (0x%02X) — use Command Palette to jump between sheets",
217 std::move(sheet_opts));
224 modified_opts.
tooltip = absl::StrFormat(
229 std::move(modified_opts));
235 return absl::FailedPreconditionError(
"ROM not loaded");
240 LOG_INFO(
"GraphicsEditor",
"No modified sheets to save");
241 return absl::OkStatus();
244 LOG_INFO(
"GraphicsEditor",
"Saving %zu modified graphics sheets",
248 std::set<uint16_t> saved_sheets;
249 std::vector<uint16_t> skipped_sheets;
255 auto& sheet = sheets[sheet_id];
256 if (!sheet.is_active())
261 bool compressed =
true;
264 if (sheet_id == 113 || sheet_id == 114 || sheet_id >= 218) {
269 if (sheet_id >= 115 && sheet_id <= 126) {
274 const size_t expected_size =
276 const size_t actual_size = sheet.vector().size();
277 if (actual_size < expected_size) {
279 "Skipping 2BPP sheet %02X save (expected %zu bytes, got %zu)",
280 sheet_id, expected_size, actual_size);
281 skipped_sheets.push_back(sheet_id);
288 auto version_constants =
289 zelda3::kVersionConstantsMap.at(
game_data()->version);
292 version_constants.kOverworldGfxPtr1);
295 version_constants.kOverworldGfxPtr2);
298 version_constants.kOverworldGfxPtr3);
301 gfx_ptr1, gfx_ptr2, gfx_ptr3,
rom_->
size());
306 constexpr size_t kDecompressedSheetSize = 0x800;
307 std::vector<uint8_t> base_data;
310 rom_->
data(), offset,
static_cast<int>(kDecompressedSheetSize), 1,
312 if (!decomp_result.ok()) {
313 return decomp_result.status();
315 base_data = std::move(*decomp_result);
318 if (!read_result.ok()) {
319 return read_result.status();
321 base_data = std::move(*read_result);
324 if (base_data.size() < snes_tile_data.size()) {
325 base_data.resize(snes_tile_data.size(), 0);
327 std::copy(snes_tile_data.begin(), snes_tile_data.end(), base_data.begin());
329 std::vector<uint8_t> final_data;
332 int compressed_size = 0;
334 base_data.data(),
static_cast<int>(base_data.size()),
335 &compressed_size, 1);
336 final_data.assign(compressed_data.begin(),
337 compressed_data.begin() + compressed_size);
339 final_data = std::move(base_data);
343 for (
size_t i = 0; i < final_data.size(); i++) {
348 "Saved sheet %02X (%zu bytes, %s) at offset %06X", sheet_id,
349 final_data.size(), compressed ?
"compressed" :
"raw", offset);
350 saved_sheets.insert(sheet_id);
355 if (!skipped_sheets.empty()) {
356 return absl::FailedPreconditionError(
357 absl::StrCat(
"Skipped ", skipped_sheets.size(),
358 " 2BPP sheet(s); full data unavailable."));
361 return absl::OkStatus();
372 return absl::OkStatus();
385 if (ImGui::GetIO().WantTextInput) {
390 if (ImGui::IsKeyPressed(ImGuiKey_V,
false)) {
393 if (ImGui::IsKeyPressed(ImGuiKey_B,
false)) {
396 if (ImGui::IsKeyPressed(ImGuiKey_E,
false)) {
399 if (ImGui::IsKeyPressed(ImGuiKey_G,
false) && !ImGui::GetIO().KeyCtrl) {
402 if (ImGui::IsKeyPressed(ImGuiKey_I,
false)) {
405 if (ImGui::IsKeyPressed(ImGuiKey_L,
false) && !ImGui::GetIO().KeyCtrl) {
408 if (ImGui::IsKeyPressed(ImGuiKey_R,
false) && !ImGui::GetIO().KeyCtrl) {
413 if (ImGui::IsKeyPressed(ImGuiKey_Equal,
false) ||
414 ImGui::IsKeyPressed(ImGuiKey_KeypadAdd,
false)) {
417 if (ImGui::IsKeyPressed(ImGuiKey_Minus,
false) ||
418 ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract,
false)) {
423 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_G,
false)) {
428 if (ImGui::IsKeyPressed(ImGuiKey_PageDown,
false)) {
431 if (ImGui::IsKeyPressed(ImGuiKey_PageUp,
false)) {
439 "No ROM loaded — CGX/SCR/COL/BIN and clipboard tools work without one. "
440 "Load a ROM when you want vanilla palette presets or to save graphics "
441 "back into a cartridge image.");
445 ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.35f, 1.0f),
"%s",
447 if (ImGui::SmallButton(
"Dismiss##prototype_import_feedback")) {
459 constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
460 ImGuiTableFlags_Resizable |
461 ImGuiTableFlags_SizingStretchSame;
466 ImGui::TableSetupColumn(
"Tilemaps and Objects (SCR, PNL, OBJ)",
467 ImGuiTableColumnFlags_WidthFixed);
491 ImGui::Image((ImTextureID)(intptr_t)
gfx_sheets_[i].texture(),
493 if ((i + 1) % 4 != 0) {
521 if (ImGui::Button(
"Open CGX")) {
529 if (ImGui::Button(
"Copy CGX Path")) {
533 if (ImGui::Button(
"Load CGX Data")) {
538 return absl::OkStatus();
550 return absl::OkStatus();
556 if (ImGui::Button(
"Open SCR")) {
566 if (ImGui::Button(
"Load Scr Data")) {
570 return absl::OkStatus();
578 absl::StrCat(
"[SCR draw] ",
status_.message());
579 return absl::OkStatus();
591 return absl::OkStatus();
599 if (ImGui::Button(
"Open COL")) {
608 auto col_file_palette_group_status =
610 if (col_file_palette_group_status.ok()) {
622 if (ImGui::Button(
"Copy Col Path")) {
626 if (
rom()->is_loaded()) {
630 IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
638 return absl::OkStatus();
647 if (ImGui::Button(
"Open OBJ")) {
656 return absl::OkStatus();
665 if (ImGui::Button(
"Open Tilemap")) {
680 return absl::OkStatus();
689 if (ImGui::Button(
"Open BIN")) {
697 if (Button(
"Copy File Path")) {
704 if (Button(
"Decompress BIN")) {
706 return absl::InvalidArgumentError(
707 "Please select a file before decompressing.");
712 return absl::OkStatus();
717 if (Button(
"Paste From Clipboard")) {
718 const char* text = ImGui::GetClipboardText();
720 const auto clipboard_data =
721 std::vector<uint8_t>(text, text + strlen(text));
722 ImGui::MemFree((
void*)text);
732 if (Button(
"Decompress Clipboard Data")) {
736 status_ = absl::InvalidArgumentError(
737 "Please paste data into the clipboard before "
742 return absl::OkStatus();
747 if (Button(
"Decompress Super Donkey Full")) {
749 return absl::InvalidArgumentError(
750 "Please select `super_donkey_1.bin` before "
755 ImGui::SetItemTooltip(
756 "Requires `super_donkey_1.bin` to be imported under the "
757 "BIN import section.");
758 return absl::OkStatus();
762 std::string title =
"Memory Editor";
768 return absl::OkStatus();
794 return absl::OkStatus();
801 std::stoi(offset,
nullptr, 16);
814 return absl::FailedPreconditionError(
"GameData not available");
829 std::stoi(offset,
nullptr, 16);
856 return absl::OkStatus();
879 const std::string& label,
880 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_
std::set< uint16_t > selected_sheets
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< 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 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()
std::string prototype_import_feedback_
absl::Status DrawClipboardImport()
uint64_t num_sheets_to_load_
absl::Status DecompressImportData(int size)
void ContributeStatus(StatusBar *status_bar) override
absl::Status DrawExperimentalFeatures()
absl::Status DrawPaletteControls()
std::unique_ptr< SheetBrowserPanel > sheet_browser_panel_
std::string scr_file_name_
A session-aware status bar displayed at the bottom of the application.
void SetSelection(int count, int width=0, int height=0)
Set selection information.
void SetCustomSegment(const std::string &key, const std::string &value)
Set a custom segment with key-value pair.
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)
WorkspaceWindowManager * window_manager
GfxGroupWorkspaceState * gfx_group_workspace
Optional behavior for an interactive status bar segment.
PaletteGroup overworld_animated
PaletteGroup * get_group(const std::string &group_name)
gfx::PaletteGroupMap palette_groups