11#include "absl/status/status.h"
12#include "absl/status/statusor.h"
13#include "absl/strings/str_cat.h"
14#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
16#include "imgui/misc/cpp/imgui_stdlib.h"
50using ImGui::InputText;
63 std::make_unique<PaletteControlsPanel>(&
state_,
rom_);
70 "Gfx Groups: blockset/roomset/spriteset selection syncs with the "
72 "editor for this ROM session (each surface has its own preview "
87 window_manager->RegisterWindowContent(
88 std::make_unique<GraphicsSheetBrowserPanel>([
this]() {
94 window_manager->RegisterWindowContent(
95 std::make_unique<GraphicsPixelEditorPanel>([
this]() {
101 window_manager->RegisterWindowContent(
102 std::make_unique<GraphicsPaletteControlsPanel>([
this]() {
108 window_manager->RegisterWindowContent(
109 std::make_unique<GraphicsLinkSpritePanel>([
this]() {
115 window_manager->RegisterWindowContent(
116 std::make_unique<GraphicsGfxGroupPanel>([
this]() {
123 window_manager->RegisterWindowContent(
124 std::make_unique<GraphicsPalettesetPanel>([
this]() {
130 window_manager->RegisterWindowContent(
131 std::make_unique<GraphicsPrototypeViewerPanel>(
134 window_manager->RegisterWindowContent(
135 std::make_unique<GraphicsPolyhedralPanel>([
this]() {
148 if (
rom()->is_loaded()) {
156 LOG_INFO(
"GraphicsEditor",
"Initializing textures for %d graphics sheets",
159 int sheets_queued = 0;
161 if (!sheets[i].is_active() || !sheets[i].surface()) {
167 if (!sheets[i].texture()) {
170 if (sheets[i].palette().empty()) {
175 game_data()->palette_groups.dungeon_main.size() > 0) {
176 sheets[i].SetPaletteWithTransparent(
177 game_data()->palette_groups.dungeon_main.palette(0), 0);
179 }
else if (i >= 113 && i <= 127) {
182 game_data()->palette_groups.sprites_aux1.size() > 0) {
183 sheets[i].SetPaletteWithTransparent(
184 game_data()->palette_groups.sprites_aux1.palette(0), 0);
189 sheets[i].SetPaletteWithTransparent(
190 game_data()->palette_groups.hud.palette(0), 0);
201 LOG_INFO(
"GraphicsEditor",
"Queued texture creation for %d graphics sheets",
205 return absl::OkStatus();
213 sheet_opts.
tooltip = absl::StrFormat(
214 "Sheet %d (0x%02X) — use Command Palette to jump between sheets",
218 std::move(sheet_opts));
225 modified_opts.
tooltip = absl::StrFormat(
230 std::move(modified_opts));
236 return absl::FailedPreconditionError(
"ROM not loaded");
241 LOG_INFO(
"GraphicsEditor",
"No modified sheets to save");
242 return absl::OkStatus();
245 LOG_INFO(
"GraphicsEditor",
"Saving %zu modified graphics sheets",
249 std::set<uint16_t> saved_sheets;
250 std::vector<uint16_t> skipped_sheets;
256 auto& sheet = sheets[sheet_id];
257 if (!sheet.is_active())
262 bool compressed =
true;
265 if (sheet_id == 113 || sheet_id == 114 || sheet_id >= 218) {
270 if (sheet_id >= 115 && sheet_id <= 126) {
275 const size_t expected_size =
277 const size_t actual_size = sheet.vector().size();
278 if (actual_size < expected_size) {
280 "Skipping 2BPP sheet %02X save (expected %zu bytes, got %zu)",
281 sheet_id, expected_size, actual_size);
282 skipped_sheets.push_back(sheet_id);
289 auto version_constants =
290 zelda3::kVersionConstantsMap.at(
game_data()->version);
293 version_constants.kOverworldGfxPtr1);
296 version_constants.kOverworldGfxPtr2);
299 version_constants.kOverworldGfxPtr3);
302 gfx_ptr1, gfx_ptr2, gfx_ptr3,
rom_->
size());
307 constexpr size_t kDecompressedSheetSize = 0x800;
308 std::vector<uint8_t> base_data;
311 rom_->
data(), offset,
static_cast<int>(kDecompressedSheetSize), 1,
313 if (!decomp_result.ok()) {
314 return decomp_result.status();
316 base_data = std::move(*decomp_result);
319 if (!read_result.ok()) {
320 return read_result.status();
322 base_data = std::move(*read_result);
325 if (base_data.size() < snes_tile_data.size()) {
326 base_data.resize(snes_tile_data.size(), 0);
328 std::copy(snes_tile_data.begin(), snes_tile_data.end(), base_data.begin());
330 std::vector<uint8_t> final_data;
333 int compressed_size = 0;
335 base_data.data(),
static_cast<int>(base_data.size()),
336 &compressed_size, 1);
337 final_data.assign(compressed_data.begin(),
338 compressed_data.begin() + compressed_size);
340 final_data = std::move(base_data);
344 for (
size_t i = 0; i < final_data.size(); i++) {
349 "Saved sheet %02X (%zu bytes, %s) at offset %06X", sheet_id,
350 final_data.size(), compressed ?
"compressed" :
"raw", offset);
351 saved_sheets.insert(sheet_id);
356 if (!skipped_sheets.empty()) {
357 return absl::FailedPreconditionError(
358 absl::StrCat(
"Skipped ", skipped_sheets.size(),
359 " 2BPP sheet(s); full data unavailable."));
362 return absl::OkStatus();
373 return absl::OkStatus();
386 if (ImGui::GetIO().WantTextInput) {
391 if (ImGui::IsKeyPressed(ImGuiKey_V,
false)) {
394 if (ImGui::IsKeyPressed(ImGuiKey_B,
false)) {
397 if (ImGui::IsKeyPressed(ImGuiKey_E,
false)) {
400 if (ImGui::IsKeyPressed(ImGuiKey_G,
false) && !ImGui::GetIO().KeyCtrl) {
403 if (ImGui::IsKeyPressed(ImGuiKey_I,
false)) {
406 if (ImGui::IsKeyPressed(ImGuiKey_L,
false) && !ImGui::GetIO().KeyCtrl) {
409 if (ImGui::IsKeyPressed(ImGuiKey_R,
false) && !ImGui::GetIO().KeyCtrl) {
414 if (ImGui::IsKeyPressed(ImGuiKey_Equal,
false) ||
415 ImGui::IsKeyPressed(ImGuiKey_KeypadAdd,
false)) {
418 if (ImGui::IsKeyPressed(ImGuiKey_Minus,
false) ||
419 ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract,
false)) {
424 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_G,
false)) {
429 if (ImGui::IsKeyPressed(ImGuiKey_PageDown,
false)) {
432 if (ImGui::IsKeyPressed(ImGuiKey_PageUp,
false)) {
439 ImGui::TextWrapped(tr(
440 "No ROM loaded — CGX/SCR/COL/BIN and clipboard tools work without one. "
441 "Load a ROM when you want vanilla palette presets or to save graphics "
442 "back into a cartridge image."));
446 ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.35f, 1.0f),
"%s",
448 if (ImGui::SmallButton(tr(
"Dismiss##prototype_import_feedback"))) {
460 constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
461 ImGuiTableFlags_Resizable |
462 ImGuiTableFlags_SizingStretchSame;
467 ImGui::TableSetupColumn(
"Tilemaps and Objects (SCR, PNL, OBJ)",
468 ImGuiTableColumnFlags_WidthFixed);
493 ImGui::Image((ImTextureID)(intptr_t)
gfx_sheets_[i].texture(),
495 if ((i + 1) % 4 != 0) {
525 if (ImGui::Button(tr(
"Open CGX"))) {
533 if (ImGui::Button(tr(
"Copy CGX Path"))) {
537 if (ImGui::Button(tr(
"Load CGX Data"))) {
542 return absl::OkStatus();
554 return absl::OkStatus();
560 if (ImGui::Button(tr(
"Open SCR"))) {
570 if (ImGui::Button(tr(
"Load Scr Data"))) {
574 return absl::OkStatus();
582 absl::StrCat(
"[SCR draw] ",
status_.message());
583 return absl::OkStatus();
595 return absl::OkStatus();
603 if (ImGui::Button(tr(
"Open COL"))) {
612 auto col_file_palette_group_status =
614 if (col_file_palette_group_status.ok()) {
626 if (ImGui::Button(tr(
"Copy Col Path"))) {
630 if (
rom()->is_loaded()) {
634 IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
642 return absl::OkStatus();
651 if (ImGui::Button(tr(
"Open OBJ"))) {
660 return absl::OkStatus();
669 if (ImGui::Button(tr(
"Open Tilemap"))) {
684 return absl::OkStatus();
693 if (ImGui::Button(tr(
"Open BIN"))) {
701 if (Button(tr(
"Copy File Path"))) {
708 if (Button(tr(
"Decompress BIN"))) {
710 return absl::InvalidArgumentError(
711 "Please select a file before decompressing.");
716 return absl::OkStatus();
721 if (Button(tr(
"Paste From Clipboard"))) {
722 const char* text = ImGui::GetClipboardText();
724 const auto clipboard_data =
725 std::vector<uint8_t>(text, text + strlen(text));
726 ImGui::MemFree((
void*)text);
736 if (Button(tr(
"Decompress Clipboard Data"))) {
740 status_ = absl::InvalidArgumentError(
741 "Please paste data into the clipboard before "
746 return absl::OkStatus();
751 if (Button(tr(
"Decompress Super Donkey Full"))) {
753 return absl::InvalidArgumentError(
754 "Please select `super_donkey_1.bin` before "
759 ImGui::SetItemTooltip(
760 tr(
"Requires `super_donkey_1.bin` to be imported under the "
761 "BIN import section."));
762 return absl::OkStatus();
766 std::string title =
"Memory Editor";
772 return absl::OkStatus();
798 return absl::OkStatus();
805 std::stoi(offset,
nullptr, 16);
818 return absl::FailedPreconditionError(
"GameData not available");
833 std::stoi(offset,
nullptr, 16);
860 return absl::OkStatus();
883 const std::string& label,
884 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).
CanvasConfig & GetConfig()
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)
bool InputHex(const char *label, uint64_t *data)
void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics, gfx::SnesPalette &palette)
void BitmapCanvasPipeline(Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id)
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