diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
new file mode 100644
index 00000000..2d0431d7
--- /dev/null
+++ b/.github/workflows/cmake.yml
@@ -0,0 +1,41 @@
+name: CMake
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+env:
+ # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+ BUILD_TYPE: Debug
+
+jobs:
+ build:
+ # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
+ # You can convert this to a matrix build if you need cross-platform coverage.
+ # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - name: Install Video Libs
+ run: sudo apt install libglew-dev
+
+ - name: Configure CMake
+ # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
+ # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
+ run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
+
+ - name: Build
+ # Build your program with the given configuration
+ run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
+
+ - name: Test
+ working-directory: ${{github.workspace}}/build
+ # Execute tests defined by the CMake configuration.
+ # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
+ run: ${{github.workspace}}/build/bin/yaze_test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..a22892fc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+build/
+.cache/
+.vscode/
+src/lib/SDL2
+src/lib/cmake
+src/lib/GL
+src/lib/abseil-cpp
+src/lib/libGLEW.2.2.0.dylib
+src/lib/libGLEW.2.2.dylib
+src/lib/libGLEW.a
+src/lib/libGLEW.dylib
+src/lib/libSDL2_test.a
+src/lib/libSDL2-2.0.0.dylib
+src/lib/libSDL2.a
+src/lib/libSDL2.dylib
+src/lib/libSDL2main.a
+checks.json
+assets/lib/libasar.dll
+cmake/yaze.plist.in
diff --git a/.gitmodules b/.gitmodules
index 4299d8d3..f610c45d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,30 @@
-[submodule "src/Library/imgui"]
- path = src/Library/imgui
+[submodule "src/lib/imgui"]
+ path = src/lib/imgui
url = https://github.com/ocornut/imgui.git
+[submodule "src/lib/ImGuiFileDialog"]
+ path = src/lib/ImGuiFileDialog
+ url = https://github.com/aiekick/ImGuiFileDialog.git
+[submodule "src/lib/ImGuiColorTextEdit"]
+ path = src/lib/ImGuiColorTextEdit
+ url = https://github.com/BalazsJako/ImGuiColorTextEdit.git
+[submodule "src/lib/sneshacking"]
+ path = src/lib/sneshacking
+ url = https://github.com/Skarsnik/sneshacking.git
+[submodule "assets/asm/alttp-hacker-workspace"]
+ path = assets/asm/alttp-hacker-workspace
+ url = https://github.com/scawful/alttp-hacker-workspace.git
+[submodule "src/lib/abseil-cpp"]
+ path = src/lib/abseil-cpp
+ url = https://github.com/abseil/abseil-cpp.git
+[submodule "src/lib/SDL"]
+ path = src/lib/SDL
+ url = https://github.com/libsdl-org/SDL.git
+[submodule "src/lib/asar"]
+ path = src/lib/asar
+ url = https://github.com/RPGHacker/asar.git
+[submodule "src/lib/snes_spc"]
+ path = src/lib/snes_spc
+ url = https://github.com/blarggs-audio-libraries/snes_spc.git
+[submodule "src/lib/SDL_mixer"]
+ path = src/lib/SDL_mixer
+ url = https://github.com/libsdl-org/SDL_mixer.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..1d4fa03c
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,81 @@
+# CMake Specifications --------------------------------------------------------
+cmake_minimum_required(VERSION 3.10)
+
+# Yet Another Zelda3 Editor
+# by scawful
+project(yaze VERSION 0.01)
+
+# C++ Standard Specifications -------------------------------------------------
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS ON)
+set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+set(CMAKE_MODULE_LINKER_FLAGS \"-Wl,--no-undefined -Wl,--no-undefined\")
+set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
+set(BUILD_SHARED_LIBS ON)
+
+# Abseil Standard Specifications ----------------------------------------------
+include(cmake/absl.cmake)
+add_subdirectory(src/lib/abseil-cpp)
+
+include(cmake/openssl.cmake)
+set(Protobuf_PROTOC_EXECUTABLE "/Users/scawful/code/protobuf/bin/protoc")
+set(Protobuf_INCLUDE_DIRS "/Users/scawful/code/protobuf/include/")
+set(PROTOBUF_INCLUDE_PATH ${CMAKE_CURRENT_BINARY_DIR}
+ CACHE INTERNAL "Path to generated protobuf files.")
+include_directories(${PROTOBUF_INCLUDE_PATH})
+
+# Video Libraries -------------------------------------------------------------
+find_package(PNG REQUIRED)
+find_package(OpenGL REQUIRED)
+find_package(GLEW REQUIRED)
+
+# SDL2 ------------------------------------------------------------------------
+if (UNIX)
+ add_subdirectory(src/lib/SDL)
+else()
+ find_package(SDL2)
+endif()
+set(SDL2MIXER_OPUS OFF)
+set(SDL2MIXER_FLAC OFF)
+set(SDL2MIXER_MOD OFF)
+set(SDL2MIXER_MIDI_FLUIDSYNTH OFF)
+find_library(SDL_MIXER_LIBRARY
+ NAMES SDL_mixer
+ HINTS
+ ENV SDLMIXERDIR
+ ENV SDLDIR
+ PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
+ )
+add_subdirectory(src/lib/SDL_mixer)
+
+# Asar ------------------------------------------------------------------------
+add_subdirectory(src/lib/asar/src)
+include(cmake/asar.cmake)
+
+# snes-spc --------------------------------------------------------------------
+ADD_DEFINITIONS(-DSNES_SPC_EXPORTS)
+set(SNES_SPC_SOURCES
+ "../src/lib/snes_spc/snes_spc/spc.cpp"
+ "../src/lib/snes_spc/snes_spc/SNES_SPC.cpp"
+ "../src/lib/snes_spc/snes_spc/SNES_SPC_misc.cpp"
+ "../src/lib/snes_spc/snes_spc/SNES_SPC_state.cpp"
+ "../src/lib/snes_spc/snes_spc/SPC_DSP.cpp"
+ "../src/lib/snes_spc/snes_spc/dsp.cpp"
+ "../src/lib/snes_spc/snes_spc/SPC_Filter.cpp"
+ "../src/lib/snes_spc/demo/wave_writer.c"
+ "../src/lib/snes_spc/demo/demo_util.c"
+)
+include_directories(src/lib/snes_spc/snes_spc)
+ADD_LIBRARY(snes_spc STATIC ${SNES_SPC_SOURCES} src/app/spc700/spc700.def)
+
+# ImGui -----------------------------------------------------------------------
+include(cmake/imgui.cmake)
+
+# Project Files
+add_subdirectory(src)
+add_subdirectory(test)
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..9a09e586
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+Copyright (C) 2022 Justin Scofield
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..b0c941af
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+# Yet Another Zelda3 Editor
+
+- Platform: Windows, macOS, GNU/Linux
+- Dependencies: SDL2, ImGui
+
+## Description
+
+General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo.
+
+Takes heavy inspiration from ALTTP community efforts such as [Hyrule Magic](https://www.romhacking.net/utilities/200/) and [ZScream](https://github.com/Zarby89/ZScreamDungeon)
+
+Building and installation
+-------------------------
+[CMake](http://www.cmake.org "CMake") is required to build yaze
+
+1. Clone the repository
+
+ git clone --recurse-submodules https://github.com/scawful/yaze.git
+
+2. Create the build directory and configuration
+
+ cmake -S . -B build
+
+3. Build and run.
+
+ cmake --build build
+
+License
+--------
+YAZE is distributed under the [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.txt) license.
+
+SDL2, ImGUI and Abseil are subject to respective licenses.
+
+Screenshots
+--------
+
+
+
diff --git a/assets/asm/alttp-hacker-workspace b/assets/asm/alttp-hacker-workspace
new file mode 160000
index 00000000..ea81eb34
--- /dev/null
+++ b/assets/asm/alttp-hacker-workspace
@@ -0,0 +1 @@
+Subproject commit ea81eb34251c4e77703791f710f5b60a8a063d21
diff --git a/assets/asm/mosaic_change.asm b/assets/asm/mosaic_change.asm
new file mode 100644
index 00000000..d4b08b9f
--- /dev/null
+++ b/assets/asm/mosaic_change.asm
@@ -0,0 +1,32 @@
+
+org
+ JML AreaCheck
+
+org
+
+AreaCheck:
+ PHB : PHK : PLB
+
+ TAX
+ LDA .pool, X
+
+ BEQ .noMosaic1
+ PLB
+ JML $02AAE5
+
+ .noMosaic1
+
+ LDX $8A
+ LDA .pool, X
+
+ BEQ .noMosaic2
+ PLB
+ JML $02AAE5
+
+ .noMosaic2
+
+ PLB
+ JML $02AAF4
+
+ NOP
+ .pool
diff --git a/assets/asm/template_song.asm b/assets/asm/template_song.asm
new file mode 100644
index 00000000..01c33d20
--- /dev/null
+++ b/assets/asm/template_song.asm
@@ -0,0 +1,160 @@
+;
+; Credit to Zarby89
+;
+lorom
+
+!End = $00
+!Rest = $C9
+!Tie = $C8
+
+macro SetChannelVolume(v)
+db $ED,
+endmacro
+
+macro SetMasterVolume(v)
+db $E5,
+endmacro
+
+macro SetTempo(v)
+db $E7,
+endmacro
+
+macro SetInstrument(v)
+db $E0,
+endmacro
+
+macro CallSubroutine(addr, repeat)
+db $EF
+dw
+db
+endmacro
+
+;1/4 = $48
+;1/4 double = $6C
+;1/4 triplet = $30
+;1/8 = $24
+;1/8 double = $36
+;1/8 triplet = $18
+;1/16 = $12
+;1/16 double = $1B
+;1/32 = $09
+; To make a whole note you tie 4 1/4 so something like
+;%SetDuration(48)
+;db !C4, !Tie, !Tie, !Tie ; will play a whole note (1/1)
+;db !C4, !Tie ; will play a half note (1/2)
+
+macro SetDuration(v)
+db , $7F
+endmacro
+
+
+!C1 = $80
+!C1s = $81
+!D1 = $82
+!D1s = $83
+!E1 = $84
+!F1 = $85
+!F1s = $86
+!G1 = $87
+!G1s = $88
+!A1 = $89
+!A1s = $8A
+!B1 = $8B
+
+
+!C2 = $8C
+!C2s = $8D
+!D2 = $8E
+!D2s = $8F
+!E2 = $90
+!F2 = $91
+!F2s = $92
+!G2 = $93
+!G2s = $94
+!A2 = $95
+!A2s = $96
+!B2 = $97
+
+
+!C3 = $98
+!C3s = $99
+!D3 = $9A
+!D3s = $9B
+!E3 = $9C
+!F3 = $9D
+!F3s = $9E
+!G3 = $9F
+!G3s = $A0
+!A3 = $A1
+!A3s = $A2
+!B3 = $A3
+
+!C4 = $A4
+!C4s = $A5
+!D4 = $A6
+!D4s = $A7
+!E4 = $A8
+!F4 = $A9
+!F4s = $AA
+!G4 = $AB
+!G4s = $AC
+!A4 = $AD
+!A4s = $AE
+!B4 = $AF
+
+!C5 = $B0
+!C5s = $B1
+!D5 = $B2
+!D5s = $B3
+!E5 = $B4
+!F5 = $B5
+!F5s = $B6
+!G5 = $B7
+!G5s = $B8
+!A5 = $B9
+!A5s = $BA
+!B5 = $BB
+
+!C6 = $BC
+!C6s = $BD
+!D6 = $BE
+!D6s = $BF
+!E6 = $C0
+!F6 = $C1
+!F6s = $C2
+!G6 = $C3
+!G6s = $C4
+!A6 = $C5
+!A6s = $C6
+!B6 = $C7
+
+org $1A9FF8 ; Hyrule Castle (Song Header information)
+Sections:
+!ARAMAddr = $D0FF
+!StartingAddr = Sections
+dw !ARAMAddr+$0A
+dw !ARAMAddr+$0A
+dw $00FF
+dw !ARAMAddr
+dw $0000
+
+Channels:
+!ARAMC = !ARAMAddr-Sections
+dw Channel0+!ARAMC
+dw $0000
+dw $0000
+dw $0000
+dw $0000
+dw $0000
+dw $0000
+dw $0000
+
+
+Channel0:
+SetMasterVolume($80)
+SetTempo($40)
+SetInstrument($17)
+
+db !Rest, !Rest, !Rest
+
+db !End
\ No newline at end of file
diff --git a/assets/font/Cousine-Regular.ttf b/assets/font/Cousine-Regular.ttf
new file mode 100644
index 00000000..70a0bf90
Binary files /dev/null and b/assets/font/Cousine-Regular.ttf differ
diff --git a/assets/font/DroidSans.ttf b/assets/font/DroidSans.ttf
new file mode 100644
index 00000000..767c63ad
Binary files /dev/null and b/assets/font/DroidSans.ttf differ
diff --git a/assets/font/Karla-Regular.ttf b/assets/font/Karla-Regular.ttf
new file mode 100644
index 00000000..81b3de6e
Binary files /dev/null and b/assets/font/Karla-Regular.ttf differ
diff --git a/assets/font/MaterialIcons-Regular.ttf b/assets/font/MaterialIcons-Regular.ttf
new file mode 100644
index 00000000..5d0494ad
Binary files /dev/null and b/assets/font/MaterialIcons-Regular.ttf differ
diff --git a/assets/font/Roboto-Medium.ttf b/assets/font/Roboto-Medium.ttf
new file mode 100644
index 00000000..39c63d74
Binary files /dev/null and b/assets/font/Roboto-Medium.ttf differ
diff --git a/cmake/absl.cmake b/cmake/absl.cmake
new file mode 100644
index 00000000..912ce128
--- /dev/null
+++ b/cmake/absl.cmake
@@ -0,0 +1,19 @@
+set(ABSL_PROPAGATE_CXX_STD ON)
+set(ABSL_CXX_STANDARD 17)
+set(ABSL_USE_GOOGLETEST_HEAD ON)
+set(ABSL_ENABLE_INSTALL ON)
+set(
+ ABSL_TARGETS
+ absl::strings
+ absl::flags
+ absl::status
+ absl::statusor
+ absl::examine_stack
+ absl::stacktrace
+ absl::base
+ absl::config
+ absl::core_headers
+ absl::raw_logging_internal
+ absl::failure_signal_handler
+ absl::flat_hash_map
+)
\ No newline at end of file
diff --git a/cmake/asar.cmake b/cmake/asar.cmake
new file mode 100644
index 00000000..278a264a
--- /dev/null
+++ b/cmake/asar.cmake
@@ -0,0 +1,33 @@
+get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES)
+target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR})
+set(ASAR_GEN_EXE OFF)
+set(ASAR_GEN_DLL ON)
+set(ASAR_GEN_LIB ON)
+set(ASAR_GEN_EXE_TEST OFF)
+set(ASAR_GEN_DLL_TEST OFF)
+
+set(ASAR_STATIC_SRC
+ "../src/lib/asar/src/asar/interface-lib.cpp"
+ "../src/lib/asar/src/asar/addr2line.cpp"
+ "../src/lib/asar/src/asar/arch-65816.cpp"
+ "../src/lib/asar/src/asar/arch-spc700.cpp"
+ "../src/lib/asar/src/asar/arch-superfx.cpp"
+ "../src/lib/asar/src/asar/assembleblock.cpp"
+ "../src/lib/asar/src/asar/crc32.cpp"
+ "../src/lib/asar/src/asar/libcon.cpp"
+ "../src/lib/asar/src/asar/libsmw.cpp"
+ "../src/lib/asar/src/asar/libstr.cpp"
+ "../src/lib/asar/src/asar/macro.cpp"
+ "../src/lib/asar/src/asar/main.cpp"
+ "../src/lib/asar/src/asar/asar_math.cpp"
+ "../src/lib/asar/src/asar/virtualfile.cpp"
+ "../src/lib/asar/src/asar/warnings.cpp"
+ "../src/lib/asar/src/asar/errors.cpp"
+ "../src/lib/asar/src/asar/platform/file-helpers.cpp"
+)
+
+if(WIN32 OR MINGW)
+ list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/windows/file-helpers-win32.cpp")
+else()
+ list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/linux/file-helpers-linux.cpp")
+endif()
\ No newline at end of file
diff --git a/cmake/imgui.cmake b/cmake/imgui.cmake
new file mode 100644
index 00000000..cf434b89
--- /dev/null
+++ b/cmake/imgui.cmake
@@ -0,0 +1,35 @@
+# gui libraries ---------------------------------------------------------------
+set(IMGUI_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui)
+file(GLOB IMGUI_SOURCES ${IMGUI_PATH}/*.cpp)
+add_library("ImGui" STATIC ${IMGUI_SOURCES})
+target_include_directories("ImGui" PUBLIC ${IMGUI_PATH})
+target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR})
+target_compile_definitions(ImGui PUBLIC
+ IMGUI_IMPL_OPENGL_LOADER_CUSTOM= GL_GLEXT_PROTOTYPES=1)
+
+set(IMGUI_FILE_DLG_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiFileDialog)
+file(GLOB IMGUI_FILE_DLG_SOURCES ${IMGUI_FILE_DLG_PATH}/*.cpp)
+add_library("ImGuiFileDialog" STATIC ${IMGUI_FILE_DLG_SOURCES})
+target_include_directories(ImGuiFileDialog PUBLIC ${IMGUI_PATH})
+target_compile_definitions(ImGuiFileDialog PUBLIC
+ IMGUI_IMPL_OPENGL_LOADER_CUSTOM= GL_GLEXT_PROTOTYPES=1)
+
+set(IMGUI_COLOR_TEXT_EDIT_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiColorTextEdit)
+file(GLOB IMGUI_COLOR_TEXT_EDIT_SOURCES ${IMGUI_COLOR_TEXT_EDIT_PATH}/*.cpp)
+add_library("ImGuiColorTextEdit" STATIC ${IMGUI_COLOR_TEXT_EDIT_SOURCES})
+target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
+target_compile_definitions(ImGuiColorTextEdit PUBLIC
+ IMGUI_IMPL_OPENGL_LOADER_CUSTOM= GL_GLEXT_PROTOTYPES=1)
+
+set(
+ IMGUI_SRC
+ ${IMGUI_PATH}/imgui.cpp
+ ${IMGUI_PATH}/imgui_demo.cpp
+ ${IMGUI_PATH}/imgui_draw.cpp
+ ${IMGUI_PATH}/imgui_widgets.cpp
+ ${IMGUI_PATH}/backends/imgui_impl_sdl.cpp
+ ${IMGUI_PATH}/backends/imgui_impl_sdlrenderer.cpp
+ ${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
+ ${IMGUI_FILE_DLG_PATH}/ImGuiFileDialog.cpp
+ ${IMGUI_COLOR_TEXT_EDIT_PATH}/TextEditor.cpp
+)
\ No newline at end of file
diff --git a/cmake/openssl.cmake b/cmake/openssl.cmake
new file mode 100644
index 00000000..d2da52c0
--- /dev/null
+++ b/cmake/openssl.cmake
@@ -0,0 +1,5 @@
+if (UNIX)
+ set(OPENSSL_INCLUDE_DIR "/usr/local/Cellar/openssl@1.1/1.1.1q/include")
+ set(OPENSSL_CRYPTO_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libcrypto.dylib")
+ set(OPENSSL_SSL_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libssl.dylib")
+endif()
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 00000000..5e74e4a0
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,26 @@
+## September 2022
+
+- Drawing Overworld maps to the screen
+- Drawing entrance data on the overworld
+- Drawing 2bpp inventory graphics data
+- Started the YazeDelta project for version control.
+
+## August 2022
+
+- Added ValidateCompressionResults to ROM::Compress
+- Improved Overworld systems in preparation for drawing maps.
+
+## July 2022
+
+- Display current overworld map graphics tile sheets.
+- Added CreateAllGraphicsData to the ROM class
+- Added Google Abseil C++ library for error handling, string manipulation
+- Refactor ROM class to use smart pointers and STL containers
+
+## June 2022
+
+- Implemented LC_LZ2 Decompression
+- Created Bitmap class for displaying SNES Graphics
+- Added Overworld and OverworldMap class definitions
+- Built user interface using ImGui and SDL2
+- Started YAZE
\ No newline at end of file
diff --git a/docs/dev-setup-windows.md b/docs/dev-setup-windows.md
new file mode 100644
index 00000000..8688b3f9
--- /dev/null
+++ b/docs/dev-setup-windows.md
@@ -0,0 +1,17 @@
+
+For VSCode users, use the following CMake extensions with MinGW-w64
+
+https://marketplace.visualstudio.com/items?itemName=twxs.cmake
+https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
+
+https://www.msys2.org/
+
+Add to environment variables `C:\msys64\mingw64\bin`
+
+Install the following packages using `pacman -S `
+
+`mingw-w64-x86_64-gcc`
+`mingw-w64-x86_64-gcc-libs`
+`mingw-w64-x86_64-cmake`
+`mingw-w64-x86_64-glew`
+`mingw-w64-x86_64-lib-png`
\ No newline at end of file
diff --git a/docs/manual/manual.pdf b/docs/manual/manual.pdf
new file mode 100644
index 00000000..974fd6fc
Binary files /dev/null and b/docs/manual/manual.pdf differ
diff --git a/docs/manual/manual.tex b/docs/manual/manual.tex
new file mode 100644
index 00000000..4574d023
--- /dev/null
+++ b/docs/manual/manual.tex
@@ -0,0 +1,40 @@
+\documentclass[12pt, oneside]{report}
+\title{Yet Another Zelda3 Editor}
+\author{Justin Scofield\thanks{Special thanks to JaredBrian, Zarby89}}
+\date{June 2022 - October 2022}
+\pagestyle{headings}
+
+\begin{document}
+\maketitle
+
+\tableofcontents
+
+\chapter{Introduction}
+
+{\bf Yet Another Zelda3 Editor} is a multi-purpose editor for the retro video game title {\it {"The Legend of Zelda: A Link to the Past"}} for the Super Nintendo Entertainment System. The editor only supports the US version.
+
+\section{Getting Started}
+\section{Loading from ROM}
+\section{Saving to ROM}
+
+\chapter{Overworld}
+
+ The editor provides an interface for the user to make various changes to the overworld maps. These changes include the manpulation of the maps tiles, palettes, entrances, exits, sprites, area music, and other properties. Here we will explain the basics of the tile system.
+
+ \section{Tile System}
+ \section{Map Toolset}
+ \section{Map Canvas}
+
+\chapter{Dungeons}
+
+\chapter{Palettes}
+
+\chapter{Sprites}
+
+\chapter{Screens}
+ \section{Inventory}
+ \section{Heads-up Display}
+
+\chapter{Modules}
+
+\end{document}
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 00000000..e3b147ad
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,183 @@
+# yaze source files -----------------------------------------------------------
+set(
+ YAZE_APP_CORE_SRC
+ app/core/common.cc
+ app/core/controller.cc
+)
+
+set(
+ YAZE_APP_EDITOR_SRC
+ app/editor/assembly_editor.cc
+ app/editor/dungeon_editor.cc
+ app/editor/master_editor.cc
+ app/editor/music_editor.cc
+ app/editor/overworld_editor.cc
+ app/editor/palette_editor.cc
+ app/editor/screen_editor.cc
+)
+
+set(
+ YAZE_APP_GFX_SRC
+ app/gfx/bitmap.cc
+ app/gfx/snes_palette.cc
+ app/gfx/snes_tile.cc
+)
+
+set(
+ YAZE_APP_ZELDA3_SRC
+ app/zelda3/inventory.cc
+ app/zelda3/overworld_map.cc
+ app/zelda3/overworld.cc
+ app/zelda3/title_screen.cc
+ app/zelda3/sprite.cc
+)
+
+set(
+ YAZE_GUI_SRC
+ gui/canvas.cc
+ gui/input.cc
+ gui/style.cc
+ gui/widgets.cc
+ gui/color.cc
+)
+
+# executable creation ---------------------------------------------------------
+
+add_executable(
+ yaze
+ app/yaze.cc
+ app/rom.cc
+ ${YAZE_APP_CORE_SRC}
+ ${YAZE_APP_EDITOR_SRC}
+ ${YAZE_APP_GFX_SRC}
+ ${YAZE_APP_ZELDA3_SRC}
+ ${YAZE_GUI_SRC}
+ ${ASAR_STATIC_SRC}
+ ${SNES_SPC_SOURCES}
+ ${IMGUI_SRC}
+)
+
+# including libraries ---------------------------------------------------------
+
+target_include_directories(
+ yaze PUBLIC
+ lib/
+ app/
+ ${CMAKE_SOURCE_DIR}/src/
+ ${PNG_INCLUDE_DIRS}
+ ${SDL2_INCLUDE_DIR}
+ lib/SDL_mixer/include/
+ ${GLEW_INCLUDE_DIRS}
+ lib/asar/src/asar/
+ lib/snes_spc/snes_spc/
+)
+
+set(SDL_TARGETS SDL2::SDL2)
+
+if(WIN32 OR MINGW)
+ list(PREPEND SDL_TARGETS SDL2::SDL2main)
+ add_definitions(-DSDL_MAIN_HANDLED)
+endif()
+
+# linking libraries -----------------------------------------------------------
+
+target_link_libraries(
+ yaze PUBLIC
+ ${ABSL_TARGETS}
+ ${SDL_TARGETS}
+ ${SDLMIXER_LIBRARY}
+ SDL2_mixer
+ ${PNG_LIBRARIES}
+ ${GLEW_LIBRARIES}
+ ${OPENGL_LIBRARIES}
+ ${CMAKE_DL_LIBS}
+ asar-static
+ snes_spc
+ ImGui
+)
+
+if (UNIX)
+ target_compile_definitions(yaze PRIVATE "linux")
+ target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
+endif()
+
+if(MACOS)
+ set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/yaze.ico)
+ set_target_properties(yaze
+ PROPERTIES
+ BUNDLE True
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+ # MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
+ )
+elseif(UNIX)
+ set_target_properties(yaze
+ PROPERTIES
+ BUNDLE True
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+ )
+else()
+set_target_properties(yaze
+ PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+ LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
+)
+endif()
+
+# add_subdirectory(app/delta)
+
+# add_executable(
+# yaze_delta
+# app/delta/delta.cc
+# app/delta/viewer.cc
+# app/delta/service.cc
+# app/delta/client.cc
+# app/rom.cc
+# ${YAZE_APP_ASM_SRC}
+# ${YAZE_APP_CORE_SRC}
+# ${YAZE_APP_EDITOR_SRC}
+# ${YAZE_APP_GFX_SRC}
+# ${YAZE_APP_ZELDA3_SRC}
+# ${YAZE_GUI_SRC}
+# ${IMGUI_SRC}
+# ${ASAR_STATIC_SRC}
+# )
+
+# target_include_directories(
+# yaze_delta PUBLIC
+# lib/
+# app/
+# lib/asar/src/
+# ${ASAR_INCLUDE_DIR}
+# ${CMAKE_SOURCE_DIR}/src/
+# ${PNG_INCLUDE_DIRS}
+# ${SDL2_INCLUDE_DIR}
+# ${GLEW_INCLUDE_DIRS}
+# )
+
+# target_link_libraries(
+# yaze_delta PUBLIC
+# ${ABSL_TARGETS}
+# ${SDL_TARGETS}
+# ${PNG_LIBRARIES}
+# ${GLEW_LIBRARIES}
+# ${OPENGL_LIBRARIES}
+# ${CMAKE_DL_LIBS}
+# delta-service
+# asar-static
+# ImGui
+# )
+# target_compile_definitions(yaze_delta PRIVATE "linux")
+# target_compile_definitions(yaze_delta PRIVATE "stricmp=strcasecmp")
+
+# set (source "${CMAKE_SOURCE_DIR}/assets")
+# set (destination "${CMAKE_CURRENT_BINARY_DIR}/assets")
+# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
+# COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination}
+# DEPENDS ${destination}
+# COMMENT "symbolic link resources folder from ${source} => ${destination}")
\ No newline at end of file
diff --git a/src/Library/imgui b/src/Library/imgui
deleted file mode 160000
index 0b1bcfcc..00000000
--- a/src/Library/imgui
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 0b1bcfcc205077d31493ec851f87ca4b54b4eab5
diff --git a/src/app/core/common.cc b/src/app/core/common.cc
new file mode 100644
index 00000000..a8d0ee85
--- /dev/null
+++ b/src/app/core/common.cc
@@ -0,0 +1,63 @@
+#include "common.h"
+
+#include
+#include
+
+namespace yaze {
+namespace app {
+namespace core {
+
+unsigned int SnesToPc(unsigned int addr) {
+ if (addr >= 0x808000) {
+ addr -= 0x808000;
+ }
+ unsigned int temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
+ return (temp + 0x0);
+}
+
+int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3) {
+ return (addr1 << 16) | (addr2 << 8) | addr3;
+}
+
+// hextodec has been imported from SNESDisasm to parse hex numbers
+int HexToDec(char *input, int length) {
+ int result = 0;
+ int value;
+ int ceiling = length - 1;
+ int power16 = 16;
+
+ int j = ceiling;
+
+ for (; j >= 0; j--) {
+ if (input[j] >= 'A' && input[j] <= 'F') {
+ value = input[j] - 'F';
+ value += 15;
+ } else {
+ value = input[j] - '9';
+ value += 9;
+ }
+
+ if (j == ceiling) {
+ result += value;
+ continue;
+ }
+
+ result += (value * power16);
+ power16 *= 16;
+ }
+
+ return result;
+}
+
+bool StringReplace(std::string &str, const std::string &from,
+ const std::string &to) {
+ size_t start = str.find(from);
+ if (start == std::string::npos) return false;
+
+ str.replace(start, from.length(), to);
+ return true;
+}
+
+} // namespace core
+} // namespace app
+} // namespace yaze
diff --git a/src/app/core/common.h b/src/app/core/common.h
new file mode 100644
index 00000000..4a6969a5
--- /dev/null
+++ b/src/app/core/common.h
@@ -0,0 +1,21 @@
+#ifndef YAZE_CORE_COMMON_H
+#define YAZE_CORE_COMMON_H
+
+#include
+#include
+
+namespace yaze {
+namespace app {
+namespace core {
+
+unsigned int SnesToPc(unsigned int addr);
+int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3);
+int HexToDec(char *input, int length);
+bool StringReplace(std::string &str, const std::string &from,
+ const std::string &to);
+
+} // namespace core
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/core/constants.h b/src/app/core/constants.h
new file mode 100644
index 00000000..94f957ba
--- /dev/null
+++ b/src/app/core/constants.h
@@ -0,0 +1,1604 @@
+#ifndef YAZE_APP_CORE_CONSTANTS_H
+#define YAZE_APP_CORE_CONSTANTS_H
+
+#include
+
+#include "absl/strings/string_view.h"
+
+#define BASIC_BUTTON(w) if (ImGui::Button(w))
+
+#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
+#define END_TAB_BAR() \
+ ImGui::EndTabBar(); \
+ }
+
+#define TAB_ITEM(w) if (ImGui::BeginTabItem(w)) {
+#define END_TAB_ITEM() \
+ ImGui::EndTabItem(); \
+ }
+
+#define MENU_BAR() if (ImGui::BeginMenuBar()) {
+#define END_MENU_BAR() \
+ ImGui::EndMenuBar(); \
+ }
+
+#define MENU_ITEM(w) if (ImGui::MenuItem(w))
+#define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v))
+
+#define BUTTON_COLUMN(w) \
+ ImGui::TableNextColumn(); \
+ ImGui::Button(w);
+
+#define TEXT_COLUMN(w) \
+ ImGui::TableNextColumn(); \
+ ImGui::Text(w);
+
+#define PRINT_IF_ERROR(expression) \
+ { \
+ auto error = expression; \
+ if (!error.ok()) { \
+ std::cout << error.ToString() << std::endl; \
+ } \
+ }
+
+#define RETURN_IF_ERROR(expression) \
+ { \
+ auto error = expression; \
+ if (!error.ok()) { \
+ return error; \
+ } \
+ }
+
+#define ASSIGN_OR_RETURN(type_variable_name, expression) \
+ ASSIGN_OR_RETURN_IMPL(APPEND_NUMBER(error_or_value, __LINE__), \
+ type_variable_name, expression)
+
+#define ASSIGN_OR_RETURN_IMPL(error_or_value, type_variable_name, expression) \
+ auto error_or_value = expression; \
+ if (!error_or_value.ok()) { \
+ return error_or_value.status(); \
+ } \
+ type_variable_name = std::move(*error_or_value);
+
+#define APPEND_NUMBER(expression, number) \
+ APPEND_NUMBER_INNER(expression, number)
+
+#define APPEND_NUMBER_INNER(expression, number) expression##number
+
+using ushort = unsigned short;
+using uint = unsigned int;
+using uchar = unsigned char;
+using Bytes = std::vector;
+
+using OWBlockset = std::vector>;
+struct OWMapTiles {
+ OWBlockset light_world; // 64 maps
+ OWBlockset dark_world; // 64 maps
+ OWBlockset special_world; // 32 maps
+};
+using OWMapTiles = struct OWMapTiles;
+
+namespace yaze {
+namespace app {
+namespace core {
+
+// ============================================================================
+// Window Variables
+// ============================================================================
+
+constexpr int kScreenWidth = 1200;
+constexpr int kScreenHeight = 800;
+
+// ============================================================================
+// 65816 LanguageDefinition
+// ============================================================================
+
+static const char *const kKeywords[] = {
+ "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE",
+ "BPL", "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV",
+ "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX",
+ "INY", "JMP", "JSR", "JSL", "LDA", "LDX", "LDY", "LSR", "MVN",
+ "NOP", "ORA", "PEA", "PER", "PHA", "PHB", "PHD", "PHP", "PHX",
+ "PHY", "PLA", "PLB", "PLD", "PLP", "PLX", "PLY", "REP", "ROL",
+ "ROR", "RTI", "RTL", "RTS", "SBC", "SEC", "SEI", "SEP", "STA",
+ "STP", "STX", "STY", "STZ", "TAX", "TAY", "TCD", "TCS", "TDC",
+ "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA", "TYX",
+ "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM", "NAMESPACE", "DB"};
+
+static const char *const kIdentifiers[] = {
+ "abort", "abs", "acos", "asin", "atan", "atexit",
+ "atof", "atoi", "atol", "ceil", "clock", "cosh",
+ "ctime", "div", "exit", "fabs", "floor", "fmod",
+ "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph",
+ "ispunct", "isspace", "isupper", "kbhit", "log10", "log2",
+ "log", "memcmp", "modf", "pow", "putchar", "putenv",
+ "puts", "rand", "remove", "rename", "sinh", "sqrt",
+ "srand", "strcat", "strcmp", "strerror", "time", "tolower",
+ "toupper"};
+
+// ============================================================================
+// Magic numbers
+// ============================================================================
+
+/// Bit set for object priority
+constexpr ushort TilePriorityBit = 0x2000;
+
+/// Bit set for object hflip
+constexpr ushort TileHFlipBit = 0x4000;
+
+/// Bit set for object vflip
+constexpr ushort TileVFlipBit = 0x8000;
+
+/// Bits used for tile name
+constexpr ushort TileNameMask = 0x03FF;
+
+constexpr int Uncompressed3BPPSize = 0x0600;
+constexpr int UncompressedSheetSize = 0x0800;
+
+constexpr int NumberOfSheets = 223;
+constexpr int LimitOfMap32 = 8864;
+constexpr int NumberOfRooms = 296;
+
+constexpr int kNumOverworldMaps = 160;
+constexpr int Map32PerScreen = 256;
+constexpr int NumberOfMap16 = 3752; // 4096
+constexpr int NumberOfMap32 = Map32PerScreen * kNumOverworldMaps;
+constexpr int NumberOfOWSprites = 352;
+constexpr int NumberOfColors = 3143;
+
+// ============================================================================
+// Game Graphics
+// ============================================================================
+
+constexpr int tile_address = 0x1B52; // JP = Same
+constexpr int tile_address_floor = 0x1B5A; // JP = Same
+constexpr int subtype1_tiles = 0x8000; // JP = Same
+constexpr int subtype2_tiles = 0x83F0; // JP = Same
+constexpr int subtype3_tiles = 0x84F0; // JP = Same
+constexpr int gfx_animated_pointer = 0x10275; // JP 0x10624 //long pointer
+constexpr int overworldgfxGroups2 = 0x6073; // 0x60B3
+
+// 2 byte pointer bank 00 pc -> 0x4320
+constexpr int gfx_1_pointer = 0x6790; // CF80 ; 004F80
+constexpr int gfx_2_pointer = 0x6795; // D05F ; 00505F
+constexpr int gfx_3_pointer = 0x679A; // D13E ; 00513E
+constexpr int hud_palettes = 0xDD660;
+constexpr int maxGfx = 0xC3FB5;
+
+constexpr int kTilesheetWidth = 128;
+constexpr int kTilesheetHeight = 32;
+constexpr int kTilesheetDepth = 8;
+
+// ============================================================================
+// Overworld Related Variables
+// ============================================================================
+
+constexpr int compressedAllMap32PointersHigh = 0x1794D;
+constexpr int compressedAllMap32PointersLow = 0x17B2D;
+constexpr int overworldgfxGroups = 0x05D97;
+constexpr int map16Tiles = 0x78000;
+constexpr int map32TilesTL = 0x18000;
+constexpr int map32TilesTR = 0x1B400;
+constexpr int map32TilesBL = 0x20000;
+constexpr int map32TilesBR = 0x23400;
+constexpr int overworldPalGroup1 = 0xDE6C8;
+constexpr int overworldPalGroup2 = 0xDE86C;
+constexpr int overworldPalGroup3 = 0xDE604;
+constexpr int overworldMapPalette = 0x7D1C;
+constexpr int overworldSpritePalette = 0x7B41;
+constexpr int overworldMapPaletteGroup = 0x75504;
+constexpr int overworldSpritePaletteGroup = 0x75580;
+constexpr int overworldSpriteset = 0x7A41;
+constexpr int overworldSpecialGFXGroup = 0x16821;
+constexpr int overworldSpecialPALGroup = 0x16831;
+
+constexpr int overworldSpritesBegining = 0x4C881;
+constexpr int overworldSpritesAgahnim = 0x4CA21;
+constexpr int overworldSpritesZelda = 0x4C901;
+
+constexpr int overworldItemsPointers = 0xDC2F9;
+constexpr int overworldItemsAddress = 0xDC8B9; // 1BC2F9
+constexpr int overworldItemsBank = 0xDC8BF;
+constexpr int overworldItemsEndData = 0xDC89C; // 0DC89E
+
+constexpr int mapGfx = 0x7C9C;
+constexpr int overlayPointers = 0x77664;
+constexpr int overlayPointersBank = 0x0E;
+
+constexpr int overworldTilesType = 0x71459;
+constexpr int overworldMessages = 0x3F51D;
+
+constexpr int overworldMusicBegining = 0x14303;
+constexpr int overworldMusicZelda = 0x14303 + 0x40;
+constexpr int overworldMusicMasterSword = 0x14303 + 0x80;
+constexpr int overworldMusicAgahim = 0x14303 + 0xC0;
+constexpr int overworldMusicDW = 0x14403;
+
+constexpr int overworldEntranceAllowedTilesLeft = 0xDB8C1;
+constexpr int overworldEntranceAllowedTilesRight = 0xDB917;
+
+// 0x00 = small maps, 0x20 = large maps
+constexpr int overworldMapSize = 0x12844;
+
+// 0x01 = small maps, 0x03 = large maps
+constexpr int overworldMapSizeHighByte = 0x12884;
+
+// relative to the WORLD + 0x200 per map
+// large map that are not == parent id = same position as their parent!
+// eg for X position small maps :
+// 0000, 0200, 0400, 0600, 0800, 0A00, 0C00, 0E00
+// all Large map would be :
+// 0000, 0000, 0400, 0400, 0800, 0800, 0C00, 0C00
+
+constexpr int overworldTransitionPositionY = 0x128C4;
+constexpr int overworldTransitionPositionX = 0x12944;
+
+constexpr int overworldScreenSize = 0x1788D;
+
+// ============================================================================
+// Overworld Exits/Entrances Variables
+// ============================================================================
+constexpr int OWExitRoomId = 0x15D8A; // 0x15E07 Credits sequences
+// 105C2 Ending maps
+// 105E2 Sprite Group Table for Ending
+constexpr int OWExitMapId = 0x15E28;
+constexpr int OWExitVram = 0x15E77;
+constexpr int OWExitYScroll = 0x15F15;
+constexpr int OWExitXScroll = 0x15FB3;
+constexpr int OWExitYPlayer = 0x16051;
+constexpr int OWExitXPlayer = 0x160EF;
+constexpr int OWExitYCamera = 0x1618D;
+constexpr int OWExitXCamera = 0x1622B;
+constexpr int OWExitDoorPosition = 0x15724;
+constexpr int OWExitUnk1 = 0x162C9;
+constexpr int OWExitUnk2 = 0x16318;
+constexpr int OWExitDoorType1 = 0x16367;
+constexpr int OWExitDoorType2 = 0x16405;
+constexpr int OWEntranceMap = 0xDB96F;
+constexpr int OWEntrancePos = 0xDBA71;
+constexpr int OWEntranceEntranceId = 0xDBB73;
+constexpr int OWHolePos = 0xDB800; //(0x13 entries, 2 bytes each) modified(less
+ // 0x400) map16 coordinates for each hole
+constexpr int OWHoleArea =
+ 0xDB826; //(0x13 entries, 2 bytes each) corresponding
+ // area numbers for each hole
+constexpr int OWHoleEntrance =
+ 0xDB84C; //(0x13 entries, 1 byte each) corresponding entrance numbers
+
+constexpr int OWExitMapIdWhirlpool = 0x16AE5; // JP = ;016849
+constexpr int OWExitVramWhirlpool = 0x16B07; // JP = ;01686B
+constexpr int OWExitYScrollWhirlpool = 0x16B29; // JP = ;01688D
+constexpr int OWExitXScrollWhirlpool = 0x16B4B; // JP = ;016DE7
+constexpr int OWExitYPlayerWhirlpool = 0x16B6D; // JP = ;016E09
+constexpr int OWExitXPlayerWhirlpool = 0x16B8F; // JP = ;016E2B
+constexpr int OWExitYCameraWhirlpool = 0x16BB1; // JP = ;016E4D
+constexpr int OWExitXCameraWhirlpool = 0x16BD3; // JP = ;016E6F
+constexpr int OWExitUnk1Whirlpool = 0x16BF5; // JP = ;016E91
+constexpr int OWExitUnk2Whirlpool = 0x16C17; // JP = ;016EB3
+constexpr int OWWhirlpoolPosition = 0x16CF8; // JP = ;016F94
+
+// ============================================================================
+// Dungeon Related Variables
+// ============================================================================
+
+// That could be turned into a pointer :
+constexpr int dungeons_palettes_groups = 0x75460; // JP 0x67DD0
+constexpr int dungeons_main_bg_palette_pointers = 0xDEC4B; // JP Same
+constexpr int dungeons_palettes =
+ 0xDD734; // JP Same (where all dungeons palettes are)
+
+// That could be turned into a pointer :
+constexpr int room_items_pointers = 0xDB69; // JP 0xDB67
+
+constexpr int rooms_sprite_pointer = 0x4C298; // JP Same //2byte bank 09D62E
+constexpr int room_header_pointer = 0xB5DD; // LONG
+constexpr int room_header_pointers_bank = 0xB5E7; // JP Same
+
+constexpr int gfx_groups_pointer = 0x6237;
+constexpr int room_object_layout_pointer = 0x882D;
+
+constexpr int room_object_pointer = 0x874C; // Long pointer
+
+constexpr int chests_length_pointer = 0xEBF6;
+constexpr int chests_data_pointer1 = 0xEBFB;
+// constexpr int chests_data_pointer2 = 0xEC0A; //Disabled for now could be used
+// for expansion constexpr int chests_data_pointer3 = 0xEC10; //Disabled for now
+// could be used for expansion
+
+constexpr int blocks_length = 0x8896; // word value
+constexpr int blocks_pointer1 = 0x15AFA;
+constexpr int blocks_pointer2 = 0x15B01;
+constexpr int blocks_pointer3 = 0x15B08;
+constexpr int blocks_pointer4 = 0x15B0F;
+
+constexpr int torch_data = 0x2736A; // JP 0x2704A
+constexpr int torches_length_pointer = 0x88C1;
+
+constexpr int kSpriteBlocksetPointer = 0x5B57;
+constexpr int sprites_data =
+ 0x4D8B0; // It use the unused pointers to have more space //Save purpose
+constexpr int sprites_data_empty_room = 0x4D8AE;
+constexpr int sprites_end_data = 0x4EC9E;
+
+constexpr int pit_pointer = 0x394AB;
+constexpr int pit_count = 0x394A6;
+
+constexpr int doorPointers = 0xF83C0;
+
+// doors
+constexpr int door_gfx_up = 0x4D9E;
+constexpr int door_gfx_down = 0x4E06;
+constexpr int door_gfx_cavexit_down = 0x4E06;
+constexpr int door_gfx_left = 0x4E66;
+constexpr int door_gfx_right = 0x4EC6;
+
+constexpr int door_pos_up = 0x197E;
+constexpr int door_pos_down = 0x1996;
+constexpr int door_pos_left = 0x19AE;
+constexpr int door_pos_right = 0x19C6;
+
+// TEXT EDITOR RELATED CONSTANTS
+constexpr int gfx_font = 0x70000; // 2bpp format
+constexpr int text_data = 0xE0000;
+constexpr int text_data2 = 0x75F40;
+constexpr int pointers_dictionaries = 0x74703;
+constexpr int characters_width = 0x74ADF;
+
+// ============================================================================
+// Dungeon Entrances Related Variables
+// ============================================================================
+
+// 0x14577 word value for each room
+constexpr int entrance_room = 0x14813;
+
+// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
+constexpr int entrance_scrolledge = 0x1491D; // 0x14681
+constexpr int entrance_yscroll = 0x14D45; // 0x14AA9 2 bytes each room
+constexpr int entrance_xscroll = 0x14E4F; // 0x14BB3 2 bytes
+constexpr int entrance_yposition = 0x14F59; // 0x14CBD 2bytes
+constexpr int entrance_xposition = 0x15063; // 0x14DC7 2bytes
+constexpr int entrance_camerayposition = 0x1516D; // 0x14ED1 2bytes
+constexpr int entrance_cameraxposition = 0x15277; // 0x14FDB 2bytes
+
+constexpr int entrance_gfx_group = 0x5D97;
+constexpr int entrance_blockset = 0x15381; // 0x150E5 1byte
+constexpr int entrance_floor = 0x15406; // 0x1516A 1byte
+constexpr int entrance_dungeon = 0x1548B; // 0x151EF 1byte (dungeon id)
+constexpr int entrance_door = 0x15510; // 0x15274 1byte
+
+// 1 byte, ---b ---a b = bg2, a = need to check
+constexpr int entrance_ladderbg = 0x15595; // 0x152F9
+constexpr int entrance_scrolling = 0x1561A; // 0x1537E 1byte --h- --v-
+constexpr int entrance_scrollquadrant = 0x1569F; // 0x15403 1byte
+constexpr int entrance_exit = 0x15724; // 0x15488 2byte word
+constexpr int entrance_music = 0x1582E; // 0x15592
+
+// word value for each room
+constexpr int startingentrance_room = 0x15B6E; // 0x158D2
+
+// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
+constexpr int startingentrance_scrolledge = 0x15B7C; // 0x158E0
+constexpr int startingentrance_yscroll = 0x15BB4; // 0x14AA9 //2bytes each room
+constexpr int startingentrance_xscroll = 0x15BC2; // 0x14BB3 //2bytes
+constexpr int startingentrance_yposition = 0x15BD0; // 0x14CBD 2bytes
+constexpr int startingentrance_xposition = 0x15BDE; // 0x14DC7 2bytes
+constexpr int startingentrance_camerayposition = 0x15BEC; // 0x14ED1 2bytes
+constexpr int startingentrance_cameraxposition = 0x15BFA; // 0x14FDB 2bytes
+
+constexpr int startingentrance_blockset = 0x15C08; // 0x150E5 1byte
+constexpr int startingentrance_floor = 0x15C0F; // 0x1516A 1byte
+constexpr int startingentrance_dungeon = 0x15C16; // 0x151EF 1byte (dungeon id)
+
+constexpr int startingentrance_door = 0x15C2B; // 0x15274 1byte
+
+// 1 byte, ---b ---a b = bg2, a = need to check
+constexpr int startingentrance_ladderbg = 0x15C1D; // 0x152F9
+// 1byte --h- --v-
+constexpr int startingentrance_scrolling = 0x15C24; // 0x1537E
+constexpr int startingentrance_scrollquadrant = 0x15C2B; // 0x15403 1byte
+constexpr int startingentrance_exit = 0x15C32; // 0x15488 //2byte word
+constexpr int startingentrance_music = 0x15C4E; // 0x15592
+constexpr int startingentrance_entrance = 0x15C40;
+
+constexpr int items_data_start = 0xDDE9; // save purpose
+constexpr int items_data_end = 0xE6B2; // save purpose
+constexpr int initial_equipement = 0x271A6;
+constexpr int messages_id_dungeon = 0x3F61D;
+
+// item id you get instead if you already have that item
+constexpr int chests_backupitems = 0x3B528;
+constexpr int chests_yoffset = 0x4836C;
+constexpr int chests_xoffset = 0x4836C + (76 * 1);
+constexpr int chests_itemsgfx = 0x4836C + (76 * 2);
+constexpr int chests_itemswide = 0x4836C + (76 * 3);
+constexpr int chests_itemsproperties = 0x4836C + (76 * 4);
+constexpr int chests_sramaddress = 0x4836C + (76 * 5);
+constexpr int chests_sramvalue = 0x4836C + (76 * 7);
+constexpr int chests_msgid = 0x442DD;
+
+constexpr int dungeons_startrooms = 0x7939;
+constexpr int dungeons_endrooms = 0x792D;
+constexpr int dungeons_bossrooms = 0x10954; // short value
+
+// Bed Related Values (Starting location)
+constexpr int bedPositionX = 0x039A37; // short value
+constexpr int bedPositionY = 0x039A32; // short value
+
+// short value (on 2 different bytes)
+constexpr int bedPositionResetXLow = 0x02DE53;
+constexpr int bedPositionResetXHigh = 0x02DE58;
+
+// short value (on 2 different bytes)
+constexpr int bedPositionResetYLow = 0x02DE5D;
+constexpr int bedPositionResetYHigh = 0x02DE62;
+
+constexpr int bedSheetPositionX = 0x0480BD; // short value
+constexpr int bedSheetPositionY = 0x0480B8; // short value
+
+// ============================================================================
+// Gravestones related variables
+// ============================================================================
+
+constexpr int GravesYTilePos = 0x49968; // short (0x0F entries)
+constexpr int GravesXTilePos = 0x49986; // short (0x0F entries)
+constexpr int GravesTilemapPos = 0x499A4; // short (0x0F entries)
+constexpr int GravesGFX = 0x499C2; // short (0x0F entries)
+
+constexpr int GravesXPos = 0x4994A; // short (0x0F entries)
+constexpr int GravesYLine = 0x4993A; // short (0x08 entries)
+constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
+
+constexpr int GraveLinkSpecialHole = 0x46DD9; // short
+constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
+
+// ============================================================================
+// Palettes Related Variables - This contain all the palettes of the game
+// ============================================================================
+constexpr int overworldPaletteMain = 0xDE6C8;
+constexpr int overworldPaletteAuxialiary = 0xDE86C;
+constexpr int overworldPaletteAnimated = 0xDE604;
+constexpr int globalSpritePalettesLW = 0xDD218;
+constexpr int globalSpritePalettesDW = 0xDD290;
+// Green, Blue, Red, Bunny, Electrocuted (15 colors each)
+constexpr int armorPalettes = 0xDD308;
+constexpr int spritePalettesAux1 = 0xDD39E; // 7 colors each
+constexpr int spritePalettesAux2 = 0xDD446; // 7 colors each
+constexpr int spritePalettesAux3 = 0xDD4E0; // 7 colors each
+constexpr int swordPalettes = 0xDD630; // 3 colors each - 4 entries
+constexpr int shieldPalettes = 0xDD648; // 4 colors each - 3 entries
+constexpr int hudPalettes = 0xDD660;
+constexpr int dungeonMapPalettes = 0xDD70A; // 21 colors
+constexpr int dungeonMainPalettes = 0xDD734; //(15*6) colors each - 20 entries
+constexpr int dungeonMapBgPalettes = 0xDE544; // 16*6
+// Mirrored Value at 0x75645 : 0x75625
+constexpr int hardcodedGrassLW = 0x5FEA9;
+constexpr int hardcodedGrassDW = 0x05FEB3; // 0x7564F
+constexpr int hardcodedGrassSpecial = 0x75640;
+constexpr int overworldMiniMapPalettes = 0x55B27;
+constexpr int triforcePalette = 0x64425;
+constexpr int crystalPalette = 0xF4CD3;
+constexpr int customAreaSpecificBGPalette =
+ 0x140000; // 2 bytes for each overworld area (320)
+constexpr int customAreaSpecificBGASM = 0x140150;
+constexpr int customAreaSpecificBGEnabled =
+ 0x140140; // 1 byte, not 0 if enabled
+constexpr int overworldCustomMosaicArray = 0x1301F0;
+// ============================================================================
+// Dungeon Map Related Variables
+// ============================================================================
+
+constexpr int dungeonMap_rooms_ptr = 0x57605; // 14 pointers of map data
+constexpr int dungeonMap_floors = 0x575D9; // 14 words values
+
+constexpr int dungeonMap_gfx_ptr = 0x57BE4; // 14 pointers of gfx data
+
+// data start for floors/gfx MUST skip 575D9 to 57621 (pointers)
+constexpr int dungeonMap_datastart = 0x57039;
+
+// IF Byte = 0xB9 dungeon maps are not expanded
+constexpr int dungeonMap_expCheck = 0x56652;
+constexpr int dungeonMap_tile16 = 0x57009;
+constexpr int dungeonMap_tile16Exp = 0x109010;
+
+// 14 words values 0x000F = no boss
+constexpr int dungeonMap_bossrooms = 0x56807;
+constexpr int triforceVertices = 0x04FFD2; // group of 3, X, Y ,Z
+constexpr int TriforceFaces = 0x04FFE4; // group of 5
+
+constexpr int crystalVertices = 0x04FF98;
+
+// ============================================================================
+// Names
+// ============================================================================
+
+static const absl::string_view RoomEffect[] = {"Nothing",
+ "Nothing",
+ "Moving Floor",
+ "Moving Water",
+ "Trinexx Shell",
+ "Red Flashes",
+ "Light Torch to See Floor",
+ "Ganon's Darkness"};
+
+static const absl::string_view RoomTag[] = {"Nothing",
+
+ "NW Kill Enemy to Open",
+ "NE Kill Enemy to Open",
+ "SW Kill Enemy to Open",
+ "SE Kill Enemy to Open",
+ "W Kill Enemy to Open",
+ "E Kill Enemy to Open",
+ "N Kill Enemy to Open",
+ "S Kill Enemy to Open",
+ "Clear Quadrant to Open",
+ "Clear Full Tile to Open",
+
+ "NW Push Block to Open",
+ "NE Push Block to Open",
+ "SW Push Block to Open",
+ "SE Push Block to Open",
+ "W Push Block to Open",
+ "E Push Block to Open",
+ "N Push Block to Open",
+ "S Push Block to Open",
+ "Push Block to Open",
+ "Pull Lever to Open",
+ "Collect Prize to Open",
+
+ "Hold Switch Open Door",
+ "Toggle Switch to Open Door",
+ "Turn off Water",
+ "Turn on Water",
+ "Water Gate",
+ "Water Twin",
+ "Moving Wall Right",
+ "Moving Wall Left",
+ "Crash",
+ "Crash",
+ "Push Switch Exploding Wall",
+ "Holes 0",
+ "Open Chest (Holes 0)",
+ "Holes 1",
+ "Holes 2",
+ "Defeat Boss for Dungeon Prize",
+
+ "SE Kill Enemy to Push Block",
+ "Trigger Switch Chest",
+ "Pull Lever Exploding Wall",
+ "NW Kill Enemy for Chest",
+ "NE Kill Enemy for Chest",
+ "SW Kill Enemy for Chest",
+ "SE Kill Enemy for Chest",
+ "W Kill Enemy for Chest",
+ "E Kill Enemy for Chest",
+ "N Kill Enemy for Chest",
+ "S Kill Enemy for Chest",
+ "Clear Quadrant for Chest",
+ "Clear Full Tile for Chest",
+
+ "Light Torches to Open",
+ "Holes 3",
+ "Holes 4",
+ "Holes 5",
+ "Holes 6",
+ "Agahnim Room",
+ "Holes 7",
+ "Holes 8",
+ "Open Chest for Holes 8",
+ "Push Block for Chest",
+ "Clear Room for Triforce Door",
+ "Light Torches for Chest",
+ "Kill Boss Again"};
+
+static const absl::string_view SecretItemNames[] = {
+ "Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
+ "Bomb", "Heart ", "Blue Rupee",
+
+ "Key", "Arrow", "Bomb", "Heart", "Magic",
+ "Full Magic", "Cucco", "Green Soldier", "Bush Stal", "Blue Soldier",
+
+ "Landmine", "Heart", "Fairy", "Heart",
+ "Nothing ", // 22
+
+ "Hole", "Warp", "Staircase", "Bombable", "Switch"};
+
+static const absl::string_view Type1RoomObjectNames[] = {
+ "Ceiling ↔",
+ "Wall (top, north) ↔",
+ "Wall (top, south) ↔",
+ "Wall (bottom, north) ↔",
+ "Wall (bottom, south) ↔",
+ "Wall columns (north) ↔",
+ "Wall columns (south) ↔",
+ "Deep wall (north) ↔",
+ "Deep wall (south) ↔",
+ "Diagonal wall A ◤ (top) ↔",
+ "Diagonal wall A ◣ (top) ↔",
+ "Diagonal wall A ◥ (top) ↔",
+ "Diagonal wall A ◢ (top) ↔",
+ "Diagonal wall B ◤ (top) ↔",
+ "Diagonal wall B ◣ (top) ↔",
+ "Diagonal wall B ◥ (top) ↔",
+ "Diagonal wall B ◢ (top) ↔",
+ "Diagonal wall C ◤ (top) ↔",
+ "Diagonal wall C ◣ (top) ↔",
+ "Diagonal wall C ◥ (top) ↔",
+ "Diagonal wall C ◢ (top) ↔",
+ "Diagonal wall A ◤ (bottom) ↔",
+ "Diagonal wall A ◣ (bottom) ↔",
+ "Diagonal wall A ◥ (bottom) ↔",
+ "Diagonal wall A ◢ (bottom) ↔",
+ "Diagonal wall B ◤ (bottom) ↔",
+ "Diagonal wall B ◣ (bottom) ↔",
+ "Diagonal wall B ◥ (bottom) ↔",
+ "Diagonal wall B ◢ (bottom) ↔",
+ "Diagonal wall C ◤ (bottom) ↔",
+ "Diagonal wall C ◣ (bottom) ↔",
+ "Diagonal wall C ◥ (bottom) ↔",
+ "Diagonal wall C ◢ (bottom) ↔",
+ "Platform stairs ↔",
+ "Rail ↔",
+ "Pit edge ┏━┓ A (north) ↔",
+ "Pit edge ┏━┓ B (north) ↔",
+ "Pit edge ┏━┓ C (north) ↔",
+ "Pit edge ┏━┓ D (north) ↔",
+ "Pit edge ┏━┓ E (north) ↔",
+ "Pit edge ┗━┛ (south) ↔",
+ "Pit edge ━━━ (south) ↔",
+ "Pit edge ━━━ (north) ↔",
+ "Pit edge ━━┛ (south) ↔",
+ "Pit edge ┗━━ (south) ↔",
+ "Pit edge ━━┓ (north) ↔",
+ "Pit edge ┏━━ (north) ↔",
+ "Rail wall (north) ↔",
+ "Rail wall (south) ↔",
+ "Nothing",
+ "Nothing",
+ "Carpet ↔",
+ "Carpet trim ↔",
+ "Weird door", // TODO: WEIRD DOOR OBJECT NEEDS INVESTIGATION
+ "Drapes (north) ↔",
+ "Drapes (west, odd) ↔",
+ "Statues ↔",
+ "Columns ↔",
+ "Wall decors (north) ↔",
+ "Wall decors (south) ↔",
+ "Chairs in pairs ↔",
+ "Tall torches ↔",
+ "Supports (north) ↔",
+ "Water edge ┏━┓ (concave) ↔",
+ "Water edge ┗━┛ (concave) ↔",
+ "Water edge ┏━┓ (convex) ↔",
+ "Water edge ┗━┛ (convex) ↔",
+ "Water edge ┏━┛ (concave) ↔",
+ "Water edge ┗━┓ (concave) ↔",
+ "Water edge ┗━┓ (convex) ↔",
+ "Water edge ┏━┛ (convex) ↔",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Supports (south) ↔",
+ "Bar ↔",
+ "Shelf A ↔",
+ "Shelf B ↔",
+ "Shelf C ↔",
+ "Somaria path ↔",
+ "Cannon hole A (north) ↔",
+ "Cannon hole A (south) ↔",
+ "Pipe path ↔",
+ "Nothing",
+ "Wall torches (north) ↔",
+ "Wall torches (south) ↔",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Cannon hole B (north) ↔",
+ "Cannon hole B (south) ↔",
+ "Thick rail ↔",
+ "Blocks ↔",
+ "Long rail ↔",
+ "Ceiling ↕",
+ "Wall (top, west) ↕",
+ "Wall (top, east) ↕",
+ "Wall (bottom, west) ↕",
+ "Wall (bottom, east) ↕",
+ "Wall columns (west) ↕",
+ "Wall columns (east) ↕",
+ "Deep wall (west) ↕",
+ "Deep wall (east) ↕",
+ "Rail ↕",
+ "Pit edge (west) ↕",
+ "Pit edge (east) ↕",
+ "Rail wall (west) ↕",
+ "Rail wall (east) ↕",
+ "Nothing",
+ "Nothing",
+ "Carpet ↕",
+ "Carpet trim ↕",
+ "Nothing",
+ "Drapes (west) ↕",
+ "Drapes (east) ↕",
+ "Columns ↕",
+ "Wall decors (west) ↕",
+ "Wall decors (east) ↕",
+ "Supports (west) ↕",
+ "Water edge (west) ↕",
+ "Water edge (east) ↕",
+ "Supports (east) ↕",
+ "Somaria path ↕",
+ "Pipe path ↕",
+ "Nothing",
+ "Wall torches (west) ↕",
+ "Wall torches (east) ↕",
+ "Wall decors tight A (west) ↕",
+ "Wall decors tight A (east) ↕",
+ "Wall decors tight B (west) ↕",
+ "Wall decors tight B (east) ↕",
+ "Cannon hole (west) ↕",
+ "Cannon hole (east) ↕",
+ "Tall torches ↕",
+ "Thick rail ↕",
+ "Blocks ↕",
+ "Long rail ↕",
+ "Jump ledge (west) ↕",
+ "Jump ledge (east) ↕",
+ "Rug trim (west) ↕",
+ "Rug trim (east) ↕",
+ "Bar ↕",
+ "Wall flair (west) ↕",
+ "Wall flair (east) ↕",
+ "Blue pegs ↕",
+ "Orange pegs ↕",
+ "Invisible floor ↕",
+ "Fake pots ↕",
+ "Hammer pegs ↕",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Diagonal ceiling A ◤",
+ "Diagonal ceiling A ◣",
+ "Diagonal ceiling A ◥",
+ "Diagonal ceiling A ◢",
+ "Pit ⇲",
+ "Diagonal layer 2 mask A ◤",
+ "Diagonal layer 2 mask A ◣",
+ "Diagonal layer 2 mask A ◥",
+ "Diagonal layer 2 mask A ◢",
+ "Diagonal layer 2 mask B ◤", // TODO: VERIFY
+ "Diagonal layer 2 mask B ◣", // TODO: VERIFY
+ "Diagonal layer 2 mask B ◥", // TODO: VERIFY
+ "Diagonal layer 2 mask B ◢", // TODO: VERIFY
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Jump ledge (north) ↔",
+ "Jump ledge (south) ↔",
+ "Rug ↔",
+ "Rug trim (north) ↔",
+ "Rug trim (south) ↔",
+ "Archery game curtains ↔",
+ "Wall flair (north) ↔",
+ "Wall flair (south) ↔",
+ "Blue pegs ↔",
+ "Orange pegs ↔",
+ "Invisible floor ↔",
+ "Fake pressure plates ↔",
+ "Fake pots ↔",
+ "Hammer pegs ↔",
+ "Nothing",
+ "Nothing",
+ "Ceiling (large) ⇲",
+ "Chest platform (tall) ⇲",
+ "Layer 2 pit mask (large) ⇲",
+ "Layer 2 pit mask (medium) ⇲",
+ "Floor 1 ⇲",
+ "Floor 3 ⇲",
+ "Layer 2 mask (large) ⇲",
+ "Floor 4 ⇲",
+ "Water floor ⇲ ",
+ "Flood water (medium) ⇲ ",
+ "Conveyor floor ⇲ ",
+ "Nothing",
+ "Nothing",
+ "Moving wall (west) ⇲",
+ "Moving wall (east) ⇲",
+ "Nothing",
+ "Nothing",
+ "Icy floor A ⇲",
+ "Icy floor B ⇲",
+ "Moving wall flag", // TODO: WTF IS THIS?
+ "Moving wall flag", // TODO: WTF IS THIS?
+ "Moving wall flag", // TODO: WTF IS THIS?
+ "Moving wall flag", // TODO: WTF IS THIS?
+ "Layer 2 mask (medium) ⇲",
+ "Flood water (large) ⇲",
+ "Layer 2 swim mask ⇲",
+ "Flood water B (large) ⇲",
+ "Floor 2 ⇲",
+ "Chest platform (short) ⇲",
+ "Table / rock ⇲",
+ "Spike blocks ⇲",
+ "Spiked floor ⇲",
+ "Floor 7 ⇲",
+ "Tiled floor ⇲",
+ "Rupee floor ⇲",
+ "Conveyor upwards ⇲",
+ "Conveyor downwards ⇲",
+ "Conveyor leftwards ⇲",
+ "Conveyor rightwards ⇲",
+ "Heavy current water ⇲",
+ "Floor 10 ⇲",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+ "Nothing",
+};
+
+static const absl::string_view Type2RoomObjectNames[] = {
+ "Corner (top, concave) ▛",
+ "Corner (top, concave) ▙",
+ "Corner (top, concave) ▜",
+ "Corner (top, concave) ▟",
+ "Corner (top, convex) ▟",
+ "Corner (top, convex) ▜",
+ "Corner (top, convex) ▙",
+ "Corner (top, convex) ▛",
+ "Corner (bottom, concave) ▛",
+ "Corner (bottom, concave) ▙",
+ "Corner (bottom, concave) ▜",
+ "Corner (bottom, concave) ▟",
+ "Corner (bottom, convex) ▟",
+ "Corner (bottom, convex) ▜",
+ "Corner (bottom, convex) ▙",
+ "Corner (bottom, convex) ▛",
+ "Kinked corner north (bottom) ▜",
+ "Kinked corner south (bottom) ▟",
+ "Kinked corner north (bottom) ▛",
+ "Kinked corner south (bottom) ▙",
+ "Kinked corner west (bottom) ▙",
+ "Kinked corner west (bottom) ▛",
+ "Kinked corner east (bottom) ▟",
+ "Kinked corner east (bottom) ▜",
+ "Deep corner (concave) ▛",
+ "Deep corner (concave) ▙",
+ "Deep corner (concave) ▜",
+ "Deep corner (concave) ▟",
+ "Large brazier",
+ "Statue",
+ "Star tile (disabled)",
+ "Star tile (enabled)",
+ "Small torch (lit)",
+ "Barrel",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Table",
+ "Fairy statue",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Chair",
+ "Bed",
+ "Fireplace",
+ "Mario portrait",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Interroom stairs (up)",
+ "Interroom stairs (down)",
+ "Interroom stairs B (down)",
+ "Intraroom stairs north B", // TODO: VERIFY LAYER HANDLING
+ "Intraroom stairs north (separate layers)",
+ "Intraroom stairs north (merged layers)",
+ "Intraroom stairs north (swim layer)",
+ "Block",
+ "Water ladder (north)",
+ "Water ladder (south)", // TODO: NEEDS IN GAME VERIFICATION
+ "Dam floodgate",
+ "Interroom spiral stairs up (top)",
+ "Interroom spiral stairs down (top)",
+ "Interroom spiral stairs up (bottom)",
+ "Interroom spiral stairs down (bottom)",
+ "Sanctuary wall (north)",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Pew",
+ "Magic bat altar",
+};
+
+static const absl::string_view Type3RoomObjectNames[] = {
+ "Waterfall face (empty)",
+ "Waterfall face (short)",
+ "Waterfall face (long)",
+ "Somaria path endpoint",
+ "Somaria path intersection ╋",
+ "Somaria path corner ┏",
+ "Somaria path corner ┗",
+ "Somaria path corner ┓",
+ "Somaria path corner ┛",
+ "Somaria path intersection ┳",
+ "Somaria path intersection ┻",
+ "Somaria path intersection ┣",
+ "Somaria path intersection ┫",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Somaria path 2-way endpoint",
+ "Somaria path crossover",
+ "Babasu hole (north)",
+ "Babasu hole (south)",
+ "9 blue rupees",
+ "Telepathy tile",
+ "Warp door", // TODO: NEEDS IN GAME VERIFICATION THAT THIS IS USELESS
+ "Kholdstare's shell",
+ "Hammer peg",
+ "Prison cell",
+ "Big key lock",
+ "Chest",
+ "Chest (open)",
+ "Intraroom stairs south", // TODO: VERIFY LAYER HANDLING
+ "Intraroom stairs south (separate layers)",
+ "Intraroom stairs south (merged layers)",
+ "Interroom straight stairs up (north, top)",
+ "Interroom straight stairs down (north, top)",
+ "Interroom straight stairs up (south, top)",
+ "Interroom straight stairs down (south, top)",
+ "Deep corner (convex) ▟",
+ "Deep corner (convex) ▜",
+ "Deep corner (convex) ▙",
+ "Deep corner (convex) ▛",
+ "Interroom straight stairs up (north, bottom)",
+ "Interroom straight stairs down (north, bottom)",
+ "Interroom straight stairs up (south, bottom)",
+ "Interroom straight stairs down (south, bottom)",
+ "Lamp cones",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Liftable large block",
+ "Agahnim's altar",
+ "Agahnim's boss room",
+ "Pot",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Big chest",
+ "Big chest (open)",
+ "Intraroom stairs south (swim layer)",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Pipe end (south)",
+ "Pipe end (north)",
+ "Pipe end (east)",
+ "Pipe end (west)",
+ "Pipe corner ▛",
+ "Pipe corner ▙",
+ "Pipe corner ▜",
+ "Pipe corner ▟",
+ "Pipe-rock intersection ⯊",
+ "Pipe-rock intersection ⯋",
+ "Pipe-rock intersection ◖",
+ "Pipe-rock intersection ◗",
+ "Pipe crossover",
+ "Bombable floor",
+ "Fake bombable floor",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Warp tile",
+ "Tool rack",
+ "Furnace",
+ "Tub (wide)",
+ "Anvil",
+ "Warp tile (disabled)",
+ "Pressure plate",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Blue peg",
+ "Orange peg",
+ "Fortune teller room",
+ "Unknown", // TODO: NEEDS IN GAME CHECKING
+ "Bar corner ▛",
+ "Bar corner ▙",
+ "Bar corner ▜",
+ "Bar corner ▟",
+ "Decorative bowl",
+ "Tub (tall)",
+ "Bookcase",
+ "Range",
+ "Suitcase",
+ "Bar bottles",
+ "Arrow game hole (west)",
+ "Arrow game hole (east)",
+ "Vitreous goo gfx",
+ "Fake pressure plate",
+ "Medusa head",
+ "4-way shooter block",
+ "Pit",
+ "Wall crack (north)",
+ "Wall crack (south)",
+ "Wall crack (west)",
+ "Wall crack (east)",
+ "Large decor",
+ "Water grate (north)",
+ "Water grate (south)",
+ "Water grate (west)",
+ "Water grate (east)",
+ "Window sunlight",
+ "Floor sunlight",
+ "Trinexx's shell",
+ "Layer 2 mask (full)",
+ "Boss entrance",
+ "Minigame chest",
+ "Ganon door",
+ "Triforce wall ornament",
+ "Triforce floor tiles",
+ "Freezor hole",
+ "Pile of bones",
+ "Vitreous goo damage",
+ "Arrow tile ↑",
+ "Arrow tile ↓",
+ "Arrow tile →",
+ "Nothing",
+};
+
+static const absl::string_view TileTypeNames[] = {
+ "$00 Nothing (standard floor)",
+ "$01 Collision",
+ "$02 Collision",
+ "$03 Collision",
+ "$04 Collision",
+ "$05 Nothing (unused?)",
+ "$06 Nothing (unused?)",
+ "$07 Nothing (unused?)",
+ "$08 Deep water",
+ "$09 Shallow water",
+ "$0A Unknown? Possibly unused",
+ "$0B Collision (different in Overworld and unknown)",
+ "$0C Overlay mask",
+ "$0D Spike floor",
+ "$0E GT ice",
+ "$0F Ice palace ice",
+ "$10 Slope ◤",
+ "$11 Slope ◥",
+ "$12 Slope ◣",
+ "$13 Slope ◢",
+ "$14 Nothing (unused?)",
+ "$15 Nothing (unused?)",
+ "$16 Nothing (unused?)",
+ "$17 Nothing (unused?)",
+ "$18 Slope ◤",
+ "$19 Slope ◥",
+ "$1A Slope ◣",
+ "$1B Slope ◢",
+ "$1C Layer 2 overlay",
+ "$1D North single-layer auto stairs",
+ "$1E North layer-swap auto stairs",
+ "$1F North layer-swap auto stairs",
+ "$20 Pit",
+ "$21 Nothing (unused?)",
+ "$22 Manual stairs",
+ "$23 Pot switch",
+ "$24 Pressure switch",
+ "$25 Nothing (unused but referenced by somaria blocks)",
+ "$26 Collision (near stairs?)",
+ "$27 Brazier/Fence/Statue/Block/General hookable things",
+ "$28 North ledge",
+ "$29 South ledge",
+ "$2A East ledge",
+ "$2B West ledge",
+ "$2C ◤ ledge",
+ "$2D ◣ ledge",
+ "$2E ◥ ledge",
+ "$2F ◢ ledge",
+ "$30 Straight inter-room stairs south/up 0",
+ "$31 Straight inter-room stairs south/up 1",
+ "$32 Straight inter-room stairs south/up 2",
+ "$33 Straight inter-room stairs south/up 3",
+ "$34 Straight inter-room stairs north/down 0",
+ "$35 Straight inter-room stairs north/down 1",
+ "$36 Straight inter-room stairs north/down 2",
+ "$37 Straight inter-room stairs north/down 3",
+ "$38 Straight inter-room stairs north/down edge",
+ "$39 Straight inter-room stairs south/up edge",
+ "$3A Star tile (inactive on load)",
+ "$3B Star tile (active on load)",
+ "$3C Nothing (unused?)",
+ "$3D South single-layer auto stairs",
+ "$3E South layer-swap auto stairs",
+ "$3F South layer-swap auto stairs",
+ "$40 Thick grass",
+ "$41 Nothing (unused?)",
+ "$42 Gravestone / Tower of hera ledge shadows??",
+ "$43 Skull Woods entrance/Hera columns???",
+ "$44 Spike",
+ "$45 Nothing (unused?)",
+ "$46 Desert Tablet",
+ "$47 Nothing (unused?)",
+ "$48 Diggable ground",
+ "$49 Nothing (unused?)",
+ "$4A Diggable ground",
+ "$4B Warp tile",
+ "$4C Nothing (unused?) | Something unknown in overworld",
+ "$4D Nothing (unused?) | Something unknown in overworld",
+ "$4E Square corners in EP overworld",
+ "$4F Square corners in EP overworld",
+ "$50 Green bush",
+ "$51 Dark bush",
+ "$52 Gray rock",
+ "$53 Black rock",
+ "$54 Hint tile/Sign",
+ "$55 Big gray rock",
+ "$56 Big black rock",
+ "$57 Bonk rocks",
+ "$58 Chest 0",
+ "$59 Chest 1",
+ "$5A Chest 2",
+ "$5B Chest 3",
+ "$5C Chest 4",
+ "$5D Chest 5",
+ "$5E Spiral stairs",
+ "$5F Spiral stairs",
+ "$60 Rupee tile",
+ "$61 Nothing (unused?)",
+ "$62 Bombable floor",
+ "$63 Minigame chest",
+ "$64 Nothing (unused?)",
+ "$65 Nothing (unused?)",
+ "$66 Crystal peg down",
+ "$67 Crystal peg up",
+ "$68 Upwards conveyor",
+ "$69 Downwards conveyor",
+ "$6A Leftwards conveyor",
+ "$6B Rightwards conveyor",
+ "$6C North vines",
+ "$6D South vines",
+ "$6E West vines",
+ "$6F East vines",
+ "$70 Pot/Hammer peg/Push block 00",
+ "$71 Pot/Hammer peg/Push block 01",
+ "$72 Pot/Hammer peg/Push block 02",
+ "$73 Pot/Hammer peg/Push block 03",
+ "$74 Pot/Hammer peg/Push block 04",
+ "$75 Pot/Hammer peg/Push block 05",
+ "$76 Pot/Hammer peg/Push block 06",
+ "$77 Pot/Hammer peg/Push block 07",
+ "$78 Pot/Hammer peg/Push block 08",
+ "$79 Pot/Hammer peg/Push block 09",
+ "$7A Pot/Hammer peg/Push block 0A",
+ "$7B Pot/Hammer peg/Push block 0B",
+ "$7C Pot/Hammer peg/Push block 0C",
+ "$7D Pot/Hammer peg/Push block 0D",
+ "$7E Pot/Hammer peg/Push block 0E",
+ "$7F Pot/Hammer peg/Push block 0F",
+ "$80 North/South door",
+ "$81 East/West door",
+ "$82 North/South shutter door",
+ "$83 East/West shutter door",
+ "$84 North/South layer 2 door",
+ "$85 East/West layer 2 door",
+ "$86 North/South layer 2 shutter door",
+ "$87 East/West layer 2 shutter door",
+ "$88 Some type of door (?)",
+ "$89 East/West transport door",
+ "$8A Some type of door (?)",
+ "$8B Some type of door (?)",
+ "$8C Some type of door (?)",
+ "$8D Some type of door (?)",
+ "$8E Entrance door",
+ "$8F Entrance door",
+ "$90 Layer toggle shutter door (?)",
+ "$91 Layer toggle shutter door (?)",
+ "$92 Layer toggle shutter door (?)",
+ "$93 Layer toggle shutter door (?)",
+ "$94 Layer toggle shutter door (?)",
+ "$95 Layer toggle shutter door (?)",
+ "$96 Layer toggle shutter door (?)",
+ "$97 Layer toggle shutter door (?)",
+ "$98 Layer+Dungeon toggle shutter door (?)",
+ "$99 Layer+Dungeon toggle shutter door (?)",
+ "$9A Layer+Dungeon toggle shutter door (?)",
+ "$9B Layer+Dungeon toggle shutter door (?)",
+ "$9C Layer+Dungeon toggle shutter door (?)",
+ "$9D Layer+Dungeon toggle shutter door (?)",
+ "$9E Layer+Dungeon toggle shutter door (?)",
+ "$9F Layer+Dungeon toggle shutter door (?)",
+ "$A0 North/South Dungeon swap door",
+ "$A1 Dungeon toggle door (?)",
+ "$A2 Dungeon toggle door (?)",
+ "$A3 Dungeon toggle door (?)",
+ "$A4 Dungeon toggle door (?)",
+ "$A5 Dungeon toggle door (?)",
+ "$A6 Nothing (unused?)",
+ "$A7 Nothing (unused?)",
+ "$A8 Layer+Dungeon toggle shutter door (?)",
+ "$A9 Layer+Dungeon toggle shutter door (?)",
+ "$AA Layer+Dungeon toggle shutter door (?)",
+ "$AB Layer+Dungeon toggle shutter door (?)",
+ "$AC Layer+Dungeon toggle shutter door (?)",
+ "$AD Layer+Dungeon toggle shutter door (?)",
+ "$AE Layer+Dungeon toggle shutter door (?)",
+ "$AF Layer+Dungeon toggle shutter door (?)",
+ "$B0 Somaria ─",
+ "$B1 Somaria │",
+ "$B2 Somaria ┌",
+ "$B3 Somaria └",
+ "$B4 Somaria ┐",
+ "$B5 Somaria ┘",
+ "$B6 Somaria ⍰ 1 way",
+ "$B7 Somaria ┬",
+ "$B8 Somaria ┴",
+ "$B9 Somaria ├",
+ "$BA Somaria ┤",
+ "$BB Somaria ┼",
+ "$BC Somaria ⍰ 2 way",
+ "$BD Somaria ┼ crossover",
+ "$BE Pipe entrance",
+ "$BF Nothing (unused?)",
+ "$C0 Torch 00",
+ "$C1 Torch 01",
+ "$C2 Torch 02",
+ "$C3 Torch 03",
+ "$C4 Torch 04",
+ "$C5 Torch 05",
+ "$C6 Torch 06",
+ "$C7 Torch 07",
+ "$C8 Torch 08",
+ "$C9 Torch 09",
+ "$CA Torch 0A",
+ "$CB Torch 0B",
+ "$CC Torch 0C",
+ "$CD Torch 0D",
+ "$CE Torch 0E",
+ "$CF Torch 0F",
+ "$D0 Nothing (unused?)",
+ "$D1 Nothing (unused?)",
+ "$D2 Nothing (unused?)",
+ "$D3 Nothing (unused?)",
+ "$D4 Nothing (unused?)",
+ "$D5 Nothing (unused?)",
+ "$D6 Nothing (unused?)",
+ "$D7 Nothing (unused?)",
+ "$D8 Nothing (unused?)",
+ "$D9 Nothing (unused?)",
+ "$DA Nothing (unused?)",
+ "$DB Nothing (unused?)",
+ "$DC Nothing (unused?)",
+ "$DD Nothing (unused?)",
+ "$DE Nothing (unused?)",
+ "$DF Nothing (unused?)",
+ "$E0 Nothing (unused?)",
+ "$E1 Nothing (unused?)",
+ "$E2 Nothing (unused?)",
+ "$E3 Nothing (unused?)",
+ "$E4 Nothing (unused?)",
+ "$E5 Nothing (unused?)",
+ "$E6 Nothing (unused?)",
+ "$E7 Nothing (unused?)",
+ "$E8 Nothing (unused?)",
+ "$E9 Nothing (unused?)",
+ "$EA Nothing (unused?)",
+ "$EB Nothing (unused?)",
+ "$EC Nothing (unused?)",
+ "$ED Nothing (unused?)",
+ "$EE Nothing (unused?)",
+ "$EF Nothing (unused?)",
+ "$F0 Door 0 bottom",
+ "$F1 Door 1 bottom",
+ "$F2 Door 2 bottom",
+ "$F3 Door 3 bottom",
+ "$F4 Door X bottom? (unused?)",
+ "$F5 Door X bottom? (unused?)",
+ "$F6 Door X bottom? (unused?)",
+ "$F7 Door X bottom? (unused?)",
+ "$F8 Door 0 top",
+ "$F9 Door 1 top",
+ "$FA Door 2 top",
+ "$FB Door 3 top",
+ "$FC Door X top? (unused?)",
+ "$FD Door X top? (unused?)",
+ "$FE Door X top? (unused?)",
+ "$FF Door X top? (unused?)"};
+
+static const absl::string_view kSpriteDefaultNames[]{
+ "00 Raven",
+ "01 Vulture",
+ "02 Flying Stalfos Head",
+ "03 No Pointer (Empty",
+ "04 Pull Switch (good",
+ "05 Pull Switch (unused",
+ "06 Pull Switch (bad",
+ "07 Pull Switch (unused",
+ "08 Octorock (one way",
+ "09 Moldorm (Boss",
+ "0A Octorock (four way",
+ "0B Chicken",
+ "0C Octorock (?",
+ "0D Buzzblock",
+ "0E Snapdragon",
+ "0F Octoballoon",
+ "10 Octoballon Hatchlings",
+ "11 Hinox",
+ "12 Moblin",
+ "13 Mini Helmasaure",
+ "14 Gargoyle's Domain Gate",
+ "15 Antifairy",
+ "16 Sahasrahla / Aginah",
+ "17 Bush Hoarder",
+ "18 Mini Moldorm",
+ "19 Poe",
+ "1A Dwarves",
+ "1B Arrow in wall",
+ "1C Statue",
+ "1D Weathervane",
+ "1E Crystal Switch",
+ "1F Bug-Catching Kid",
+ "20 Sluggula",
+ "21 Push Switch",
+ "22 Ropa",
+ "23 Red Bari",
+ "24 Blue Bari",
+ "25 Talking Tree",
+ "26 Hardhat Beetle",
+ "27 Deadrock",
+ "28 Storytellers",
+ "29 Blind Hideout attendant",
+ "2A Sweeping Lady",
+ "2B Storytellers",
+ "2C Lumberjacks",
+ "2D Telepathic Stones",
+ "2E Multipurpose Sprite",
+ "2F Race Npc",
+ "30 Person?",
+ "31 Fortune Teller",
+ "32 Angry Brothers",
+ "33 Pull for items",
+ "34 Scared Girl",
+ "35 Innkeeper",
+ "36 Witch",
+ "37 Waterfall",
+ "38 Arrow Target",
+ "39 Average Middle",
+ "3A Half Magic Bat",
+ "3B Dash Item",
+ "3C Village Kid",
+ "3D Signs? Chicken lady also showed up / Scared ladies outside houses.",
+ "3E Rock Hoarder",
+ "3F Tutorial Soldier",
+ "40 Lightning Lock",
+ "41 Blue Sword Soldier / Used by guards to detect player",
+ "42 Green Sword Soldier",
+ "43 Red Spear Soldier",
+ "44 Assault Sword Soldier",
+ "45 Green Spear Soldier",
+ "46 Blue Archer",
+ "47 Green Archer",
+ "48 Red Javelin Soldier",
+ "49 Red Javelin Soldier 2",
+ "4A Red Bomb Soldiers",
+ "4B Green Soldier Recruits",
+ "4C Geldman",
+ "4D Rabbit",
+ "4E Popo",
+ "4F Popo 2",
+ "50 Cannon Balls",
+ "51 Armos",
+ "52 Giant Zora",
+ "53 Armos Knights (Boss",
+ "54 Lanmolas (Boss",
+ "55 Fireball Zora",
+ "56 Walking Zora",
+ "57 Desert Palace Barriers",
+ "58 Crab",
+ "59 Bird",
+ "5A Squirrel",
+ "5B Spark (Left to Right",
+ "5C Spark (Right to Left",
+ "5D Roller (vertical moving",
+ "5E Roller (vertical moving",
+ "5F Roller",
+ "60 Roller (horizontal moving",
+ "61 Beamos",
+ "62 Master Sword",
+ "63 Devalant (Non",
+ "64 Devalant (Shooter",
+ "65 Shooting Gallery Proprietor",
+ "66 Moving Cannon Ball Shooters (Right",
+ "67 Moving Cannon Ball Shooters (Left",
+ "68 Moving Cannon Ball Shooters (Down",
+ "69 Moving Cannon Ball Shooters (Up",
+ "6A Ball N' Chain Trooper",
+ "6B Cannon Soldier",
+ "6C Mirror Portal",
+ "6D Rat",
+ "6E Rope",
+ "6F Keese",
+ "70 Helmasaur King Fireball",
+ "71 Leever",
+ "72 Activator for the ponds (where you throw in items",
+ "73 Uncle / Priest",
+ "74 Running Man",
+ "75 Bottle Salesman",
+ "76 Princess Zelda",
+ "77 Antifairy (Alternate",
+ "78 Village Elder",
+ "79 Bee",
+ "7A Agahnim",
+ "7B Agahnim Energy Ball",
+ "7C Hyu",
+ "7D Big Spike Trap",
+ "7E Guruguru Bar (Clockwise",
+ "7F Guruguru Bar (Counter Clockwise",
+ "80 Winder",
+ "81 Water Tektite",
+ "82 Antifairy Circle",
+ "83 Green Eyegore",
+ "84 Red Eyegore",
+ "85 Yellow Stalfos",
+ "86 Kodongos",
+ "87 Flames",
+ "88 Mothula (Boss",
+ "89 Mothula's Beam",
+ "8A Spike Trap",
+ "8B Gibdo",
+ "8C Arrghus (Boss",
+ "8D Arrghus spawn",
+ "8E Terrorpin",
+ "8F Slime",
+ "90 Wallmaster",
+ "91 Stalfos Knight",
+ "92 Helmasaur King",
+ "93 Bumper",
+ "94 Swimmers",
+ "95 Eye Laser (Right",
+ "96 Eye Laser (Left",
+ "97 Eye Laser (Down",
+ "98 Eye Laser (Up",
+ "99 Pengator",
+ "9A Kyameron",
+ "9B Wizzrobe",
+ "9C Tadpoles",
+ "9D Tadpoles",
+ "9E Ostrich (Haunted Grove",
+ "9F Flute",
+ "A0 Birds (Haunted Grove",
+ "A1 Freezor",
+ "A2 Kholdstare (Boss",
+ "A3 Kholdstare's Shell",
+ "A4 Falling Ice",
+ "A5 Zazak Fireball",
+ "A6 Red Zazak",
+ "A7 Stalfos",
+ "A8 Bomber Flying Creatures from Darkworld",
+ "A9 Bomber Flying Creatures from Darkworld",
+ "AA Pikit",
+ "AB Maiden",
+ "AC Apple",
+ "AD Lost Old Man",
+ "AE Down Pipe",
+ "AF Up Pipe",
+ "B0 Right Pip",
+ "B1 Left Pipe",
+ "B2 Good bee again?",
+ "B3 Hylian Inscription",
+ "B4 Thief?s chest (not the one that follows you",
+ "B5 Bomb Salesman",
+ "B6 Kiki",
+ "B7 Maiden following you in Blind Dungeon",
+ "B8 Monologue Testing Sprite",
+ "B9 Feuding Friends on Death Mountain",
+ "BA Whirlpool",
+ "BB Salesman / chestgame guy / 300 rupee giver guy / Chest game thief",
+ "BC Drunk in the inn",
+ "BD Vitreous (Large Eyeball",
+ "BE Vitreous (Small Eyeball",
+ "BF Vitreous' Lightning",
+ "C0 Monster in Lake of Ill Omen / Quake Medallion",
+ "C1 Agahnim teleporting Zelda to dark world",
+ "C2 Boulders",
+ "C3 Gibo",
+ "C4 Thief",
+ "C5 Medusa",
+ "C6 Four Way Fireball Spitters (spit when you use your sword",
+ "C7 Hokku",
+ "C8 Big Fairy who heals you",
+ "C9 Tektite",
+ "CA Chain Chomp",
+ "CB Trinexx",
+ "CC Another part of trinexx",
+ "CD Yet another part of trinexx",
+ "CE Blind The Thief (Boss)",
+ "CF Swamola",
+ "D0 Lynel",
+ "D1 Bunny Beam",
+ "D2 Flopping fish",
+ "D3 Stal",
+ "D4 Landmine",
+ "D5 Digging Game Proprietor",
+ "D6 Ganon",
+ "D7 Copy of Ganon",
+ "D8 Heart",
+ "D9 Green Rupee",
+ "DA Blue Rupee",
+ "DB Red Rupee",
+ "DC Bomb Refill (1)",
+ "DD Bomb Refill (4)",
+ "DE Bomb Refill (8)",
+ "DF Small Magic Refill",
+ "E0 Full Magic Refill",
+ "E1 Arrow Refill (5)",
+ "E2 Arrow Refill (10)",
+ "E3 Fairy",
+ "E4 Key",
+ "E5 Big Key",
+ "E6 Shield",
+ "E7 Mushroom",
+ "E8 Fake Master Sword",
+ "E9 Magic Shop dude / His items",
+ "EA Heart Container",
+ "EB Heart Piece",
+ "EC Bushes",
+ "ED Cane Of Somaria Platform",
+ "EE Mantle",
+ "EF Cane of Somaria Platform (Unused)",
+ "F0 Cane of Somaria Platform (Unused)",
+ "F1 Cane of Somaria Platform (Unused)",
+ "F2 Medallion Tablet",
+ "F3",
+ "F4 Falling Rocks",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "FA",
+ "FB",
+ "FC",
+ "FD",
+ "FE",
+ "FF",
+};
+
+static const absl::string_view overlordnames[] = {
+ "Overlord_SpritePositionTarget",
+ "Overlord_AllDirectionMetalBallFactory",
+ "Overlord_CascadeMetalBallFactory",
+ "Overlord_StalfosFactory",
+ "Overlord_StalfosTrap",
+ "Overlord_SnakeTrap",
+ "Overlord_MovingFloor",
+ "Overlord_ZolFactory",
+ "Overlord_WallMasterFactory",
+ "Overlord_CrumbleTilePath 1",
+ "Overlord_CrumbleTilePath 2",
+ "Overlord_CrumbleTilePath 3",
+ "Overlord_CrumbleTilePath 4",
+ "Overlord_CrumbleTilePath 5",
+ "Overlord_CrumbleTilePath 6",
+ "Overlord_PirogusuFactory 1",
+ "Overlord_PirogusuFactory 2",
+ "Overlord_PirogusuFactory 3",
+ "Overlord_PirogusuFactory 4",
+ "Overlord_FlyingTileFactory",
+ "Overlord_WizzrobeFactory",
+ "Overlord_ZoroFactory",
+ "Overlord_StalfosTrapTriggerWindow",
+ "Overlord_RedStalfosTrap",
+ "Overlord_ArmosCoordinator",
+ "Overlord_BombTrap",
+};
+
+} // namespace core
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/core/controller.cc b/src/app/core/controller.cc
new file mode 100644
index 00000000..65b66100
--- /dev/null
+++ b/src/app/core/controller.cc
@@ -0,0 +1,229 @@
+#include "controller.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "absl/status/status.h"
+#include "absl/strings/str_format.h"
+#include "app/editor/master_editor.h"
+#include "gui/icons.h"
+#include "gui/style.h"
+
+namespace yaze {
+namespace app {
+namespace core {
+
+namespace {
+
+void InitializeKeymap() {
+ ImGuiIO &io = ImGui::GetIO();
+ io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
+ io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
+ io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
+ io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
+ io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN);
+ io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
+ io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
+}
+
+void HandleKeyDown(SDL_Event &event) {
+ ImGuiIO &io = ImGui::GetIO();
+ switch (event.key.keysym.sym) {
+ case SDLK_UP:
+ case SDLK_DOWN:
+ case SDLK_RETURN:
+ case SDLK_BACKSPACE:
+ case SDLK_LSHIFT:
+ case SDLK_LCTRL:
+ case SDLK_TAB:
+ io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
+ break;
+ default:
+ break;
+ }
+}
+
+void HandleKeyUp(SDL_Event &event) {
+ ImGuiIO &io = ImGui::GetIO();
+ int key = event.key.keysym.scancode;
+ IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
+ io.KeysDown[key] = (event.type == SDL_KEYDOWN);
+ io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
+ io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
+ io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
+ io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
+}
+
+void ChangeWindowSizeEvent(SDL_Event &event) {
+ ImGuiIO &io = ImGui::GetIO();
+ io.DisplaySize.x = static_cast(event.window.data1);
+ io.DisplaySize.y = static_cast(event.window.data2);
+}
+
+void HandleMouseMovement(int &wheel) {
+ ImGuiIO &io = ImGui::GetIO();
+ int mouseX;
+ int mouseY;
+ const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
+
+ io.DeltaTime = 1.0f / 60.0f;
+ io.MousePos = ImVec2(static_cast(mouseX), static_cast(mouseY));
+ io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
+ io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
+ io.MouseWheel = static_cast(wheel);
+}
+
+} // namespace
+
+bool Controller::isActive() const { return active_; }
+
+absl::Status Controller::onEntry() {
+ RETURN_IF_ERROR(CreateWindow())
+ RETURN_IF_ERROR(CreateRenderer())
+ RETURN_IF_ERROR(CreateGuiContext())
+ InitializeKeymap();
+ master_editor_.SetupScreen(renderer_);
+ active_ = true;
+ return absl::OkStatus();
+}
+
+void Controller::onInput() {
+ int wheel = 0;
+ SDL_Event event;
+ ImGuiIO &io = ImGui::GetIO();
+
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_KEYDOWN:
+ HandleKeyDown(event);
+ break;
+ case SDL_KEYUP:
+ HandleKeyUp(event);
+ break;
+ case SDL_TEXTINPUT:
+ io.AddInputCharactersUTF8(event.text.text);
+ break;
+ case SDL_MOUSEWHEEL:
+ wheel = event.wheel.y;
+ break;
+ case SDL_WINDOWEVENT:
+ switch (event.window.event) {
+ case SDL_WINDOWEVENT_CLOSE:
+ CloseWindow();
+ break;
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ ChangeWindowSizeEvent(event);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ HandleMouseMovement(wheel);
+}
+
+void Controller::onLoad() { master_editor_.UpdateScreen(); }
+
+void Controller::doRender() const {
+ SDL_RenderClear(renderer_.get());
+ ImGui::Render();
+ ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
+ SDL_RenderPresent(renderer_.get());
+}
+
+void Controller::onExit() const {
+ ImGui_ImplSDLRenderer_Shutdown();
+ ImGui_ImplSDL2_Shutdown();
+ ImGui::DestroyContext();
+ SDL_Quit();
+}
+
+absl::Status Controller::CreateWindow() {
+ if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
+ return absl::InternalError(
+ absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
+ } else {
+ window_ = std::unique_ptr(
+ SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
+ SDL_WINDOWPOS_UNDEFINED, // initial x position
+ SDL_WINDOWPOS_UNDEFINED, // initial y position
+ kScreenWidth, // width, in pixels
+ kScreenHeight, // height, in pixels
+ SDL_WINDOW_RESIZABLE),
+ sdl_deleter());
+ if (window_ == nullptr) {
+ return absl::InternalError(
+ absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
+ }
+ // Initialize SDL_mixer
+ if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
+ printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
+ Mix_GetError());
+ }
+ }
+ return absl::OkStatus();
+}
+
+absl::Status Controller::CreateRenderer() {
+ renderer_ = std::unique_ptr(
+ SDL_CreateRenderer(window_.get(), -1,
+ SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
+ sdl_deleter());
+ if (renderer_ == nullptr) {
+ return absl::InternalError(
+ absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
+ } else {
+ SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
+ SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
+ }
+ return absl::OkStatus();
+}
+
+absl::Status Controller::CreateGuiContext() {
+ ImGui::CreateContext();
+
+ // Initialize ImGui for SDL
+ ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
+ ImGui_ImplSDLRenderer_Init(renderer_.get());
+
+ // Load available fonts
+ const ImGuiIO &io = ImGui::GetIO();
+ io.Fonts->AddFontFromFileTTF("assets/font/Karla-Regular.ttf", 14.0f);
+
+ // merge in icons from Google Material Design
+ static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
+ ImFontConfig icons_config;
+ icons_config.MergeMode = true;
+ icons_config.GlyphOffset.y = 5.0f;
+ icons_config.GlyphMinAdvanceX = 13.0f;
+ icons_config.PixelSnapH = true;
+ io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, 18.0f, &icons_config,
+ icons_ranges);
+ io.Fonts->AddFontFromFileTTF("assets/font/Roboto-Medium.ttf", 14.0f);
+ io.Fonts->AddFontFromFileTTF("assets/font/Cousine-Regular.ttf", 14.0f);
+ io.Fonts->AddFontFromFileTTF("assets/font/DroidSans.ttf", 16.0f);
+
+ // Set the default style
+ gui::ColorsYaze();
+
+ // Build a new ImGui frame
+ ImGui_ImplSDLRenderer_NewFrame();
+ ImGui_ImplSDL2_NewFrame(window_.get());
+
+ return absl::OkStatus();
+}
+
+} // namespace core
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/core/controller.h b/src/app/core/controller.h
new file mode 100644
index 00000000..ba5af3e4
--- /dev/null
+++ b/src/app/core/controller.h
@@ -0,0 +1,57 @@
+#ifndef YAZE_APP_CORE_CONTROLLER_H
+#define YAZE_APP_CORE_CONTROLLER_H
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "absl/status/status.h"
+#include "app/editor/master_editor.h"
+#include "gui/icons.h"
+#include "gui/style.h"
+
+int main(int argc, char **argv);
+
+namespace yaze {
+namespace app {
+namespace core {
+
+class Controller {
+ public:
+ bool isActive() const;
+ absl::Status onEntry();
+ void onInput();
+ void onLoad();
+ void onLoadDelta();
+ void doRender() const;
+ void onExit() const;
+
+ private:
+ struct sdl_deleter {
+ void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
+ void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
+ void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
+ };
+
+ absl::Status CreateWindow();
+ absl::Status CreateRenderer();
+ absl::Status CreateGuiContext();
+ void CloseWindow() { active_ = false; }
+
+ friend int ::main(int argc, char **argv);
+
+ bool active_;
+ editor::MasterEditor master_editor_;
+ std::shared_ptr window_;
+ std::shared_ptr renderer_;
+};
+
+} // namespace core
+} // namespace app
+} // namespace yaze
+
+#endif // YAZE_APP_CORE_CONTROLLER_H
\ No newline at end of file
diff --git a/src/app/delta/CMakeLists.txt b/src/app/delta/CMakeLists.txt
new file mode 100644
index 00000000..087dd9f7
--- /dev/null
+++ b/src/app/delta/CMakeLists.txt
@@ -0,0 +1,32 @@
+
+add_library(delta-service delta.proto)
+target_link_libraries(delta-service
+ PUBLIC
+ protobuf::libprotobuf
+ gRPC::grpc
+ gRPC::grpc++
+)
+
+target_include_directories(delta-service
+ PUBLIC
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${PROTOBUF_INCLUDE_PATH}
+)
+
+get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
+
+# compile the message types
+protobuf_generate(TARGET delta-service LANGUAGE cpp)
+
+# compile the GRPC services
+protobuf_generate(
+ TARGET
+ delta-service
+ LANGUAGE
+ grpc
+ GENERATE_EXTENSIONS
+ .grpc.pb.h
+ .grpc.pb.cc
+ PLUGIN
+ "protoc-gen-grpc=${grpc_cpp_plugin_location}"
+)
\ No newline at end of file
diff --git a/src/app/delta/client.cc b/src/app/delta/client.cc
new file mode 100644
index 00000000..4a24a78d
--- /dev/null
+++ b/src/app/delta/client.cc
@@ -0,0 +1,54 @@
+#include "client.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/status.h"
+#include "src/app/delta/delta.grpc.pb.h"
+#include "src/app/delta/delta.pb.h"
+
+namespace yaze {
+namespace app {
+namespace delta {
+
+using grpc::Channel;
+using grpc::ClientAsyncResponseReader;
+using grpc::ClientContext;
+using grpc::CompletionQueue;
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::Status;
+
+void Client::CreateChannel() {
+ auto channel = grpc::CreateChannel("localhost:50051",
+ grpc::InsecureChannelCredentials());
+ stub_ = ::YazeDelta::NewStub(channel);
+}
+
+absl::Status Client::InitRepo(std::string author_name,
+ std::string project_name) {
+ Repository new_repo;
+ new_repo.set_author_name(author_name);
+ new_repo.set_project_name(project_name);
+ new_repo.mutable_tree()->Add();
+
+ InitRequest request;
+ request.set_allocated_repo(&new_repo);
+
+ InitResponse response;
+ Status status = stub_->Init(&rpc_context, request, &response);
+
+ if (!status.ok()) {
+ std::cerr << status.error_code() << ": " << status.error_message()
+ << std::endl;
+ return absl::InternalError(status.error_message());
+ }
+ return absl::OkStatus();
+}
+
+} // namespace delta
+} // namespace app
+} // namespace yaze
diff --git a/src/app/delta/client.h b/src/app/delta/client.h
new file mode 100644
index 00000000..6c3d7f74
--- /dev/null
+++ b/src/app/delta/client.h
@@ -0,0 +1,44 @@
+#ifndef YAZE_APP_DELTA_CLIENT_H
+#define YAZE_APP_DELTA_CLIENT_H
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "absl/status/status.h"
+#include "src/app/delta/delta.grpc.pb.h"
+#include "src/app/delta/delta.pb.h"
+
+namespace yaze {
+namespace app {
+namespace delta {
+
+using grpc::Channel;
+using grpc::ClientAsyncResponseReader;
+using grpc::ClientContext;
+using grpc::CompletionQueue;
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::Status;
+
+class Client {
+ public:
+ Client() = default;
+ void CreateChannel();
+ absl::Status InitRepo(std::string author_name, std::string project_name);
+
+ private:
+ ClientContext rpc_context;
+ std::vector repos_;
+ std::unique_ptr stub_;
+};
+
+} // namespace delta
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/delta/delta.cc b/src/app/delta/delta.cc
new file mode 100644
index 00000000..4a413d67
--- /dev/null
+++ b/src/app/delta/delta.cc
@@ -0,0 +1,32 @@
+#if defined(_WIN32)
+#define main SDL_main
+#endif
+
+#include "absl/debugging/failure_signal_handler.h"
+#include "absl/debugging/symbolize.h"
+#include "app/core/controller.h"
+#include "app/delta/viewer.h"
+
+int main(int argc, char** argv) {
+ absl::InitializeSymbolizer(argv[0]);
+
+ absl::FailureSignalHandlerOptions options;
+ absl::InstallFailureSignalHandler(options);
+
+ yaze::app::core::Controller controller;
+ yaze::app::delta::Viewer viewer;
+
+ auto entry_status = controller.onEntry();
+ if (!entry_status.ok()) {
+ return EXIT_FAILURE;
+ }
+
+ while (controller.isActive()) {
+ controller.onInput();
+ viewer.Update();
+ controller.doRender();
+ }
+ controller.onExit();
+
+ return EXIT_SUCCESS;
+}
\ No newline at end of file
diff --git a/src/app/delta/delta.proto b/src/app/delta/delta.proto
new file mode 100644
index 00000000..c15090b3
--- /dev/null
+++ b/src/app/delta/delta.proto
@@ -0,0 +1,105 @@
+syntax = "proto3";
+option cc_enable_arenas = true;
+
+service YazeDelta {
+ rpc Init(InitRequest) returns (InitResponse) {}
+
+ rpc Clone(CloneRequest) returns (CloneResponse) {}
+
+ rpc Push(PushRequest) returns (PushResponse) {}
+ rpc Pull(PullRequest) returns (PullResponse) {}
+
+ rpc Sync(stream SyncRequest) returns (stream SyncResponse) {}
+
+ rpc CreateBranch(CreateBranchRequest) returns (CreateBranchResponse) {}
+ rpc DeleteBranch(DeleteBranchRequest) returns (DeleteBranchResponse) {}
+
+ rpc Merge(MergeRequest) returns (MergeResponse) {}
+ rpc UndoMerge(UndoMergeRequest) returns (UndoMergeResponse) {}
+}
+
+enum ChangeType {
+ OVERWORLD_MAP = 0;
+ DUNGEON_MAP = 1;
+ MONOLOGUE = 2;
+ PALETTE = 3;
+ OBJECT = 4;
+ ASSEMBLY = 5;
+ MISC = 6;
+}
+
+message Delta {
+ int64 offset = 1;
+ int64 length = 2;
+ bytes data = 3;
+ ChangeType type = 4;
+}
+
+message Commit {
+ int64 commit_id = 1;
+ int64 parent_commit_id = 2;
+ string author_name = 3;
+ string message_header = 4;
+ optional string message_body = 5;
+ repeated Delta delta = 6;
+ int64 signature = 7;
+}
+
+message Branch {
+ string branch_name = 1;
+ optional string parent_name = 2;
+ repeated Commit commits = 3;
+}
+
+message Repository {
+ string project_name = 1;
+ string author_name = 2;
+ int64 signature = 3;
+ optional bool locked = 4;
+ optional string password = 5;
+ repeated Branch tree = 6;
+}
+
+message InitRequest {
+ Repository repo = 1;
+}
+
+message InitResponse {
+ int32 response = 1;
+}
+
+message CloneRequest {}
+
+message CloneResponse {}
+
+message PushRequest {
+ string author_name = 1;
+ string repository_name= 2;
+ string branch_name = 3;
+ repeated Commit commits = 4;
+}
+message PushResponse {}
+
+message PullRequest {
+ string repository_name = 1;
+ string branch_name = 2;
+ repeated Commit commits = 3;
+}
+
+message PullResponse {}
+
+message SyncRequest {}
+
+message SyncResponse {}
+
+message CreateBranchRequest {}
+message CreateBranchResponse {}
+
+message DeleteBranchRequest {}
+message DeleteBranchResponse {}
+
+message MergeRequest {}
+message MergeResponse {}
+
+message UndoMergeRequest {}
+message UndoMergeResponse {}
\ No newline at end of file
diff --git a/src/app/delta/service.cc b/src/app/delta/service.cc
new file mode 100644
index 00000000..cdfb5504
--- /dev/null
+++ b/src/app/delta/service.cc
@@ -0,0 +1,87 @@
+#include "service.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "src/app/delta/delta.grpc.pb.h"
+#include "src/app/delta/delta.pb.h"
+
+namespace yaze {
+namespace app {
+namespace delta {
+
+using grpc::Channel;
+using grpc::ClientAsyncResponseReader;
+using grpc::ClientContext;
+using grpc::CompletionQueue;
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::Status;
+
+namespace {
+auto FindRepository(std::vector& repos, const std::string& name) {
+ for (auto& repo : repos) {
+ if (repo.project_name() == name) {
+ return repo.mutable_tree();
+ }
+ }
+}
+
+auto FindBranch(google::protobuf::RepeatedPtrField* repo,
+ const std::string& branch_name) {
+ for (auto it = repo->begin(); it != repo->end(); ++it) {
+ if (it->branch_name() == branch_name) {
+ return it->mutable_commits();
+ }
+ }
+}
+} // namespace
+
+Status DeltaService::Init(grpc::ServerContext* context,
+ const InitRequest* request, InitResponse* reply) {
+ std::filesystem::create_directories("./.yaze");
+ repos_.push_back(request->repo());
+ // std::ofstream commit_stream("./.yaze/commits");
+ // for (const auto& repo : repos_) {
+ // for (const auto& branch : repo.tree()) {
+ // for (const auto& commit : branch.commits()) {
+ // commit_stream << commit.DebugString();
+ // }
+ // }
+ // }
+ return Status::OK;
+}
+
+Status DeltaService::Push(grpc::ServerContext* context,
+ const PushRequest* request, PushResponse* reply) {
+ const auto& repository_name = request->repository_name();
+ const auto& branch_name = request->branch_name();
+ auto repo = FindRepository(repos_, repository_name);
+ auto mutable_commits = FindBranch(repo, branch_name);
+ auto size = request->commits().size();
+ for (int i = 1; i < size; ++i) {
+ *mutable_commits->Add() = request->commits().at(i);
+ }
+ return Status::OK;
+}
+
+Status DeltaService::Pull(grpc::ServerContext* context,
+ const PullRequest* request, PullResponse* reply) {
+ return Status::OK;
+}
+
+Status DeltaService::Clone(grpc::ServerContext* context,
+ const CloneRequest* request, CloneResponse* reply) {
+ return Status::OK;
+}
+
+} // namespace delta
+} // namespace app
+} // namespace yaze
diff --git a/src/app/delta/service.h b/src/app/delta/service.h
new file mode 100644
index 00000000..d986def7
--- /dev/null
+++ b/src/app/delta/service.h
@@ -0,0 +1,48 @@
+#ifndef YAZE_APP_DELTA_SERVICE_H
+#define YAZE_APP_DELTA_SERVICE_H
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "absl/status/status.h"
+#include "src/app/delta/delta.grpc.pb.h"
+#include "src/app/delta/delta.pb.h"
+
+namespace yaze {
+namespace app {
+namespace delta {
+
+using grpc::Status;
+
+class DeltaService final : public ::YazeDelta::Service {
+ public:
+ Status Init(grpc::ServerContext* context, const InitRequest* request,
+ InitResponse* reply) override;
+
+ Status Push(grpc::ServerContext* context, const PushRequest* request,
+ PushResponse* reply) override;
+
+ Status Pull(grpc::ServerContext* context, const PullRequest* request,
+ PullResponse* reply) override;
+
+ Status Clone(grpc::ServerContext* context, const CloneRequest* request,
+ CloneResponse* reply) override;
+
+ auto Repos() const { return repos_; }
+
+ private:
+ std::vector repos_;
+};
+
+} // namespace delta
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/delta/viewer.cc b/src/app/delta/viewer.cc
new file mode 100644
index 00000000..c3848ebe
--- /dev/null
+++ b/src/app/delta/viewer.cc
@@ -0,0 +1,229 @@
+#include "viewer.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/status.h"
+#include "app/core/constants.h"
+#include "app/gfx/snes_palette.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+#include "gui/input.h"
+
+namespace yaze {
+namespace app {
+namespace delta {
+namespace {
+
+constexpr ImGuiWindowFlags kMainEditorFlags =
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
+ ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
+ ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
+
+void NewMasterFrame() {
+ const ImGuiIO& io = ImGui::GetIO();
+ ImGui::NewFrame();
+ ImGui::SetNextWindowPos(ImVec2(0, 0));
+ ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
+ ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
+
+ if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
+ ImGui::End();
+ return;
+ }
+}
+
+} // namespace
+
+void Viewer::Update() {
+ NewMasterFrame();
+ DrawYazeMenu();
+ DrawFileDialog();
+
+ ImGui::Text(ICON_MD_CHANGE_HISTORY);
+ ImGui::SameLine();
+ ImGui::Text("%s", rom_.GetTitle());
+
+ ImGui::Separator();
+
+ ImGui::Button(ICON_MD_SYNC);
+ ImGui::SameLine();
+ ImGui::Button(ICON_MD_ARROW_UPWARD);
+ ImGui::SameLine();
+ ImGui::Button(ICON_MD_ARROW_DOWNWARD);
+ ImGui::SameLine();
+ ImGui::Button(ICON_MD_MERGE);
+ ImGui::SameLine();
+
+ ImGui::Button(ICON_MD_MANAGE_HISTORY);
+ ImGui::SameLine();
+ ImGui::Button(ICON_MD_LAN);
+ ImGui::SameLine();
+ ImGui::Button(ICON_MD_COMMIT);
+ ImGui::SameLine();
+ ImGui::Button(ICON_MD_DIFFERENCE);
+
+ ImGui::Separator();
+
+ ImGui::SetNextItemWidth(75.f);
+ ImGui::Button(ICON_MD_SEND);
+ ImGui::SameLine();
+ ImGui::InputText("Server Address", &client_address_);
+
+ ImGui::SetNextItemWidth(75.f);
+ ImGui::Button(ICON_MD_DOWNLOAD);
+ ImGui::SameLine();
+ ImGui::InputText("Repository Source", &client_address_);
+
+ ImGui::Separator();
+ DrawBranchTree();
+ ImGui::End();
+}
+
+void Viewer::DrawFileDialog() {
+ if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
+ if (ImGuiFileDialog::Instance()->IsOk()) {
+ std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
+ rom_.LoadFromFile(filePathName);
+ }
+ ImGuiFileDialog::Instance()->Close();
+ }
+}
+
+void Viewer::DrawYazeMenu() {
+ MENU_BAR()
+ DrawFileMenu();
+ DrawViewMenu();
+ END_MENU_BAR()
+}
+
+void Viewer::DrawFileMenu() const {
+ if (ImGui::BeginMenu("File")) {
+ if (ImGui::MenuItem("Open", "Ctrl+O")) {
+ ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
+ ".sfc,.smc", ".");
+ }
+
+ MENU_ITEM2("Save", "Ctrl+S") {}
+
+ ImGui::EndMenu();
+ }
+}
+
+void Viewer::DrawViewMenu() {
+ static bool show_imgui_metrics = false;
+ static bool show_imgui_style_editor = false;
+ static bool show_memory_editor = false;
+ static bool show_imgui_demo = false;
+
+ if (show_imgui_metrics) {
+ ImGui::ShowMetricsWindow(&show_imgui_metrics);
+ }
+
+ if (show_memory_editor) {
+ static MemoryEditor mem_edit;
+ mem_edit.DrawWindow("Memory Editor", (void*)&rom_, rom_.size());
+ }
+
+ if (show_imgui_demo) {
+ ImGui::ShowDemoWindow();
+ }
+
+ if (show_imgui_style_editor) {
+ ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
+ ImGui::ShowStyleEditor();
+ ImGui::End();
+ }
+
+ if (ImGui::BeginMenu("View")) {
+ ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
+ ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
+ ImGui::Separator();
+ if (ImGui::BeginMenu("GUI Tools")) {
+ ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
+ ImGui::MenuItem("Style Editor (ImGui)", nullptr,
+ &show_imgui_style_editor);
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenu();
+ }
+}
+
+void Viewer::DrawBranchTree() {
+ static ImGuiTableFlags flags =
+ ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH |
+ ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg |
+ ImGuiTableFlags_NoBordersInBody;
+
+ if (ImGui::BeginTable("3ways", 3, flags)) {
+ // The first column will use the default _WidthStretch when ScrollX is Off
+ // and _WidthFixed when ScrollX is On
+ ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
+ ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed,
+ 10 * 12.0f);
+ ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
+ 10 * 18.0f);
+ ImGui::TableHeadersRow();
+
+ // Simple storage to output a dummy file-system.
+ struct MyTreeNode {
+ const char* Name;
+ const char* Type;
+ int Size;
+ int ChildIdx;
+ int ChildCount;
+ static void DisplayNode(const MyTreeNode* node,
+ const MyTreeNode* all_nodes) {
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ const bool is_folder = (node->ChildCount > 0);
+ if (is_folder) {
+ bool open =
+ ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth);
+ ImGui::TableNextColumn();
+ ImGui::TextDisabled("--");
+ ImGui::TableNextColumn();
+ ImGui::TextUnformatted(node->Type);
+ if (open) {
+ for (int child_n = 0; child_n < node->ChildCount; child_n++)
+ DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);
+ ImGui::TreePop();
+ }
+ } else {
+ ImGui::TreeNodeEx(
+ node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet |
+ ImGuiTreeNodeFlags_NoTreePushOnOpen |
+ ImGuiTreeNodeFlags_SpanFullWidth);
+ ImGui::TableNextColumn();
+ ImGui::Text("%d", node->Size);
+ ImGui::TableNextColumn();
+ ImGui::TextUnformatted(node->Type);
+ }
+ }
+ };
+ static const MyTreeNode nodes[] = {
+ {"lttp-redux", "Repository", -1, 1, 3},
+ {"main", "Branch", -1, 4, 2},
+ {"hyrule-castle", "Branch", -1, 4, 2},
+ {"lost-woods", "Branch", -1, 6, 3},
+ {"Added some bushes", "Commit", 1024, -1, -1},
+ {"Constructed a new house", "Commit", 123000, -1, -1},
+ {"File1_b.wav", "Commit", 456000, -1, -1},
+ {"Image001.png", "Commit", 203128, -1, -1},
+ {"Copy of Image001.png", "Commit", 203256, -1, -1},
+ {"Copy of Image001 (Final2).png", "Commit", 203512, -1, -1},
+ };
+
+ MyTreeNode::DisplayNode(&nodes[0], nodes);
+
+ ImGui::EndTable();
+ }
+}
+} // namespace delta
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/delta/viewer.h b/src/app/delta/viewer.h
new file mode 100644
index 00000000..edcc8d48
--- /dev/null
+++ b/src/app/delta/viewer.h
@@ -0,0 +1,45 @@
+#ifndef YAZE_APP_DELTA_VIEWER_H
+#define YAZE_APP_DELTA_VIEWER_H
+
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/status.h"
+#include "app/core/constants.h"
+#include "app/delta/client.h"
+#include "app/gfx/snes_palette.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+#include "gui/input.h"
+
+namespace yaze {
+namespace app {
+namespace delta {
+class Viewer {
+ public:
+ void Update();
+
+ private:
+ void DrawFileDialog();
+
+ void DrawYazeMenu();
+ void DrawFileMenu() const;
+ void DrawViewMenu();
+
+ void DrawBranchTree();
+
+ std::string client_address_;
+
+ ROM rom_;
+ Client client_;
+};
+} // namespace delta
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/editor/assembly_editor.cc b/src/app/editor/assembly_editor.cc
new file mode 100644
index 00000000..aacdcaaa
--- /dev/null
+++ b/src/app/editor/assembly_editor.cc
@@ -0,0 +1,115 @@
+#include "assembly_editor.h"
+
+#include "core/constants.h"
+#include "gui/widgets.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+AssemblyEditor::AssemblyEditor() {
+ text_editor_.SetLanguageDefinition(gui::widgets::GetAssemblyLanguageDef());
+ text_editor_.SetPalette(TextEditor::GetDarkPalette());
+}
+
+void AssemblyEditor::Update() {
+ ImGui::Begin("Assembly Editor", &file_is_loaded_);
+ MENU_BAR()
+ DrawFileMenu();
+ DrawEditMenu();
+ END_MENU_BAR()
+
+ auto cpos = text_editor_.GetCursorPosition();
+ SetEditorText();
+ ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
+ cpos.mColumn + 1, text_editor_.GetTotalLines(),
+ text_editor_.IsOverwrite() ? "Ovr" : "Ins",
+ text_editor_.CanUndo() ? "*" : " ",
+ text_editor_.GetLanguageDefinition().mName.c_str(),
+ current_file_.c_str());
+
+ text_editor_.Render("##asm_editor");
+ ImGui::End();
+}
+
+
+
+void AssemblyEditor::InlineUpdate() {
+ ChangeActiveFile("assets/asm/template_song.asm");
+ auto cpos = text_editor_.GetCursorPosition();
+ SetEditorText();
+ ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
+ cpos.mColumn + 1, text_editor_.GetTotalLines(),
+ text_editor_.IsOverwrite() ? "Ovr" : "Ins",
+ text_editor_.CanUndo() ? "*" : " ",
+ text_editor_.GetLanguageDefinition().mName.c_str(),
+ current_file_.c_str());
+
+ text_editor_.Render("##asm_editor", ImVec2(0, 0));
+}
+
+void AssemblyEditor::ChangeActiveFile(const std::string& filename) {
+ current_file_ = filename;
+}
+
+void AssemblyEditor::DrawFileMenu() {
+ if (ImGui::BeginMenu("File")) {
+ if (ImGui::MenuItem("Open", "Ctrl+O")) {
+ ImGuiFileDialog::Instance()->OpenDialog(
+ "ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
+ }
+ if (ImGui::MenuItem("Save", "Ctrl+S")) {
+ // TODO: Implement this
+ }
+ ImGui::EndMenu();
+ }
+
+ if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
+ if (ImGuiFileDialog::Instance()->IsOk()) {
+ ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
+ }
+ ImGuiFileDialog::Instance()->Close();
+ }
+}
+
+void AssemblyEditor::DrawEditMenu() {
+ if (ImGui::BeginMenu("Edit")) {
+ if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
+ text_editor_.Undo();
+ }
+ if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
+ text_editor_.Redo();
+ }
+ ImGui::Separator();
+ if (ImGui::MenuItem("Cut", "Ctrl+X")) {
+ text_editor_.Cut();
+ }
+ if (ImGui::MenuItem("Copy", "Ctrl+C")) {
+ text_editor_.Copy();
+ }
+ if (ImGui::MenuItem("Paste", "Ctrl+V")) {
+ text_editor_.Paste();
+ }
+ ImGui::Separator();
+ if (ImGui::MenuItem("Find", "Ctrl+F")) {
+ // TODO: Implement this.
+ }
+ ImGui::EndMenu();
+ }
+}
+
+void AssemblyEditor::SetEditorText() {
+ if (!file_is_loaded_) {
+ std::ifstream t(current_file_);
+ if (t.good()) {
+ std::string str((std::istreambuf_iterator(t)),
+ std::istreambuf_iterator());
+ text_editor_.SetText(str);
+ file_is_loaded_ = true;
+ }
+ }
+}
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/editor/assembly_editor.h b/src/app/editor/assembly_editor.h
new file mode 100644
index 00000000..63195aa8
--- /dev/null
+++ b/src/app/editor/assembly_editor.h
@@ -0,0 +1,38 @@
+#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
+#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
+
+#include
+#include
+
+#include
+#include
+#include
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+class AssemblyEditor {
+ public:
+ AssemblyEditor();
+
+ void Update();
+ void InlineUpdate();
+ void ChangeActiveFile(const std::string &);
+
+ private:
+ void DrawFileMenu();
+ void DrawEditMenu();
+ void SetEditorText();
+
+ bool file_is_loaded_ = false;
+
+ std::string current_file_;
+ TextEditor text_editor_;
+};
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/editor/dungeon_editor.cc b/src/app/editor/dungeon_editor.cc
new file mode 100644
index 00000000..151c2736
--- /dev/null
+++ b/src/app/editor/dungeon_editor.cc
@@ -0,0 +1,61 @@
+#include "dungeon_editor.h"
+
+#include "gui/icons.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+void DungeonEditor::Update() {
+ DrawToolset();
+ canvas_.DrawBackground();
+ canvas_.DrawContextMenu();
+ canvas_.DrawGrid();
+ canvas_.DrawOverlay();
+}
+
+void DungeonEditor::DrawToolset() {
+ if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) {
+ ImGui::TableSetupColumn("#undoTool");
+ ImGui::TableSetupColumn("#redoTool");
+ ImGui::TableSetupColumn("#history");
+ ImGui::TableSetupColumn("#separator");
+ ImGui::TableSetupColumn("#bg1Tool");
+ ImGui::TableSetupColumn("#bg2Tool");
+ ImGui::TableSetupColumn("#bg3Tool");
+ ImGui::TableSetupColumn("#itemTool");
+ ImGui::TableSetupColumn("#spriteTool");
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_UNDO);
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_REDO);
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_MANAGE_HISTORY);
+
+ ImGui::TableNextColumn();
+ ImGui::Text(ICON_MD_MORE_VERT);
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_FILTER_1);
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_FILTER_2);
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_FILTER_3);
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_GRASS);
+
+ ImGui::TableNextColumn();
+ ImGui::Button(ICON_MD_PEST_CONTROL_RODENT);
+ ImGui::EndTable();
+ }
+}
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/editor/dungeon_editor.h b/src/app/editor/dungeon_editor.h
new file mode 100644
index 00000000..5c000d2d
--- /dev/null
+++ b/src/app/editor/dungeon_editor.h
@@ -0,0 +1,26 @@
+#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
+#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
+
+#include
+
+#include "gui/canvas.h"
+#include "gui/icons.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+class DungeonEditor {
+ public:
+ void Update();
+
+ private:
+ void DrawToolset();
+
+ gui::Canvas canvas_;
+ ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
+};
+} // namespace editor
+} // namespace app
+} // namespace yaze
+
+#endif
diff --git a/src/app/editor/master_editor.cc b/src/app/editor/master_editor.cc
new file mode 100644
index 00000000..16ae7243
--- /dev/null
+++ b/src/app/editor/master_editor.cc
@@ -0,0 +1,322 @@
+#include "master_editor.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/status.h"
+#include "app/core/constants.h"
+#include "app/editor/assembly_editor.h"
+#include "app/editor/dungeon_editor.h"
+#include "app/editor/music_editor.h"
+#include "app/editor/overworld_editor.h"
+#include "app/gfx/snes_palette.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+#include "gui/input.h"
+#include "gui/widgets.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+namespace {
+
+constexpr ImGuiWindowFlags kMainEditorFlags =
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
+ ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
+ ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
+
+void NewMasterFrame() {
+ const ImGuiIO &io = ImGui::GetIO();
+ ImGui::NewFrame();
+ ImGui::SetNextWindowPos(ImVec2(0, 0));
+ ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
+ ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
+
+ if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
+ ImGui::End();
+ return;
+ }
+}
+
+bool BeginCentered(const char *name) {
+ ImGuiIO const &io = ImGui::GetIO();
+ ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
+ ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
+ ImGuiWindowFlags flags =
+ ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
+ ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
+ return ImGui::Begin(name, nullptr, flags);
+}
+
+void DisplayStatus(absl::Status &status) {
+ if (BeginCentered("StatusWindow")) {
+ ImGui::Text("%s", status.ToString().c_str());
+ ImGui::Spacing();
+ ImGui::NextColumn();
+ ImGui::Columns(1);
+ ImGui::Separator();
+ ImGui::NewLine();
+ ImGui::SameLine(270);
+ if (ImGui::Button("OK", ImVec2(200, 0))) {
+ status = absl::OkStatus();
+ }
+ ImGui::End();
+ }
+}
+
+} // namespace
+
+void MasterEditor::SetupScreen(std::shared_ptr renderer) {
+ sdl_renderer_ = renderer;
+ rom_.SetupRenderer(renderer);
+}
+
+void MasterEditor::UpdateScreen() {
+ NewMasterFrame();
+
+ DrawYazeMenu();
+ DrawFileDialog();
+ DrawStatusPopup();
+ DrawAboutPopup();
+ DrawInfoPopup();
+
+ TAB_BAR("##TabBar")
+ DrawOverworldEditor();
+ DrawDungeonEditor();
+ DrawMusicEditor();
+ DrawSpriteEditor();
+ DrawScreenEditor();
+ DrawPaletteEditor();
+ END_TAB_BAR()
+
+ ImGui::End();
+}
+
+void MasterEditor::DrawFileDialog() {
+ if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
+ if (ImGuiFileDialog::Instance()->IsOk()) {
+ std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
+ status_ = rom_.LoadFromFile(filePathName);
+ overworld_editor_.SetupROM(rom_);
+ screen_editor_.SetupROM(rom_);
+ palette_editor_.SetupROM(rom_);
+ }
+ ImGuiFileDialog::Instance()->Close();
+ }
+}
+
+void MasterEditor::DrawStatusPopup() {
+ if (!status_.ok()) {
+ DisplayStatus(status_);
+ }
+}
+
+void MasterEditor::DrawAboutPopup() {
+ if (about_) ImGui::OpenPopup("About");
+ if (ImGui::BeginPopupModal("About", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::Text("Yet Another Zelda3 Editor - v0.02");
+ ImGui::Text("Written by: scawful");
+ ImGui::Spacing();
+ ImGui::Text("Special Thanks: Zarby89, JaredBrian");
+ ImGui::Separator();
+
+ if (ImGui::Button("Close", ImVec2(200, 0))) {
+ about_ = false;
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+}
+
+void MasterEditor::DrawInfoPopup() {
+ if (rom_info_) ImGui::OpenPopup("ROM Information");
+ if (ImGui::BeginPopupModal("ROM Information", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::Text("Title: %s", rom_.GetTitle());
+ ImGui::Text("ROM Size: %ld", rom_.size());
+
+ if (ImGui::Button("Close", ImVec2(200, 0))) {
+ rom_info_ = false;
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+}
+
+void MasterEditor::DrawYazeMenu() {
+ MENU_BAR()
+ DrawFileMenu();
+ DrawEditMenu();
+ DrawViewMenu();
+ DrawHelpMenu();
+ END_MENU_BAR()
+}
+
+void MasterEditor::DrawFileMenu() const {
+ if (ImGui::BeginMenu("File")) {
+ if (ImGui::MenuItem("Open", "Ctrl+O")) {
+ ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
+ ".sfc,.smc", ".");
+ }
+
+ MENU_ITEM2("Save", "Ctrl+S") {}
+ MENU_ITEM("Save As..") {}
+
+ ImGui::Separator();
+
+ // TODO: Make these options matter
+ if (ImGui::BeginMenu("Options")) {
+ static bool enabled = true;
+ ImGui::MenuItem("Enabled", "", &enabled);
+ ImGui::BeginChild("child", ImVec2(0, 60), true);
+ for (int i = 0; i < 10; i++) ImGui::Text("Scrolling Text %d", i);
+ ImGui::EndChild();
+ static float f = 0.5f;
+ static int n = 0;
+ ImGui::SliderFloat("Value", &f, 0.0f, 1.0f);
+ ImGui::InputFloat("Input", &f, 0.1f);
+ ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0");
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenu();
+ }
+}
+
+void MasterEditor::DrawEditMenu() {
+ if (ImGui::BeginMenu("Edit")) {
+ MENU_ITEM2("Undo", "Ctrl+Z") { status_ = overworld_editor_.Undo(); }
+ MENU_ITEM2("Redo", "Ctrl+Y") { status_ = overworld_editor_.Redo(); }
+ ImGui::Separator();
+ MENU_ITEM2("Cut", "Ctrl+X") { status_ = overworld_editor_.Cut(); }
+ MENU_ITEM2("Copy", "Ctrl+C") { status_ = overworld_editor_.Copy(); }
+ MENU_ITEM2("Paste", "Ctrl+V") { status_ = overworld_editor_.Paste(); }
+ ImGui::Separator();
+ MENU_ITEM2("Find", "Ctrl+F") {}
+ ImGui::Separator();
+ MENU_ITEM("ROM Information") rom_info_ = true;
+ ImGui::EndMenu();
+ }
+}
+
+void MasterEditor::DrawViewMenu() {
+ static bool show_imgui_metrics = false;
+ static bool show_imgui_style_editor = false;
+ static bool show_memory_editor = false;
+ static bool show_asm_editor = false;
+ static bool show_imgui_demo = false;
+ static bool show_memory_viewer = false;
+
+ if (show_imgui_metrics) {
+ ImGui::ShowMetricsWindow(&show_imgui_metrics);
+ }
+
+ if (show_memory_editor) {
+ static MemoryEditor mem_edit;
+ mem_edit.DrawWindow("Memory Editor", (void *)&rom_, rom_.size());
+ }
+
+ if (show_imgui_demo) {
+ ImGui::ShowDemoWindow();
+ }
+
+ if (show_asm_editor) {
+ assembly_editor_.Update();
+ }
+
+ if (show_imgui_style_editor) {
+ ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
+ ImGui::ShowStyleEditor();
+ ImGui::End();
+ }
+
+ if (show_memory_viewer) {
+ ImGui::Begin("Memory Viewer (ImGui)", &show_memory_viewer);
+
+ const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
+ static ImGuiTableFlags flags =
+ ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
+ ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg |
+ ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX;
+ if (auto outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f);
+ ImGui::BeginTable("table1", 3, flags, outer_size)) {
+ for (int row = 0; row < 10; row++) {
+ ImGui::TableNextRow();
+ for (int column = 0; column < 3; column++) {
+ ImGui::TableNextColumn();
+ ImGui::Text("Cell %d,%d", column, row);
+ }
+ }
+ ImGui::EndTable();
+ }
+
+ ImGui::End();
+ }
+
+ if (ImGui::BeginMenu("View")) {
+ ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
+ ImGui::MenuItem("ASM Editor", nullptr, &show_asm_editor);
+ ImGui::MenuItem("Memory Viewer", nullptr, &show_memory_viewer);
+ ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
+ ImGui::Separator();
+ if (ImGui::BeginMenu("GUI Tools")) {
+ ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
+ ImGui::MenuItem("Style Editor (ImGui)", nullptr,
+ &show_imgui_style_editor);
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenu();
+ }
+}
+
+void MasterEditor::DrawHelpMenu() {
+ if (ImGui::BeginMenu("Help")) {
+ if (ImGui::MenuItem("About")) about_ = true;
+ ImGui::EndMenu();
+ }
+}
+
+void MasterEditor::DrawOverworldEditor() {
+ TAB_ITEM("Overworld")
+ status_ = overworld_editor_.Update();
+ END_TAB_ITEM()
+}
+
+void MasterEditor::DrawDungeonEditor() {
+ TAB_ITEM("Dungeon")
+ dungeon_editor_.Update();
+ END_TAB_ITEM()
+}
+
+void MasterEditor::DrawPaletteEditor() {
+ TAB_ITEM("Palettes")
+ status_ = palette_editor_.Update();
+ END_TAB_ITEM()
+}
+
+void MasterEditor::DrawScreenEditor() {
+ TAB_ITEM("Screens")
+ screen_editor_.Update();
+ END_TAB_ITEM()
+}
+
+void MasterEditor::DrawMusicEditor() {
+ TAB_ITEM("Music")
+ music_editor_.Update();
+ END_TAB_ITEM()
+}
+
+void MasterEditor::DrawSpriteEditor() {
+ TAB_ITEM("Sprites")
+ END_TAB_ITEM()
+}
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/editor/master_editor.h b/src/app/editor/master_editor.h
new file mode 100644
index 00000000..66712aee
--- /dev/null
+++ b/src/app/editor/master_editor.h
@@ -0,0 +1,72 @@
+#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
+#define YAZE_APP_EDITOR_MASTER_EDITOR_H
+
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/status.h"
+#include "app/core/constants.h"
+#include "app/editor/assembly_editor.h"
+#include "app/editor/dungeon_editor.h"
+#include "app/editor/music_editor.h"
+#include "app/editor/overworld_editor.h"
+#include "app/editor/palette_editor.h"
+#include "app/editor/screen_editor.h"
+#include "app/gfx/snes_palette.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+#include "gui/input.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+class MasterEditor {
+ public:
+ void SetupScreen(std::shared_ptr renderer);
+ void UpdateScreen();
+
+ private:
+ void DrawFileDialog();
+ void DrawStatusPopup();
+ void DrawAboutPopup();
+ void DrawInfoPopup();
+
+ void DrawYazeMenu();
+ void DrawFileMenu() const;
+ void DrawEditMenu();
+ void DrawViewMenu();
+ void DrawHelpMenu();
+
+ void DrawOverworldEditor();
+ void DrawDungeonEditor();
+ void DrawPaletteEditor();
+ void DrawMusicEditor();
+ void DrawScreenEditor();
+ void DrawSpriteEditor();
+
+ bool about_ = false;
+ bool rom_info_ = false;
+
+ std::shared_ptr sdl_renderer_;
+ absl::Status status_;
+
+ AssemblyEditor assembly_editor_;
+ DungeonEditor dungeon_editor_;
+ OverworldEditor overworld_editor_;
+ PaletteEditor palette_editor_;
+ ScreenEditor screen_editor_;
+ MusicEditor music_editor_;
+ ROM rom_;
+};
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
+
+#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
\ No newline at end of file
diff --git a/src/app/editor/music_editor.cc b/src/app/editor/music_editor.cc
new file mode 100644
index 00000000..a635f081
--- /dev/null
+++ b/src/app/editor/music_editor.cc
@@ -0,0 +1,292 @@
+#include "music_editor.h"
+
+#include
+#include
+
+#include "absl/strings/str_format.h"
+#include "app/editor/assembly_editor.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+#include "gui/input.h"
+#include "snes_spc/demo/demo_util.h"
+#include "snes_spc/demo/wave_writer.h"
+#include "snes_spc/snes_spc/spc.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+namespace {
+
+#define BUF_SIZE 2048
+
+void PlaySPC() {
+ /* Create emulator and filter */
+ SNES_SPC* snes_spc = spc_new();
+ SPC_Filter* filter = spc_filter_new();
+ if (!snes_spc || !filter) error("Out of memory");
+
+ /* Load SPC */
+ {
+ /* Load file into memory */
+ long spc_size;
+ void* spc = load_file("assets/music/hyrule_field.spc", &spc_size);
+
+ /* Load SPC data into emulator */
+ error(spc_load_spc(snes_spc, spc, spc_size));
+ free(spc); /* emulator makes copy of data */
+
+ /* Most SPC files have garbage data in the echo buffer, so clear that */
+ spc_clear_echo(snes_spc);
+
+ /* Clear filter before playing */
+ spc_filter_clear(filter);
+ }
+
+ /* Record 20 seconds to wave file */
+ wave_open(spc_sample_rate, "out.wav");
+ wave_enable_stereo();
+ while (wave_sample_count() < 30 * spc_sample_rate * 2) {
+ /* Play into buffer */
+ short buf[BUF_SIZE];
+ error(spc_play(snes_spc, BUF_SIZE, buf));
+
+ /* Filter samples */
+ spc_filter_run(filter, buf, BUF_SIZE);
+
+ wave_write(buf, BUF_SIZE);
+ }
+
+ /* Cleanup */
+ spc_filter_delete(filter);
+ spc_delete(snes_spc);
+ wave_close();
+}
+
+} // namespace
+
+void MusicEditor::Update() {
+ if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
+ ImVec2(0, 0))) {
+ ImGui::TableSetupColumn("Assembly");
+ ImGui::TableSetupColumn("Composition");
+ ImGui::TableHeadersRow();
+ ImGui::TableNextRow();
+
+ ImGui::TableNextColumn();
+ assembly_editor_.InlineUpdate();
+
+ ImGui::TableNextColumn();
+ DrawToolset();
+ DrawChannels();
+ DrawPianoRoll();
+
+ ImGui::EndTable();
+ }
+}
+
+void MusicEditor::DrawChannels() {
+ if (ImGui::BeginTabBar("MyTabBar", ImGuiTabBarFlags_None)) {
+ for (int i = 1; i <= 8; ++i) {
+ if (ImGui::BeginTabItem(absl::StrFormat("%d", i).data())) {
+ DrawPianoStaff();
+ ImGui::EndTabItem();
+ }
+ }
+ ImGui::EndTabBar();
+ }
+}
+
+static const int NUM_KEYS = 25;
+static bool keys[NUM_KEYS];
+
+void MusicEditor::DrawPianoStaff() {
+ if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
+ ImGui::BeginChild(child_id, ImVec2(0, 170), false)) {
+ const int NUM_LINES = 5;
+ const int LINE_THICKNESS = 2;
+ const int LINE_SPACING = 40;
+
+ // Get the draw list for the current window
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ // Draw the staff lines
+ ImVec2 canvas_p0 =
+ ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
+ ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
+ canvas_p0.y + ImGui::GetContentRegionAvail().y);
+ draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(32, 32, 32, 255));
+ for (int i = 0; i < NUM_LINES; i++) {
+ auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING);
+ auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
+ canvas_p0.y + i * LINE_SPACING);
+ draw_list->AddLine(line_start, line_end, IM_COL32(200, 200, 200, 255),
+ LINE_THICKNESS);
+ }
+
+ // Draw the ledger lines
+ const int NUM_LEDGER_LINES = 3;
+ for (int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) {
+ if (i % 2 == 0) continue; // skip every other line
+ auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2);
+ auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
+ canvas_p0.y + i * LINE_SPACING / 2);
+ draw_list->AddLine(line_start, line_end, IM_COL32(150, 150, 150, 255),
+ LINE_THICKNESS);
+ }
+ }
+ ImGui::EndChild();
+}
+
+void MusicEditor::DrawPianoRoll() {
+ // Render the piano roll
+ float key_width = ImGui::GetContentRegionAvail().x / NUM_KEYS;
+ float white_key_height = ImGui::GetContentRegionAvail().y * 0.8f;
+ float black_key_height = ImGui::GetContentRegionAvail().y * 0.5f;
+ ImGui::Text("Piano Roll");
+ ImGui::Separator();
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ // Draw the staff lines
+ ImVec2 canvas_p0 =
+ ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
+ ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
+ canvas_p0.y + ImGui::GetContentRegionAvail().y);
+ draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(200, 200, 200, 255));
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.f, 0.f));
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.f);
+ for (int i = 0; i < NUM_KEYS; i++) {
+ // Calculate the position and size of the key
+ ImVec2 key_pos = ImVec2(i * key_width, 0.0f);
+ ImVec2 key_size;
+ ImVec4 key_color;
+ ImVec4 text_color;
+ if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 ||
+ i % 12 == 10) {
+ // This is a black key
+ key_size = ImVec2(key_width * 0.6f, black_key_height);
+ key_color = ImVec4(0, 0, 0, 255);
+ text_color = ImVec4(255, 255, 255, 255);
+ } else {
+ // This is a white key
+ key_size = ImVec2(key_width, white_key_height);
+ key_color = ImVec4(255, 255, 255, 255);
+ text_color = ImVec4(0, 0, 0, 255);
+ }
+
+ ImGui::PushID(i);
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
+ ImGui::PushStyleColor(ImGuiCol_Button, key_color);
+ ImGui::PushStyleColor(ImGuiCol_Text, text_color);
+ if (ImGui::Button(kSongNotes[i].data(), key_size)) {
+ keys[i] ^= 1;
+ }
+ ImGui::PopStyleColor();
+ ImGui::PopStyleColor();
+ ImGui::PopStyleVar();
+
+ ImVec2 button_pos = ImGui::GetItemRectMin();
+ ImVec2 button_size = ImGui::GetItemRectSize();
+ if (keys[i]) {
+ ImVec2 dest;
+ dest.x = button_pos.x + button_size.x;
+ dest.y = button_pos.y + button_size.y;
+ ImGui::GetWindowDrawList()->AddRectFilled(button_pos, dest,
+ IM_COL32(200, 200, 255, 200));
+ }
+ ImGui::PopID();
+ ImGui::SameLine();
+ }
+ ImGui::PopStyleVar();
+ ImGui::PopStyleVar();
+}
+
+void MusicEditor::DrawSongToolset() {
+ if (ImGui::BeginTable("DWToolset", 8, toolset_table_flags_, ImVec2(0, 0))) {
+ ImGui::TableSetupColumn("#1");
+ ImGui::TableSetupColumn("#play");
+ ImGui::TableSetupColumn("#rewind");
+ ImGui::TableSetupColumn("#fastforward");
+ ImGui::TableSetupColumn("volumeController");
+
+ ImGui::EndTable();
+ }
+}
+
+void MusicEditor::DrawToolset() {
+ static bool is_playing = false;
+ static int selected_option = 0;
+ static int current_volume = 0;
+ static bool has_loaded_song = false;
+ const int MAX_VOLUME = 100;
+
+ if (is_playing) {
+ if (!has_loaded_song) {
+ PlaySPC();
+ current_song_ = Mix_LoadMUS("out.wav");
+ Mix_PlayMusic(current_song_, -1);
+ has_loaded_song = true;
+ }
+
+ // // If there is no music playing
+ // if (Mix_PlayingMusic() == 0) {
+ // Mix_PlayMusic(current_song_, -1);
+ // }
+ // // If music is being played
+ // else {
+ // // If the music is paused
+ // if (Mix_PausedMusic() == 1) {
+ // // Resume the music
+ // Mix_ResumeMusic();
+ // }
+ // // If the music is playing
+ // else {
+ // // Pause the music
+ // Mix_PauseMusic();
+ // }
+ // }
+ }
+
+ gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
+ ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
+
+ gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
+ if (ImGui::BeginTable("SongToolset", 5, toolset_table_flags_, ImVec2(0, 0))) {
+ ImGui::TableSetupColumn("#play");
+ ImGui::TableSetupColumn("#rewind");
+ ImGui::TableSetupColumn("#fastforward");
+ ImGui::TableSetupColumn("#volume");
+ ImGui::TableSetupColumn("#slider");
+
+ ImGui::TableNextColumn();
+ if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
+ if (is_playing) {
+ Mix_HaltMusic();
+ has_loaded_song = false;
+ }
+ is_playing = !is_playing;
+ }
+
+ BUTTON_COLUMN(ICON_MD_FAST_REWIND)
+ BUTTON_COLUMN(ICON_MD_FAST_FORWARD)
+ BUTTON_COLUMN(ICON_MD_VOLUME_UP)
+ ImGui::TableNextColumn();
+ ImGui::SliderInt("Volume", ¤t_volume, 0, 100);
+ ImGui::EndTable();
+ }
+
+ const int SONG_DURATION = 120; // duration of the song in seconds
+ static int current_time = 0; // current time in the song in seconds
+
+ // Display the current time in the song
+ gui::ItemLabel("Current Time: ", gui::ItemLabelFlags::Left);
+ ImGui::Text("%d:%02d", current_time / 60, current_time % 60);
+ ImGui::SameLine();
+ // Display the song duration/progress using a progress bar
+ ImGui::ProgressBar((float)current_time / SONG_DURATION);
+}
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
diff --git a/src/app/editor/music_editor.h b/src/app/editor/music_editor.h
new file mode 100644
index 00000000..929d36ae
--- /dev/null
+++ b/src/app/editor/music_editor.h
@@ -0,0 +1,86 @@
+#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
+#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
+
+#include
+#include
+
+#include "absl/strings/str_format.h"
+#include "app/editor/assembly_editor.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+#include "gui/input.h"
+#include "snes_spc/demo/demo_util.h"
+#include "snes_spc/demo/wave_writer.h"
+#include "snes_spc/snes_spc/spc.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+static const char* kGameSongs[] = {"Title",
+ "Light World",
+ "Beginning",
+ "Rabbit",
+ "Forest",
+ "Intro",
+ "Town",
+ "Warp",
+ "Dark world",
+ "Master sword",
+ "File select",
+ "Soldier",
+ "Mountain",
+ "Shop",
+ "Fanfare",
+ "Castle",
+ "Palace (Pendant)",
+ "Cave (Same as Secret Way)",
+ "Clear (Dungeon end)",
+ "Church",
+ "Boss",
+ "Dungeon (Crystal)",
+ "Psychic",
+ "Secret Way (Same as Cave)",
+ "Rescue",
+ "Crystal",
+ "Fountain",
+ "Pyramid",
+ "Kill Agahnim",
+ "Ganon Room",
+ "Last Boss"};
+
+static constexpr absl::string_view kSongNotes[] = {
+ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C",
+ "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
+class MusicEditor {
+ public:
+ void Update();
+
+ private:
+ void DrawChannels();
+ void DrawPianoStaff();
+ void DrawPianoRoll();
+ void DrawSongToolset();
+ void DrawToolset();
+
+ Mix_Music* current_song_ = NULL;
+
+ AssemblyEditor assembly_editor_;
+ ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
+ ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit |
+ ImGuiTableFlags_Resizable |
+ ImGuiTableFlags_Reorderable;
+
+ ImGuiTableFlags channel_table_flags_ =
+ ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
+ ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable |
+ ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg |
+ ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV |
+ ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY;
+};
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
+
+#endif
diff --git a/src/app/editor/overworld_editor.cc b/src/app/editor/overworld_editor.cc
new file mode 100644
index 00000000..f37c78f7
--- /dev/null
+++ b/src/app/editor/overworld_editor.cc
@@ -0,0 +1,352 @@
+#include "overworld_editor.h"
+
+#include
+
+#include
+#include
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_format.h"
+#include "app/editor/palette_editor.h"
+#include "app/gfx/bitmap.h"
+#include "app/gfx/snes_palette.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "app/zelda3/overworld.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+// ----------------------------------------------------------------------------
+
+absl::Status OverworldEditor::Update() {
+ // Initialize overworld graphics, maps, and palettes
+ if (rom_.isLoaded() && !all_gfx_loaded_) {
+ RETURN_IF_ERROR(LoadGraphics())
+ all_gfx_loaded_ = true;
+ }
+
+ // Draws the toolset for editing the Overworld.
+ RETURN_IF_ERROR(DrawToolset())
+
+ ImGui::Separator();
+ if (ImGui::BeginTable("#owEditTable", 2, ow_edit_flags, ImVec2(0, 0))) {
+ ImGui::TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
+ ImGui::GetContentRegionAvail().x);
+ ImGui::TableSetupColumn("Tile Selector");
+ ImGui::TableHeadersRow();
+ ImGui::TableNextRow();
+ ImGui::TableNextColumn();
+ DrawOverworldCanvas();
+ ImGui::TableNextColumn();
+ DrawTileSelector();
+ ImGui::EndTable();
+ }
+
+ return absl::OkStatus();
+}
+
+// ----------------------------------------------------------------------------
+
+absl::Status OverworldEditor::DrawToolset() {
+ if (ImGui::BeginTable("OWToolset", 17, toolset_table_flags, ImVec2(0, 0))) {
+ for (const auto &name : kToolsetColumnNames)
+ ImGui::TableSetupColumn(name.data());
+
+ BUTTON_COLUMN(ICON_MD_UNDO) // Undo
+ BUTTON_COLUMN(ICON_MD_REDO) // Redo
+ TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
+ BUTTON_COLUMN(ICON_MD_ZOOM_OUT) // Zoom Out
+ BUTTON_COLUMN(ICON_MD_ZOOM_IN) // Zoom In
+ TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
+ BUTTON_COLUMN(ICON_MD_DRAW) // Draw Tile
+ BUTTON_COLUMN(ICON_MD_DOOR_FRONT) // Entrances
+ BUTTON_COLUMN(ICON_MD_DOOR_BACK) // Exits
+ BUTTON_COLUMN(ICON_MD_GRASS) // Items
+ BUTTON_COLUMN(ICON_MD_PEST_CONTROL_RODENT) // Sprites
+ BUTTON_COLUMN(ICON_MD_ADD_LOCATION) // Transports
+ BUTTON_COLUMN(ICON_MD_MUSIC_NOTE) // Music
+ TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator
+ ImGui::TableNextColumn(); // Palette
+ palette_editor_.DisplayPalette(palette_, overworld_.isLoaded());
+
+ ImGui::EndTable();
+ }
+ return absl::OkStatus();
+}
+
+// ----------------------------------------------------------------------------
+
+void OverworldEditor::DrawOverworldMapSettings() {
+ if (ImGui::BeginTable("#mapSettings", 8, ow_map_flags, ImVec2(0, 0), -1)) {
+ for (const auto &name : kOverworldSettingsColumnNames)
+ ImGui::TableSetupColumn(name.data());
+
+ ImGui::TableNextColumn();
+ ImGui::SetNextItemWidth(50.f);
+ ImGui::InputInt("Current Map", ¤t_map_);
+
+ ImGui::TableNextColumn();
+ ImGui::SetNextItemWidth(100.f);
+ ImGui::Combo("##world", ¤t_world_,
+ "Light World\0Dark World\0Extra World\0");
+
+ ImGui::TableNextColumn();
+ ImGui::Text("GFX");
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(kInputFieldSize);
+ ImGui::InputText("##mapGFX", map_gfx_, kByteSize);
+
+ ImGui::TableNextColumn();
+ ImGui::Text("Palette");
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(kInputFieldSize);
+ ImGui::InputText("##mapPal", map_palette_, kByteSize);
+
+ ImGui::TableNextColumn();
+ ImGui::Text("Spr GFX");
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(kInputFieldSize);
+ ImGui::InputText("##sprGFX", spr_gfx_, kByteSize);
+
+ ImGui::TableNextColumn();
+ ImGui::Text("Spr Palette");
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(kInputFieldSize);
+ ImGui::InputText("##sprPal", spr_palette_, kByteSize);
+
+ ImGui::TableNextColumn();
+ ImGui::Text("Msg ID");
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(50.f);
+ ImGui::InputText("##msgid", spr_palette_, kMessageIdSize);
+
+ ImGui::TableNextColumn();
+ ImGui::Checkbox("Show grid", &opt_enable_grid); // TODO
+ ImGui::EndTable();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+void OverworldEditor::DrawOverworldEntrances() {
+ for (const auto &each : overworld_.Entrances()) {
+ if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
+ each.map_id_ >= (current_world_ * 0x40)) {
+ ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16,
+ ImVec4(210, 24, 210, 150));
+ std::string str = absl::StrFormat("%#x", each.entrance_id_);
+ ow_map_canvas_.DrawText(str, each.x_ - 4, each.y_ - 2);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+void OverworldEditor::DrawOverworldMaps() {
+ int xx = 0;
+ int yy = 0;
+ for (int i = 0; i < 0x40; i++) {
+ int world_index = i + (current_world_ * 0x40);
+ int map_x = (xx * 0x200);
+ int map_y = (yy * 0x200);
+ ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y);
+ xx++;
+ if (xx >= 8) {
+ yy++;
+ xx = 0;
+ }
+ DrawOverworldEntrances();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Overworld Editor canvas
+// Allows the user to make changes to the overworld map.
+void OverworldEditor::DrawOverworldCanvas() {
+ DrawOverworldMapSettings();
+ ImGui::Separator();
+ if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)7);
+ ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
+ ImGuiWindowFlags_AlwaysVerticalScrollbar |
+ ImGuiWindowFlags_AlwaysHorizontalScrollbar)) {
+ ow_map_canvas_.DrawBackground(ImVec2(0x200 * 8, 0x200 * 8));
+ ow_map_canvas_.DrawContextMenu();
+ if (overworld_.isLoaded()) {
+ DrawOverworldMaps();
+ // User has selected a tile they want to draw from the blockset.
+ if (!blockset_canvas_.Points().empty()) {
+ int x = blockset_canvas_.Points().front().x / 32;
+ int y = blockset_canvas_.Points().front().y / 32;
+ std::cout << x << " " << y << std::endl;
+ current_tile16_ = x + (y * 8);
+ std::cout << current_tile16_ << std::endl;
+ ow_map_canvas_.DrawTilePainter(tile16_individual_[current_tile16_], 16);
+ }
+ }
+ ow_map_canvas_.DrawGrid(64.0f);
+ ow_map_canvas_.DrawOverlay();
+ }
+ ImGui::EndChild();
+}
+
+// ----------------------------------------------------------------------------
+
+// Tile 16 Selector
+// Displays all the tiles in the game.
+void OverworldEditor::DrawTile16Selector() {
+ blockset_canvas_.DrawBackground(ImVec2(0x100 + 1, (8192 * 2) + 1));
+ blockset_canvas_.DrawContextMenu();
+ blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 2, map_blockset_loaded_);
+ blockset_canvas_.DrawTileSelector(32);
+ blockset_canvas_.DrawGrid(32.0f);
+ blockset_canvas_.DrawOverlay();
+}
+
+// ----------------------------------------------------------------------------
+
+// Tile 8 Selector
+// Displays all the individual tiles that make up a tile16.
+void OverworldEditor::DrawTile8Selector() {
+ graphics_bin_canvas_.DrawBackground(
+ ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1));
+ graphics_bin_canvas_.DrawContextMenu();
+ if (all_gfx_loaded_) {
+ for (const auto &[key, value] : graphics_bin_) {
+ int offset = 0x40 * (key + 1);
+ int top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 2;
+ if (key >= 1) {
+ top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 0x40 * key;
+ }
+ graphics_bin_canvas_.GetDrawList()->AddImage(
+ (void *)value.GetTexture(),
+ ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 2, top_left_y),
+ ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 0x100,
+ graphics_bin_canvas_.GetZeroPoint().y + offset));
+ }
+ }
+ graphics_bin_canvas_.DrawGrid(16.0f);
+ graphics_bin_canvas_.DrawOverlay();
+}
+
+// ----------------------------------------------------------------------------
+
+// Displays the graphics tilesheets that are available on the current selected
+// overworld map.
+void OverworldEditor::DrawAreaGraphics() {
+ current_gfx_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
+ current_gfx_canvas_.DrawContextMenu();
+ current_gfx_canvas_.DrawTileSelector(32);
+ current_gfx_canvas_.DrawBitmap(current_gfx_bmp_, 2, overworld_.isLoaded());
+ current_gfx_canvas_.DrawGrid(32.0f);
+ current_gfx_canvas_.DrawOverlay();
+}
+
+// ----------------------------------------------------------------------------
+
+void OverworldEditor::DrawTileSelector() {
+ if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
+ if (ImGui::BeginTabItem("Tile16")) {
+ if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)2);
+ ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
+ ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
+ DrawTile16Selector();
+ }
+ ImGui::EndChild();
+ ImGui::EndTabItem();
+ }
+ if (ImGui::BeginTabItem("Tile8")) {
+ if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)1);
+ ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
+ ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
+ DrawTile8Selector();
+ }
+ ImGui::EndChild();
+ ImGui::EndTabItem();
+ }
+ if (ImGui::BeginTabItem("Area Graphics")) {
+ if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
+ ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
+ ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
+ DrawAreaGraphics();
+ }
+ ImGui::EndChild();
+ ImGui::EndTabItem();
+ }
+ ImGui::EndTabBar();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+absl::Status OverworldEditor::LoadGraphics() {
+ // Load all of the graphics data from the game.
+ PRINT_IF_ERROR(rom_.LoadAllGraphicsData())
+ graphics_bin_ = rom_.GetGraphicsBin();
+
+ // Load the Link to the Past overworld.
+ RETURN_IF_ERROR(overworld_.Load(rom_))
+ palette_ = overworld_.AreaPalette();
+ current_gfx_bmp_.Create(0x80, 0x200, 0x40, overworld_.AreaGraphics());
+ current_gfx_bmp_.ApplyPalette(palette_);
+ rom_.RenderBitmap(¤t_gfx_bmp_);
+
+ // Create the tile16 blockset image
+ tile16_blockset_bmp_.Create(0x80, 8192, 0x80, overworld_.Tile16Blockset());
+ tile16_blockset_bmp_.ApplyPalette(palette_);
+ rom_.RenderBitmap(&tile16_blockset_bmp_);
+ map_blockset_loaded_ = true;
+
+ // Copy the tile16 data into individual tiles.
+ auto tile16_data = overworld_.Tile16Blockset();
+
+ // Loop through the tiles and copy their pixel data into separate vectors
+ for (int i = 0; i < 4096; i++) {
+ // Create a new vector for the pixel data of the current tile
+ Bytes tile_data;
+ for (int j = 0; j < 32 * 32; j++) tile_data.push_back(0x00);
+
+ // Copy the pixel data for the current tile into the vector
+ for (int ty = 0; ty < 32; ty++) {
+ for (int tx = 0; tx < 32; tx++) {
+ int position = (tx + (ty * 0x20));
+ uchar value = tile16_data[i + tx + (ty * 0x80)];
+ tile_data[position] = value;
+ }
+ }
+
+ // Add the vector for the current tile to the vector of tile pixel data
+ tile16_individual_data_.push_back(tile_data);
+ }
+
+ // Render the bitmaps of each tile.
+ for (int id = 0; id < 4096; id++) {
+ gfx::Bitmap new_tile16;
+ tile16_individual_.emplace_back(new_tile16);
+ tile16_individual_[id].Create(0x10, 0x10, 0x80,
+ tile16_individual_data_[id]);
+ tile16_individual_[id].ApplyPalette(palette_);
+ rom_.RenderBitmap(&tile16_individual_[id]);
+ }
+
+ // Render the overworld maps loaded from the ROM.
+ for (int i = 0; i < core::kNumOverworldMaps; ++i) {
+ overworld_.SetCurrentMap(i);
+ auto palette = overworld_.AreaPalette();
+ maps_bmp_[i].Create(0x200, 0x200, 0x200, overworld_.BitmapData());
+ maps_bmp_[i].ApplyPalette(palette);
+ rom_.RenderBitmap(&(maps_bmp_[i]));
+ }
+
+ return absl::OkStatus();
+}
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/editor/overworld_editor.h b/src/app/editor/overworld_editor.h
new file mode 100644
index 00000000..4c664843
--- /dev/null
+++ b/src/app/editor/overworld_editor.h
@@ -0,0 +1,118 @@
+#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
+#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
+
+#include
+
+#include
+#include
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_format.h"
+#include "app/editor/palette_editor.h"
+#include "app/gfx/bitmap.h"
+#include "app/gfx/snes_palette.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "app/zelda3/overworld.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+static constexpr uint k4BPP = 4;
+static constexpr uint kByteSize = 3;
+static constexpr uint kMessageIdSize = 5;
+static constexpr uint kNumSheetsToLoad = 223;
+static constexpr uint kTile8DisplayHeight = 64;
+static constexpr float kInputFieldSize = 30.f;
+
+static constexpr absl::string_view kToolsetColumnNames[] = {
+ "#undoTool", "#redoTool", "#drawTool", "#separator2",
+ "#zoomOutTool", "#zoomInTool", "#separator", "#history",
+ "#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
+ "#transportTool", "#musicTool"};
+
+static constexpr absl::string_view kOverworldSettingsColumnNames[] = {
+ "##1stCol", "##gfxCol", "##palCol", "##sprgfxCol",
+ "##sprpalCol", "##msgidCol", "##2ndCol"};
+
+class OverworldEditor {
+ public:
+ absl::Status Update();
+ absl::Status Undo() const { return absl::UnimplementedError("Undo"); }
+ absl::Status Redo() const { return absl::UnimplementedError("Redo"); }
+ absl::Status Cut() const { return absl::UnimplementedError("Cut"); }
+ absl::Status Copy() const { return absl::UnimplementedError("Copy"); }
+ absl::Status Paste() const { return absl::UnimplementedError("Paste"); }
+ void SetupROM(ROM &rom) { rom_ = rom; }
+
+ private:
+ absl::Status DrawToolset();
+ void DrawOverworldMapSettings();
+
+ void DrawOverworldEntrances();
+ void DrawOverworldMaps();
+ void DrawOverworldCanvas();
+
+ void DrawTile16Selector();
+ void DrawTile8Selector();
+ void DrawAreaGraphics();
+ void DrawTileSelector();
+ absl::Status LoadGraphics();
+
+ int current_world_ = 0;
+ int current_map_ = 0;
+ int current_tile16_ = 0;
+ int selected_tile_ = 0;
+ char map_gfx_[3] = "";
+ char map_palette_[3] = "";
+ char spr_gfx_[3] = "";
+ char spr_palette_[3] = "";
+ char message_id_[5] = "";
+ char staticgfx[16];
+
+ bool opt_enable_grid = true;
+ bool all_gfx_loaded_ = false;
+ bool map_blockset_loaded_ = false;
+ bool selected_tile_loaded_ = false;
+ bool update_selected_tile_ = true;
+
+ ImGuiTableFlags toolset_table_flags = ImGuiTableFlags_SizingFixedFit;
+ ImGuiTableFlags ow_map_flags = ImGuiTableFlags_Borders;
+ ImGuiTableFlags ow_edit_flags = ImGuiTableFlags_Reorderable |
+ ImGuiTableFlags_Resizable |
+ ImGuiTableFlags_SizingStretchSame;
+
+ Bytes selected_tile_data_;
+ std::unordered_map graphics_bin_;
+ std::unordered_map current_graphics_set_;
+ std::unordered_map maps_bmp_;
+ std::unordered_map sprite_previews_;
+
+ std::vector tile16_individual_data_;
+ std::vector tile16_individual_;
+
+ ROM rom_;
+ PaletteEditor palette_editor_;
+ zelda3::Overworld overworld_;
+
+ gfx::SNESPalette palette_;
+ gfx::Bitmap selected_tile_bmp_;
+ gfx::Bitmap tile16_blockset_bmp_;
+ gfx::Bitmap current_gfx_bmp_;
+ gfx::Bitmap all_gfx_bmp;
+
+ gui::Canvas ow_map_canvas_;
+ gui::Canvas current_gfx_canvas_;
+ gui::Canvas blockset_canvas_;
+ gui::Canvas graphics_bin_canvas_;
+};
+} // namespace editor
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/editor/palette_editor.cc b/src/app/editor/palette_editor.cc
new file mode 100644
index 00000000..00e6c381
--- /dev/null
+++ b/src/app/editor/palette_editor.cc
@@ -0,0 +1,128 @@
+#include "palette_editor.h"
+
+#include
+
+#include "absl/status/status.h"
+#include "app/gfx/snes_palette.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+absl::Status PaletteEditor::Update() {
+ for (int i = 0; i < 11; ++i) {
+ if (ImGui::TreeNode(kPaletteCategoryNames[i].data())) {
+ auto size = rom_.GetPaletteGroup(kPaletteGroupNames[i].data()).size;
+ auto palettes = rom_.GetPaletteGroup(kPaletteGroupNames[i].data());
+ for (int j = 0; j < size; j++) {
+ ImGui::Text("%d", j);
+ auto palette = palettes[j];
+ for (int n = 0; n < size; n++) {
+ ImGui::PushID(n);
+ if ((n % 8) != 0)
+ ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
+
+ ImGuiColorEditFlags palette_button_flags =
+ ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker;
+ if (ImGui::ColorButton("##palette", palette[n].RGB(),
+ palette_button_flags, ImVec2(20, 20)))
+ current_color_ =
+ ImVec4(palette[n].rgb.x, palette[n].rgb.y, palette[n].rgb.z,
+ current_color_.w); // Preserve alpha!
+
+ ImGui::PopID();
+ }
+ }
+ ImGui::TreePop();
+ }
+ }
+ return absl::OkStatus();
+}
+
+void PaletteEditor::DisplayPalette(gfx::SNESPalette& palette, bool loaded) {
+ static ImVec4 color = ImVec4(0, 0, 0, 255.f);
+ ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
+ ImGuiColorEditFlags_NoDragDrop |
+ ImGuiColorEditFlags_NoOptions;
+
+ // Generate a default palette. The palette will persist and can be edited.
+ static bool init = false;
+ static ImVec4 saved_palette[256] = {};
+ if (loaded && !init) {
+ for (int n = 0; n < palette.size_; n++) {
+ saved_palette[n].x = palette.GetColor(n).rgb.x / 255;
+ saved_palette[n].y = palette.GetColor(n).rgb.y / 255;
+ saved_palette[n].z = palette.GetColor(n).rgb.z / 255;
+ saved_palette[n].w = 255; // Alpha
+ }
+ init = true;
+ }
+
+ static ImVec4 backup_color;
+ bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags);
+ ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
+ open_popup |= ImGui::Button("Palette");
+ if (open_popup) {
+ ImGui::OpenPopup("mypicker");
+ backup_color = color;
+ }
+ if (ImGui::BeginPopup("mypicker")) {
+ ImGui::Text("Current Overworld Palette");
+ ImGui::Separator();
+ ImGui::ColorPicker4("##picker", (float*)&color,
+ misc_flags | ImGuiColorEditFlags_NoSidePreview |
+ ImGuiColorEditFlags_NoSmallPreview);
+ ImGui::SameLine();
+
+ ImGui::BeginGroup(); // Lock X position
+ ImGui::Text("Current ==>");
+ ImGui::SameLine();
+ ImGui::Text("Previous");
+
+ ImGui::ColorButton(
+ "##current", color,
+ ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
+ ImVec2(60, 40));
+ ImGui::SameLine();
+
+ if (ImGui::ColorButton(
+ "##previous", backup_color,
+ ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
+ ImVec2(60, 40)))
+ color = backup_color;
+ ImGui::Separator();
+ ImGui::Text("Palette");
+ for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) {
+ ImGui::PushID(n);
+ if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
+
+ ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
+ ImGuiColorEditFlags_NoPicker |
+ ImGuiColorEditFlags_NoTooltip;
+ if (ImGui::ColorButton("##palette", saved_palette[n],
+ palette_button_flags, ImVec2(20, 20)))
+ color = ImVec4(saved_palette[n].x, saved_palette[n].y,
+ saved_palette[n].z, color.w); // Preserve alpha!
+
+ if (ImGui::BeginDragDropTarget()) {
+ if (const ImGuiPayload* payload =
+ ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
+ memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
+ if (const ImGuiPayload* payload =
+ ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
+ memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
+ ImGui::EndDragDropTarget();
+ }
+
+ ImGui::PopID();
+ }
+ ImGui::EndGroup();
+ ImGui::EndPopup();
+ }
+}
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/editor/palette_editor.h b/src/app/editor/palette_editor.h
new file mode 100644
index 00000000..80ab485f
--- /dev/null
+++ b/src/app/editor/palette_editor.h
@@ -0,0 +1,42 @@
+#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
+#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
+
+#include
+
+#include "absl/status/status.h"
+#include "app/gfx/snes_palette.h"
+#include "app/rom.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+static constexpr absl::string_view kPaletteCategoryNames[] = {
+ "Sword", "Shield", "Clothes", "World Colors",
+ "Area Colors", "Enemies", "Dungeons", "World Map",
+ "Dungeon Map", "Triforce", "Crystal"};
+
+static constexpr absl::string_view kPaletteGroupNames[] = {
+ "swords", "shields", "armors", "ow_main",
+ "ow_aux", "global_sprites", "dungeon_main", "ow_mini_map",
+ "ow_mini_map", "3d_object", "3d_object"};
+
+class PaletteEditor {
+ public:
+ absl::Status Update();
+ void DisplayPalette(gfx::SNESPalette& palette, bool loaded);
+
+ auto SetupROM(ROM& rom) { rom_ = rom; }
+
+ private:
+ ImVec4 current_color_;
+ ROM rom_;
+};
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/editor/screen_editor.cc b/src/app/editor/screen_editor.cc
new file mode 100644
index 00000000..bcf19352
--- /dev/null
+++ b/src/app/editor/screen_editor.cc
@@ -0,0 +1,201 @@
+#include "app/editor/screen_editor.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/statusor.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "app/core/common.h"
+#include "app/core/constants.h"
+#include "app/gfx/bitmap.h"
+#include "app/gfx/snes_tile.h"
+#include "gui/canvas.h"
+#include "gui/icons.h"
+#include "gui/input.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); }
+
+void ScreenEditor::Update() {
+ TAB_BAR("##TabBar")
+ DrawInventoryMenuEditor();
+ DrawTitleScreenEditor();
+ DrawNamingScreenEditor();
+ DrawOverworldMapEditor();
+ DrawDungeonMapsEditor();
+ DrawMosaicEditor();
+ END_TAB_BAR()
+}
+
+void ScreenEditor::DrawWorldGrid(int world, int h, int w) {
+ const float time = (float)ImGui::GetTime();
+
+ int i = 0;
+ if (world == 1) {
+ i = 64;
+ } else if (world == 2) {
+ i = 128;
+ }
+ for (int y = 0; y < h; y++)
+ for (int x = 0; x < w; x++) {
+ if (x > 0) ImGui::SameLine();
+ ImGui::PushID(y * 4 + x);
+ std::string label = absl::StrCat(" #", absl::StrFormat("%x", i));
+ if (ImGui::Selectable(label.c_str(), mosaic_tiles_[i] != 0, 0,
+ ImVec2(35, 25))) {
+ mosaic_tiles_[i] ^= 1;
+ }
+ ImGui::PopID();
+ i++;
+ }
+}
+
+void ScreenEditor::DrawInventoryMenuEditor() {
+ TAB_ITEM("Inventory Menu")
+
+ static bool create = false;
+ if (!create && rom_.isLoaded()) {
+ inventory_.Create();
+ palette_ = inventory_.Palette();
+ create = true;
+ }
+
+ DrawInventoryToolset();
+
+ if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
+ ImGui::TableSetupColumn("Canvas");
+ ImGui::TableSetupColumn("Tiles");
+ ImGui::TableSetupColumn("Palette");
+ ImGui::TableHeadersRow();
+
+ ImGui::TableNextColumn();
+ screen_canvas_.DrawBackground();
+ screen_canvas_.DrawContextMenu();
+ screen_canvas_.DrawBitmap(inventory_.Bitmap(), 2, create);
+ screen_canvas_.DrawGrid(32.0f);
+ screen_canvas_.DrawOverlay();
+
+ ImGui::TableNextColumn();
+ tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
+ tilesheet_canvas_.DrawContextMenu();
+ tilesheet_canvas_.DrawBitmap(inventory_.Tilesheet(), 2, create);
+ tilesheet_canvas_.DrawGrid(16.0f);
+ tilesheet_canvas_.DrawOverlay();
+
+ ImGui::TableNextColumn();
+ gui::DisplayPalette(palette_, create);
+
+ ImGui::EndTable();
+ }
+ ImGui::Separator();
+ END_TAB_ITEM()
+}
+
+void ScreenEditor::DrawTitleScreenEditor() {
+ TAB_ITEM("Title Screen")
+ END_TAB_ITEM()
+}
+void ScreenEditor::DrawNamingScreenEditor() {
+ TAB_ITEM("Naming Screen")
+ END_TAB_ITEM()
+}
+void ScreenEditor::DrawOverworldMapEditor() {
+ TAB_ITEM("Overworld Map")
+ END_TAB_ITEM()
+}
+void ScreenEditor::DrawDungeonMapsEditor() {
+ TAB_ITEM("Dungeon Maps")
+ END_TAB_ITEM()
+}
+
+void ScreenEditor::DrawMosaicEditor() {
+ TAB_ITEM("Mosaic Transitions")
+
+ if (ImGui::BeginTable("Worlds", 3, ImGuiTableFlags_Borders)) {
+ ImGui::TableSetupColumn("Light World");
+ ImGui::TableSetupColumn("Dark World");
+ ImGui::TableSetupColumn("Special World");
+ ImGui::TableHeadersRow();
+
+ ImGui::TableNextColumn();
+ DrawWorldGrid(0);
+
+ ImGui::TableNextColumn();
+ DrawWorldGrid(1);
+
+ ImGui::TableNextColumn();
+ DrawWorldGrid(2, 4);
+
+ ImGui::EndTable();
+ }
+
+ gui::InputHex("Routine Location", &overworldCustomMosaicASM);
+
+ if (ImGui::Button("Generate Mosaic Assembly")) {
+ auto mosaic =
+ rom_.PatchOverworldMosaic(mosaic_tiles_, overworldCustomMosaicASM);
+ if (!mosaic.ok()) {
+ std::cout << mosaic;
+ }
+ }
+
+ END_TAB_ITEM()
+}
+
+void ScreenEditor::DrawToolset() {
+ static bool show_bg1 = true;
+ static bool show_bg2 = true;
+ static bool show_bg3 = true;
+
+ static bool drawing_bg1 = true;
+ static bool drawing_bg2 = false;
+ static bool drawing_bg3 = false;
+
+ ImGui::Checkbox("Show BG1", &show_bg1);
+ ImGui::SameLine();
+ ImGui::Checkbox("Show BG2", &show_bg2);
+
+ ImGui::Checkbox("Draw BG1", &drawing_bg1);
+ ImGui::SameLine();
+ ImGui::Checkbox("Draw BG2", &drawing_bg2);
+ ImGui::SameLine();
+ ImGui::Checkbox("Draw BG3", &drawing_bg3);
+}
+
+void ScreenEditor::DrawInventoryToolset() {
+ if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
+ ImVec2(0, 0))) {
+ ImGui::TableSetupColumn("#drawTool");
+ ImGui::TableSetupColumn("#sep1");
+ ImGui::TableSetupColumn("#zoomOut");
+ ImGui::TableSetupColumn("#zoomIN");
+ ImGui::TableSetupColumn("#sep2");
+ ImGui::TableSetupColumn("#bg2Tool");
+ ImGui::TableSetupColumn("#bg3Tool");
+ ImGui::TableSetupColumn("#itemTool");
+
+ BUTTON_COLUMN(ICON_MD_UNDO)
+ BUTTON_COLUMN(ICON_MD_REDO)
+ TEXT_COLUMN(ICON_MD_MORE_VERT)
+ BUTTON_COLUMN(ICON_MD_ZOOM_OUT)
+ BUTTON_COLUMN(ICON_MD_ZOOM_IN)
+ TEXT_COLUMN(ICON_MD_MORE_VERT)
+ BUTTON_COLUMN(ICON_MD_DRAW)
+ BUTTON_COLUMN(ICON_MD_BUILD)
+
+ ImGui::EndTable();
+ }
+}
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/editor/screen_editor.h b/src/app/editor/screen_editor.h
new file mode 100644
index 00000000..6de72848
--- /dev/null
+++ b/src/app/editor/screen_editor.h
@@ -0,0 +1,60 @@
+#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
+#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
+
+#include
+
+#include
+
+#include "app/core/constants.h"
+#include "app/gfx/bitmap.h"
+#include "app/gfx/snes_palette.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "app/zelda3/inventory.h"
+#include "gui/canvas.h"
+#include "gui/color.h"
+#include "gui/icons.h"
+
+namespace yaze {
+namespace app {
+namespace editor {
+
+using MosaicArray = std::array;
+static int overworldCustomMosaicASM = 0x1301D0;
+
+class ScreenEditor {
+ public:
+ ScreenEditor();
+ void SetupROM(ROM &rom) {
+ rom_ = rom;
+ inventory_.SetupROM(rom_);
+ }
+ void Update();
+
+ private:
+ void DrawMosaicEditor();
+ void DrawTitleScreenEditor();
+ void DrawNamingScreenEditor();
+ void DrawOverworldMapEditor();
+ void DrawDungeonMapsEditor();
+ void DrawInventoryMenuEditor();
+
+ void DrawToolset();
+ void DrawInventoryToolset();
+ void DrawWorldGrid(int world, int h = 8, int w = 8);
+
+ char mosaic_tiles_[core::kNumOverworldMaps];
+
+ ROM rom_;
+ Bytes all_gfx_;
+ zelda3::Inventory inventory_;
+ gfx::SNESPalette palette_;
+ gui::Canvas screen_canvas_;
+ gui::Canvas tilesheet_canvas_;
+};
+
+} // namespace editor
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/gfx/bitmap.cc b/src/app/gfx/bitmap.cc
new file mode 100644
index 00000000..c4ab757b
--- /dev/null
+++ b/src/app/gfx/bitmap.cc
@@ -0,0 +1,135 @@
+#include "bitmap.h"
+
+#include
+
+#include
+#include
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "app/core/constants.h"
+#include "app/gfx/snes_palette.h"
+
+namespace yaze {
+namespace app {
+namespace gfx {
+
+namespace {
+void GrayscalePalette(SDL_Palette *palette) {
+ for (int i = 0; i < 8; i++) {
+ palette->colors[i].r = i * 31;
+ palette->colors[i].g = i * 31;
+ palette->colors[i].b = i * 31;
+ }
+}
+} // namespace
+
+Bitmap::Bitmap(int width, int height, int depth, uchar *data) {
+ Create(width, height, depth, data);
+}
+
+Bitmap::Bitmap(int width, int height, int depth, int data_size) {
+ Create(width, height, depth, data_size);
+}
+
+Bitmap::Bitmap(int width, int height, int depth, uchar *data, int data_size) {
+ Create(width, height, depth, data, data_size);
+}
+
+// Pass raw pixel data directly to the surface
+void Bitmap::Create(int width, int height, int depth, uchar *data) {
+ active_ = true;
+ width_ = width;
+ height_ = height;
+ depth_ = depth;
+ pixel_data_ = data;
+ surface_ = std::unique_ptr(
+ SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
+ SDL_PIXELFORMAT_INDEX8),
+ SDL_Surface_Deleter());
+ surface_->pixels = pixel_data_;
+ GrayscalePalette(surface_->format->palette);
+}
+
+// Reserves data to later draw to surface via pointer
+void Bitmap::Create(int width, int height, int depth, int size) {
+ active_ = true;
+ width_ = width;
+ height_ = height;
+ depth_ = depth;
+ data_size_ = size;
+ data_.reserve(size);
+ pixel_data_ = data_.data();
+ surface_ = std::unique_ptr(
+ SDL_CreateRGBSurfaceWithFormat(0, width, height, depth,
+ SDL_PIXELFORMAT_INDEX8),
+ SDL_Surface_Deleter());
+ surface_->pixels = pixel_data_;
+ GrayscalePalette(surface_->format->palette);
+}
+
+// Pass raw pixel data directly to the surface
+void Bitmap::Create(int width, int height, int depth, uchar *data, int size) {
+ active_ = true;
+ width_ = width;
+ height_ = height;
+ depth_ = depth;
+ pixel_data_ = data;
+ data_size_ = size;
+ surface_ = std::unique_ptr(
+ SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
+ SDL_PIXELFORMAT_INDEX8),
+ SDL_Surface_Deleter());
+ surface_->pixels = pixel_data_;
+ GrayscalePalette(surface_->format->palette);
+}
+
+void Bitmap::Create(int width, int height, int depth, Bytes data) {
+ active_ = true;
+ width_ = width;
+ height_ = height;
+ depth_ = depth;
+ data_ = data;
+ pixel_data_ = data_.data();
+ surface_ = std::unique_ptr(
+ SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_,
+ SDL_PIXELFORMAT_INDEX8),
+ SDL_Surface_Deleter());
+ surface_->pixels = pixel_data_;
+ GrayscalePalette(surface_->format->palette);
+}
+
+void Bitmap::Apply(Bytes data) {
+ pixel_data_ = data.data();
+ data_ = data;
+}
+
+// Creates the texture that will be displayed to the screen.
+void Bitmap::CreateTexture(std::shared_ptr renderer) {
+ texture_ = std::shared_ptr{
+ SDL_CreateTextureFromSurface(renderer.get(), surface_.get()),
+ SDL_Texture_Deleter{}};
+}
+
+// Convert SNESPalette to SDL_Palette for surface.
+void Bitmap::ApplyPalette(const SNESPalette &palette) {
+ palette_ = palette;
+ for (int i = 0; i < palette.size_; ++i) {
+ if (palette.GetColor(i).transparent) {
+ surface_->format->palette->colors[i].r = 0;
+ surface_->format->palette->colors[i].g = 0;
+ surface_->format->palette->colors[i].b = 0;
+ surface_->format->palette->colors[i].a = 0;
+ } else {
+ surface_->format->palette->colors[i].r = palette.GetColor(i).rgb.x;
+ surface_->format->palette->colors[i].g = palette.GetColor(i).rgb.y;
+ surface_->format->palette->colors[i].b = palette.GetColor(i).rgb.z;
+ surface_->format->palette->colors[i].a = palette.GetColor(i).rgb.w;
+ }
+ }
+}
+
+} // namespace gfx
+} // namespace app
+} // namespace yaze
diff --git a/src/app/gfx/bitmap.h b/src/app/gfx/bitmap.h
new file mode 100644
index 00000000..9ae90ee3
--- /dev/null
+++ b/src/app/gfx/bitmap.h
@@ -0,0 +1,87 @@
+#ifndef YAZE_APP_GFX_BITMAP_H
+#define YAZE_APP_GFX_BITMAP_H
+
+#include
+
+#include
+#include
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "app/core/constants.h"
+#include "app/gfx/snes_palette.h"
+
+namespace yaze {
+namespace app {
+namespace gfx {
+
+class Bitmap {
+ public:
+ Bitmap() = default;
+ Bitmap(int width, int height, int depth, uchar *data);
+ Bitmap(int width, int height, int depth, int data_size);
+ Bitmap(int width, int height, int depth, uchar *data, int data_size);
+
+ void Create(int width, int height, int depth, uchar *data);
+ void Create(int width, int height, int depth, int data_size);
+ void Create(int width, int height, int depth, uchar *data, int data_size);
+ void Create(int width, int height, int depth, Bytes data);
+
+ void Apply(Bytes data);
+
+ void CreateTexture(std::shared_ptr renderer);
+
+ void ApplyPalette(const SNESPalette &palette);
+
+ void WriteToPixel(int position, uchar value) {
+ this->pixel_data_[position] = value;
+ }
+
+ int GetWidth() const { return width_; }
+ int GetHeight() const { return height_; }
+ auto GetSize() const { return data_size_; }
+ auto GetData() const { return pixel_data_; }
+ auto GetByte(int i) const { return pixel_data_[i]; }
+ auto GetTexture() const { return texture_.get(); }
+ auto GetSurface() const { return surface_.get(); }
+ auto IsActive() const { return active_; }
+
+ private:
+ struct SDL_Texture_Deleter {
+ void operator()(SDL_Texture *p) const {
+ if (p != nullptr) {
+ SDL_DestroyTexture(p);
+ p = nullptr;
+ }
+ }
+ };
+
+ struct SDL_Surface_Deleter {
+ void operator()(SDL_Surface *p) const {
+ if (p != nullptr) {
+ p->pixels = nullptr;
+ SDL_FreeSurface(p);
+ p = nullptr;
+ }
+ }
+ };
+
+ int width_ = 0;
+ int height_ = 0;
+ int depth_ = 0;
+ int data_size_ = 0;
+ bool freed_ = false;
+ bool active_ = false;
+ uchar *pixel_data_;
+ Bytes data_;
+ gfx::SNESPalette palette_;
+ std::shared_ptr texture_ = nullptr;
+ std::shared_ptr surface_ = nullptr;
+};
+
+} // namespace gfx
+} // namespace app
+} // namespace yaze
+
+#endif // YAZE_APP_GFX_BITMAP_H
\ No newline at end of file
diff --git a/src/app/gfx/snes_palette.cc b/src/app/gfx/snes_palette.cc
new file mode 100644
index 00000000..5ff1c878
--- /dev/null
+++ b/src/app/gfx/snes_palette.cc
@@ -0,0 +1,197 @@
+#include "snes_palette.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "app/core/constants.h"
+
+namespace yaze {
+namespace app {
+namespace gfx {
+
+ushort ConvertRGBtoSNES(const snes_color color) {
+ uchar red = color.red / 8;
+ uchar green = color.green / 8;
+ uchar blue = color.blue / 8;
+ return blue * 1024 + green * 32 + red;
+}
+
+snes_color ConvertSNEStoRGB(const ushort color) {
+ snes_color toret;
+
+ toret.red = ((color) % 32) * 8;
+ toret.green = ((color / 32) % 32) * 8;
+ toret.blue = ((color / 1024) % 32) * 8;
+
+ toret.red = toret.red + toret.red / 32;
+ toret.green = toret.green + toret.green / 32;
+ toret.blue = toret.blue + toret.blue / 32;
+ return toret;
+}
+
+snes_palette* Extract(const char* data, const unsigned int offset,
+ const unsigned int palette_size) {
+ snes_palette* toret = nullptr; // palette_create(palette_size, 0)
+ unsigned colnum = 0;
+ for (int i = 0; i < palette_size * 2; i += 2) {
+ unsigned short snes_color;
+ snes_color = ((uchar)data[offset + i + 1]) << 8;
+ snes_color = snes_color | ((uchar)data[offset + i]);
+ toret->colors[colnum] = ConvertSNEStoRGB(snes_color);
+ colnum++;
+ }
+ return toret;
+}
+
+char* Convert(const snes_palette pal) {
+ char* toret = (char*)malloc(pal.size * 2);
+ for (unsigned int i = 0; i < pal.size; i++) {
+ unsigned short snes_data = ConvertRGBtoSNES(pal.colors[i]);
+ toret[i * 2] = snes_data & 0xFF;
+ toret[i * 2 + 1] = snes_data >> 8;
+ }
+ return toret;
+}
+
+// ============================================================================
+
+SNESColor::SNESColor() : rgb(ImVec4(0.f, 0.f, 0.f, 0.f)) {}
+
+SNESColor::SNESColor(snes_color val) {
+ rgb.x = val.red;
+ rgb.y = val.green;
+ rgb.z = val.blue;
+}
+
+SNESColor::SNESColor(ImVec4 val) : rgb(val) {
+ snes_color col;
+ col.red = (uchar)val.x;
+ col.blue = (uchar)val.y;
+ col.green = (uchar)val.z;
+ snes = ConvertRGBtoSNES(col);
+}
+
+void SNESColor::setRgb(ImVec4 val) {
+ rgb = val;
+ snes_color col;
+ col.red = val.x;
+ col.blue = val.y;
+ col.green = val.z;
+ snes = ConvertRGBtoSNES(col);
+}
+
+void SNESColor::setSNES(snes_color val) {
+ rgb = ImVec4(val.red, val.green, val.blue, 255.f);
+}
+
+void SNESColor::setSNES(uint16_t val) {
+ snes = val;
+ snes_color col = ConvertSNEStoRGB(val);
+ rgb = ImVec4(col.red, col.green, col.blue, 0.f);
+}
+
+// ============================================================================
+
+SNESPalette::SNESPalette(uint8_t mSize) : size_(mSize) {
+ for (unsigned int i = 0; i < mSize; i++) {
+ SNESColor col;
+ colors.push_back(col);
+ }
+}
+
+SNESPalette::SNESPalette(char* data) : size_(sizeof(data) / 2) {
+ assert((sizeof(data) % 4 == 0) && (sizeof(data) <= 32));
+ for (unsigned i = 0; i < sizeof(data); i += 2) {
+ SNESColor col;
+ col.snes = static_cast(data[i + 1]) << 8;
+ col.snes = col.snes | static_cast(data[i]);
+ snes_color mColor = ConvertSNEStoRGB(col.snes);
+ col.rgb = ImVec4(mColor.red, mColor.green, mColor.blue, 1.f);
+ colors.push_back(col);
+ }
+}
+
+SNESPalette::SNESPalette(const unsigned char* snes_pal)
+ : size_(sizeof(snes_pal) / 2) {
+ assert((sizeof(snes_pal) % 4 == 0) && (sizeof(snes_pal) <= 32));
+ for (unsigned i = 0; i < sizeof(snes_pal); i += 2) {
+ SNESColor col;
+ col.snes = snes_pal[i + 1] << (uint16_t)8;
+ col.snes = col.snes | snes_pal[i];
+ snes_color mColor = ConvertSNEStoRGB(col.snes);
+ col.rgb = ImVec4(mColor.red, mColor.green, mColor.blue, 1.f);
+ colors.push_back(col);
+ }
+}
+
+SNESPalette::SNESPalette(const std::vector& cols) {
+ for (const auto& each : cols) {
+ SNESColor scol;
+ scol.setRgb(each);
+ colors.push_back(scol);
+ }
+ size_ = cols.size();
+}
+
+SNESPalette::SNESPalette(const std::vector& cols) {
+ for (const auto& each : cols) {
+ SNESColor scol;
+ scol.setSNES(each);
+ colors.push_back(scol);
+ }
+ size_ = cols.size();
+}
+
+SNESPalette::SNESPalette(const std::vector& cols) {
+ for (const auto& each : cols) {
+ colors.push_back(each);
+ }
+ size_ = cols.size();
+}
+
+void SNESPalette::Create(const std::vector& cols) {
+ for (const auto each : cols) {
+ colors.push_back(each);
+ }
+ size_ = cols.size();
+}
+
+char* SNESPalette::encode() {
+ auto data = new char[size_ * 2];
+ for (unsigned int i = 0; i < size_; i++) {
+ std::cout << colors[i].snes << std::endl;
+ data[i * 2] = (char)(colors[i].snes & 0xFF);
+ data[i * 2 + 1] = (char)(colors[i].snes >> 8);
+ }
+ return data;
+}
+
+SDL_Palette* SNESPalette::GetSDL_Palette() {
+ auto sdl_palette = std::make_shared();
+ sdl_palette->ncolors = size_;
+
+ auto color = std::vector(size_);
+ for (int i = 0; i < size_; i++) {
+ color[i].r = (uint8_t)colors[i].rgb.x * 100;
+ color[i].g = (uint8_t)colors[i].rgb.y * 100;
+ color[i].b = (uint8_t)colors[i].rgb.z * 100;
+ color[i].a = 0;
+ std::cout << "Color " << i << " added (R:" << color[i].r
+ << " G:" << color[i].g << " B:" << color[i].b << ")" << std::endl;
+ }
+ sdl_palette->colors = color.data();
+ return sdl_palette.get();
+}
+
+PaletteGroup::PaletteGroup(uint8_t mSize) : size(mSize) {}
+
+} // namespace gfx
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/gfx/snes_palette.h b/src/app/gfx/snes_palette.h
new file mode 100644
index 00000000..1c0e4143
--- /dev/null
+++ b/src/app/gfx/snes_palette.h
@@ -0,0 +1,117 @@
+#ifndef YAZE_APP_GFX_PALETTE_H
+#define YAZE_APP_GFX_PALETTE_H
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "app/core/constants.h"
+
+namespace yaze {
+namespace app {
+namespace gfx {
+
+struct snes_color {
+ uchar red;
+ uchar blue;
+ uchar green;
+};
+using snes_color = struct snes_color;
+
+struct snes_palette {
+ uint id;
+ uint size;
+ snes_color* colors;
+};
+using snes_palette = struct snes_palette;
+
+ushort ConvertRGBtoSNES(const snes_color color);
+snes_color ConvertSNEStoRGB(const ushort snes_color);
+snes_palette* Extract(const char* data, const unsigned int offset,
+ const unsigned int palette_size);
+char* Convert(const snes_palette pal);
+
+struct SNESColor {
+ SNESColor();
+ explicit SNESColor(ImVec4);
+ explicit SNESColor(snes_color);
+
+ void setRgb(ImVec4);
+ void setSNES(snes_color);
+ void setSNES(uint16_t);
+ void setTransparent(bool t) { transparent = t; }
+
+ auto RGB() {
+ return ImVec4(rgb.x / 255, rgb.y / 255, rgb.z / 255, rgb.w);
+ }
+
+ bool transparent = false;
+ uint16_t snes = 0;
+ ImVec4 rgb;
+};
+
+class SNESPalette {
+ public:
+ SNESPalette() = default;
+ explicit SNESPalette(uint8_t mSize);
+ explicit SNESPalette(char* snesPal);
+ explicit SNESPalette(const unsigned char* snes_pal);
+ explicit SNESPalette(const std::vector&);
+ explicit SNESPalette(const std::vector&);
+ explicit SNESPalette(const std::vector&);
+
+ void Create(const std::vector&);
+ void AddColor(SNESColor color) { colors.push_back(color); }
+ auto GetColor(int i) const { return colors[i]; }
+
+ SNESColor operator[](int i) {
+ if (i > size_) {
+ std::cout << "SNESPalette: Index out of bounds" << std::endl;
+ return colors[0];
+ }
+ return colors[i];
+ }
+
+ char* encode();
+ SDL_Palette* GetSDL_Palette();
+
+ int size_ = 0;
+ std::vector colors;
+};
+
+struct PaletteGroup {
+ PaletteGroup() = default;
+ explicit PaletteGroup(uint8_t mSize);
+ void AddPalette(SNESPalette pal) {
+ palettes.emplace_back(pal);
+ size = palettes.size();
+ }
+ void AddColor(SNESColor color) {
+ if (size == 0) {
+ SNESPalette empty_pal;
+ palettes.emplace_back(empty_pal);
+ }
+ palettes[0].AddColor(color);
+ }
+ SNESPalette operator[](int i) {
+ if (i > size) {
+ std::cout << "PaletteGroup: Index out of bounds" << std::endl;
+ return palettes[0];
+ }
+ return palettes[i];
+ }
+ int size = 0;
+ std::vector palettes;
+};
+
+} // namespace gfx
+} // namespace app
+} // namespace yaze
+
+#endif // YAZE_APP_GFX_PALETTE_H
\ No newline at end of file
diff --git a/src/app/gfx/snes_tile.cc b/src/app/gfx/snes_tile.cc
new file mode 100644
index 00000000..3e02f34b
--- /dev/null
+++ b/src/app/gfx/snes_tile.cc
@@ -0,0 +1,29 @@
+#include "snes_tile.h"
+
+#include
+#include
+
+#include "app/core/constants.h"
+
+namespace yaze {
+namespace app {
+namespace gfx {
+
+TileInfo GetTilesInfo(ushort tile) {
+ // vhopppcc cccccccc
+ bool o = false;
+ bool v = false;
+ bool h = false;
+ auto tid = (ushort)(tile & core::TileNameMask);
+ auto p = (uchar)((tile >> 10) & 0x07);
+
+ o = ((tile & core::TilePriorityBit) == core::TilePriorityBit);
+ h = ((tile & core::TileHFlipBit) == core::TileHFlipBit);
+ v = ((tile & core::TileVFlipBit) == core::TileVFlipBit);
+
+ return TileInfo(tid, p, v, h, o);
+}
+
+} // namespace gfx
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/gfx/snes_tile.h b/src/app/gfx/snes_tile.h
new file mode 100644
index 00000000..72764333
--- /dev/null
+++ b/src/app/gfx/snes_tile.h
@@ -0,0 +1,94 @@
+#ifndef YAZE_APP_GFX_SNES_TILE_H
+#define YAZE_APP_GFX_SNES_TILE_H
+
+#include
+#include
+
+#include "app/core/constants.h"
+
+namespace yaze {
+namespace app {
+namespace gfx {
+
+struct tile8 {
+ unsigned int id;
+ char data[64];
+ unsigned int palette_id;
+};
+using tile8 = struct tile8;
+
+// vhopppcc cccccccc
+// [0, 1]
+// [2, 3]
+class TileInfo {
+ public:
+ ushort id_;
+ bool over_;
+ bool vertical_mirror_;
+ bool horizontal_mirror_;
+ uchar palette_;
+ TileInfo() = default;
+ TileInfo(ushort id, uchar palette, bool v, bool h, bool o)
+ : id_(id),
+ over_(o),
+ vertical_mirror_(v),
+ horizontal_mirror_(h),
+ palette_(palette) {}
+};
+
+TileInfo GetTilesInfo(ushort tile);
+
+class Tile32 {
+ public:
+ ushort tile0_;
+ ushort tile1_;
+ ushort tile2_;
+ ushort tile3_;
+
+ Tile32(ushort t0, ushort t1, ushort t2, ushort t3)
+ : tile0_(t0), tile1_(t1), tile2_(t2), tile3_(t3) {}
+};
+
+class Tile16 {
+ public:
+ TileInfo tile0_;
+ TileInfo tile1_;
+ TileInfo tile2_;
+ TileInfo tile3_;
+ std::vector tiles_info;
+
+ Tile16() = default;
+ Tile16(TileInfo t0, TileInfo t1, TileInfo t2, TileInfo t3)
+ : tile0_(t0), tile1_(t1), tile2_(t2), tile3_(t3) {
+ tiles_info.push_back(tile0_);
+ tiles_info.push_back(tile1_);
+ tiles_info.push_back(tile2_);
+ tiles_info.push_back(tile3_);
+ }
+};
+
+class OAMTile {
+ public:
+ int x_;
+ int y_;
+ int mx_;
+ int my_;
+ int pal_;
+ ushort tile_;
+ OAMTile() = default;
+ OAMTile(int x, int y, ushort tile, int pal, bool upper = false, int mx = 0,
+ int my = 0)
+ : x_(x), y_(y), mx_(mx), my_(my), pal_(pal) {
+ if (upper) {
+ tile_ = (ushort)(tile + 512);
+ } else {
+ tile_ = (ushort)(tile + 256 + 512);
+ }
+ }
+};
+
+} // namespace gfx
+} // namespace app
+} // namespace yaze
+
+#endif // YAZE_APP_GFX_SNES_TILE_H
\ No newline at end of file
diff --git a/src/app/rom.cc b/src/app/rom.cc
new file mode 100644
index 00000000..8c6f6310
--- /dev/null
+++ b/src/app/rom.cc
@@ -0,0 +1,851 @@
+#include "rom.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "app/core/common.h"
+#include "app/core/constants.h"
+#include "app/gfx/bitmap.h"
+
+namespace yaze {
+namespace app {
+
+namespace lc_lz2 {
+
+void PrintCompressionPiece(const std::shared_ptr& piece) {
+ printf("Command: %d\n", piece->command);
+ printf("Command kength: %d\n", piece->length);
+ printf("Argument:");
+ auto arg_size = piece->argument.size();
+ for (int i = 0; i < arg_size; ++i) {
+ printf("%02X ", piece->argument.at(i));
+ }
+ printf("\nArgument length: %d\n", piece->argument_length);
+}
+
+void PrintCompressionChain(
+ const std::shared_ptr& compressed_chain_start) {
+ auto compressed_chain = compressed_chain_start->next;
+ while (compressed_chain != nullptr) {
+ printf("- Compression Piece -\n");
+ PrintCompressionPiece(compressed_chain);
+ compressed_chain = compressed_chain->next;
+ }
+}
+
+void CheckByteRepeat(const uchar* rom_data, DataSizeArray& data_size_taken,
+ CommandArgumentArray& cmd_args, uint& src_data_pos,
+ const uint last_pos) {
+ uint pos = src_data_pos;
+ char byte_to_repeat = rom_data[pos];
+ while (pos <= last_pos && rom_data[pos] == byte_to_repeat) {
+ data_size_taken[kCommandByteFill]++;
+ pos++;
+ }
+ cmd_args[kCommandByteFill][0] = byte_to_repeat;
+}
+
+void CheckWordRepeat(const uchar* rom_data, DataSizeArray& data_size_taken,
+ CommandArgumentArray& cmd_args, uint& src_data_pos,
+ const uint last_pos) {
+ if (src_data_pos + 2 <= last_pos &&
+ rom_data[src_data_pos] != rom_data[src_data_pos + 1]) {
+ uint pos = src_data_pos;
+ char byte1 = rom_data[pos];
+ char byte2 = rom_data[pos + 1];
+ pos += 2;
+ data_size_taken[kCommandWordFill] = 2;
+ while (pos + 1 <= last_pos) {
+ if (rom_data[pos] == byte1 && rom_data[pos + 1] == byte2)
+ data_size_taken[kCommandWordFill] += 2;
+ else
+ break;
+ pos += 2;
+ }
+ cmd_args[kCommandWordFill][0] = byte1;
+ cmd_args[kCommandWordFill][1] = byte2;
+ }
+}
+
+void CheckIncByte(const uchar* rom_data, DataSizeArray& data_size_taken,
+ CommandArgumentArray& cmd_args, uint& src_data_pos,
+ const uint last_pos) {
+ uint pos = src_data_pos;
+ char byte = rom_data[pos];
+ pos++;
+ data_size_taken[kCommandIncreasingFill] = 1;
+ byte++;
+ while (pos <= last_pos && byte == rom_data[pos]) {
+ data_size_taken[kCommandIncreasingFill]++;
+ byte++;
+ pos++;
+ }
+ cmd_args[kCommandIncreasingFill][0] = rom_data[src_data_pos];
+}
+
+void CheckIntraCopy(const uchar* rom_data, DataSizeArray& data_size_taken,
+ CommandArgumentArray& cmd_args, uint& src_data_pos,
+ const uint last_pos, uint start) {
+ if (src_data_pos != start) {
+ uint searching_pos = start;
+ uint current_pos_u = src_data_pos;
+ uint copied_size = 0;
+ uint search_start = start;
+
+ while (searching_pos < src_data_pos && current_pos_u <= last_pos) {
+ while (rom_data[current_pos_u] != rom_data[searching_pos] &&
+ searching_pos < src_data_pos)
+ searching_pos++;
+ search_start = searching_pos;
+ while (current_pos_u <= last_pos &&
+ rom_data[current_pos_u] == rom_data[searching_pos] &&
+ searching_pos < src_data_pos) {
+ copied_size++;
+ current_pos_u++;
+ searching_pos++;
+ }
+ if (copied_size > data_size_taken[kCommandRepeatingBytes]) {
+ search_start -= start;
+ printf("- Found repeat of %d at %d\n", copied_size, search_start);
+ data_size_taken[kCommandRepeatingBytes] = copied_size;
+ cmd_args[kCommandRepeatingBytes][0] = search_start & kSnesByteMax;
+ cmd_args[kCommandRepeatingBytes][1] = search_start >> 8;
+ }
+ current_pos_u = src_data_pos;
+ copied_size = 0;
+ }
+ }
+}
+
+// Check if a command managed to pick up `max_win` or more bytes
+// Avoids being even with copy command, since it's possible to merge copy
+void ValidateForByteGain(const DataSizeArray& data_size_taken,
+ const CommandSizeArray& cmd_size, uint& max_win,
+ uint& cmd_with_max) {
+ for (uint cmd_i = 1; cmd_i < 5; cmd_i++) {
+ uint cmd_size_taken = data_size_taken[cmd_i];
+ // TODO(@scawful): Replace conditional with table of command sizes
+ // "Table that is even with copy but all other cmd are 2"
+ auto table_check =
+ !(cmd_i == kCommandRepeatingBytes && cmd_size_taken == 3);
+ if (cmd_size_taken > max_win && cmd_size_taken > cmd_size[cmd_i] &&
+ table_check) {
+ printf("==> C:%d / S:%d\n", cmd_i, cmd_size_taken);
+ cmd_with_max = cmd_i;
+ max_win = cmd_size_taken;
+ }
+ }
+}
+
+void CompressionCommandAlternative(
+ const uchar* rom_data, std::shared_ptr& compressed_chain,
+ const CommandSizeArray& cmd_size, const CommandArgumentArray& cmd_args,
+ uint& src_data_pos, uint& comp_accumulator, uint& cmd_with_max,
+ uint& max_win) {
+ printf("- Ok we get a gain from %d\n", cmd_with_max);
+ std::string buffer;
+ buffer.push_back(cmd_args[cmd_with_max][0]);
+ if (cmd_size[cmd_with_max] == 2) {
+ buffer.push_back(cmd_args[cmd_with_max][1]);
+ }
+
+ auto new_comp_piece = std::make_shared(
+ cmd_with_max, max_win, buffer, cmd_size[cmd_with_max]);
+ PrintCompressionPiece(new_comp_piece);
+ // If we let non compressed stuff, we need to add a copy chunk before
+ if (comp_accumulator != 0) {
+ std::string copy_buff;
+ copy_buff.resize(comp_accumulator);
+ for (int i = 0; i < comp_accumulator; ++i) {
+ copy_buff[i] = rom_data[i + src_data_pos - comp_accumulator];
+ }
+ auto copy_chunk = std::make_shared(
+ kCommandDirectCopy, comp_accumulator, copy_buff, comp_accumulator);
+ compressed_chain->next = copy_chunk;
+ compressed_chain = copy_chunk;
+ } else {
+ compressed_chain->next = new_comp_piece;
+ compressed_chain = new_comp_piece;
+ }
+ src_data_pos += max_win;
+ comp_accumulator = 0;
+}
+
+absl::StatusOr> SplitCompressionPiece(
+ std::shared_ptr& piece, int mode) {
+ std::shared_ptr new_piece;
+ uint length_left = piece->length - kMaxLengthCompression;
+ piece->length = kMaxLengthCompression;
+
+ switch (piece->command) {
+ case kCommandByteFill:
+ case kCommandWordFill:
+ new_piece = std::make_shared(
+ piece->command, length_left, piece->argument, piece->argument_length);
+ break;
+ case kCommandIncreasingFill:
+ new_piece = std::make_shared(
+ piece->command, length_left, piece->argument, piece->argument_length);
+ new_piece->argument[0] =
+ (char)(piece->argument[0] + kMaxLengthCompression);
+ break;
+ case kCommandDirectCopy:
+ piece->argument_length = kMaxLengthCompression;
+ new_piece = std::make_shared(
+ piece->command, length_left, nullptr, length_left);
+ // MEMCPY
+ for (int i = 0; i < length_left; ++i) {
+ new_piece->argument[i] = piece->argument[i + kMaxLengthCompression];
+ }
+ break;
+ case kCommandRepeatingBytes: {
+ piece->argument_length = kMaxLengthCompression;
+ uint offset = piece->argument[0] + (piece->argument[1] << 8);
+ new_piece = std::make_shared(
+ piece->command, length_left, piece->argument, piece->argument_length);
+ if (mode == kNintendoMode2) {
+ new_piece->argument[0] =
+ (offset + kMaxLengthCompression) & kSnesByteMax;
+ new_piece->argument[1] = (offset + kMaxLengthCompression) >> 8;
+ }
+ if (mode == kNintendoMode1) {
+ new_piece->argument[1] =
+ (offset + kMaxLengthCompression) & kSnesByteMax;
+ new_piece->argument[0] = (offset + kMaxLengthCompression) >> 8;
+ }
+ } break;
+ default: {
+ return absl::InvalidArgumentError(
+ "SplitCompressionCommand: Invalid Command");
+ }
+ }
+ return new_piece;
+}
+
+Bytes CreateCompressionString(std::shared_ptr& start,
+ int mode) {
+ uint pos = 0;
+ auto piece = start;
+ Bytes output;
+
+ while (piece != nullptr) {
+ if (piece->length <= kMaxLengthNormalHeader) { // Normal header
+ output.push_back(BUILD_HEADER(piece->command, piece->length));
+ pos++;
+ } else {
+ if (piece->length <= kMaxLengthCompression) {
+ output.push_back(kCompressionStringMod | ((uchar)piece->command << 2) |
+ (((piece->length - 1) & 0xFF00) >> 8));
+ pos++;
+ printf("Building extended header : cmd: %d, length: %d - %02X\n",
+ piece->command, piece->length, output[pos - 1]);
+ output.push_back(((piece->length - 1) & 0x00FF)); // (char)
+ pos++;
+ } else {
+ // We need to split the command
+ auto new_piece = SplitCompressionPiece(piece, mode);
+ if (!new_piece.ok()) {
+ std::cout << new_piece.status().ToString() << std::endl;
+ }
+ printf("New added piece\n");
+ auto piece_data = new_piece.value();
+ PrintCompressionPiece(piece_data);
+ piece_data->next = piece->next;
+ piece->next = piece_data;
+ continue;
+ }
+ }
+
+ if (piece->command == kCommandRepeatingBytes) {
+ char tmp[2];
+ tmp[0] = piece->argument[0];
+ tmp[1] = piece->argument[1];
+ if (mode == kNintendoMode1) {
+ tmp[0] = piece->argument[1];
+ tmp[1] = piece->argument[0];
+ }
+ for (const auto& each : tmp) {
+ output.push_back(each);
+ pos++;
+ }
+ } else {
+ for (int i = 0; i < piece->argument_length; ++i) {
+ output.push_back(piece->argument[i]);
+ pos++;
+ }
+ }
+ pos += piece->argument_length;
+ piece = piece->next;
+ }
+ output.push_back(kSnesByteMax);
+ return output;
+}
+
+absl::Status ValidateCompressionResult(
+ CompressionPiecePointer& compressed_chain_start, int mode, int start,
+ int src_data_pos) {
+ if (compressed_chain_start->next != nullptr) {
+ ROM temp_rom;
+ RETURN_IF_ERROR(temp_rom.LoadFromBytes(
+ CreateCompressionString(compressed_chain_start->next, mode)))
+ ASSIGN_OR_RETURN(auto decomp_data, temp_rom.Decompress(0, temp_rom.size()))
+ if (!std::equal(decomp_data.begin() + start, decomp_data.end(),
+ temp_rom.begin())) {
+ return absl::InternalError(absl::StrFormat(
+ "Compressed data does not match uncompressed data at %d\n",
+ (uint)(src_data_pos - start)));
+ }
+ }
+ return absl::OkStatus();
+}
+
+// Merge consecutive copy if possible
+CompressionPiecePointer MergeCopy(CompressionPiecePointer& start) {
+ CompressionPiecePointer piece = start;
+
+ while (piece != nullptr) {
+ if (piece->command == kCommandDirectCopy && piece->next != nullptr &&
+ piece->next->command == kCommandDirectCopy &&
+ piece->length + piece->next->length <= kMaxLengthCompression) {
+ uint previous_length = piece->length;
+ piece->length = piece->length + piece->next->length;
+
+ for (int i = 0; i < piece->next->argument_length; ++i) {
+ piece->argument[i + previous_length] = piece->next->argument[i];
+ }
+ piece->argument_length = piece->length;
+ PrintCompressionPiece(piece);
+
+ auto p_next_next = piece->next->next;
+ piece->next = p_next_next;
+ continue; // Next could be another copy
+ }
+ piece = piece->next;
+ }
+ return start;
+}
+
+} // namespace lc_lz2
+
+namespace {
+
+int GetGraphicsAddress(const uchar* data, uint8_t offset) {
+ auto part_one = data[kOverworldGraphicsPos1 + offset] << 16;
+ auto part_two = data[kOverworldGraphicsPos2 + offset] << 8;
+ auto part_three = data[kOverworldGraphicsPos3 + offset];
+ auto snes_addr = (part_one | part_two | part_three);
+ return core::SnesToPc(snes_addr);
+}
+
+Bytes SnesTo8bppSheet(Bytes sheet, int bpp) {
+ int xx = 0; // positions where we are at on the sheet
+ int yy = 0;
+ int pos = 0;
+ int ypos = 0;
+ int num_tiles = 64;
+ int buffer_size = 0x1000;
+ if (bpp == 2) {
+ bpp = 16;
+ num_tiles = 128;
+ buffer_size = 0x2000;
+ } else if (bpp == 3) {
+ bpp = 24;
+ }
+ Bytes sheet_buffer_out(buffer_size);
+
+ for (int i = 0; i < num_tiles; i++) { // for each tiles, 16 per line
+ for (int y = 0; y < 8; y++) { // for each line
+ for (int x = 0; x < 8; x++) { //[0] + [1] + [16]
+ auto b1 = (sheet[(y * 2) + (bpp * pos)] & (kGraphicsBitmap[x]));
+ auto b2 = (sheet[((y * 2) + (bpp * pos)) + 1] & (kGraphicsBitmap[x]));
+ auto b3 = (sheet[(16 + y) + (bpp * pos)] & (kGraphicsBitmap[x]));
+ unsigned char b = 0;
+ if (b1 != 0) {
+ b |= 1;
+ }
+ if (b2 != 0) {
+ b |= 2;
+ }
+ if (b3 != 0 && bpp != 16) {
+ b |= 4;
+ }
+ sheet_buffer_out[x + xx + (y * 128) + (yy * 1024)] = b;
+ }
+ }
+ pos++;
+ ypos++;
+ xx += 8;
+ if (ypos >= 16) {
+ yy++;
+ xx = 0;
+ ypos = 0;
+ }
+ }
+ return sheet_buffer_out;
+}
+
+} // namespace
+
+// TODO TEST compressed data border for each cmd
+absl::StatusOr ROM::Compress(const int start, const int length, int mode,
+ bool check) {
+ // Worse case should be a copy of the string with extended header
+ auto compressed_chain = std::make_shared(1, 1, "aaa", 2);
+ auto compressed_chain_start = compressed_chain;
+
+ CommandArgumentArray cmd_args = {{}};
+ DataSizeArray data_size_taken = {0, 0, 0, 0, 0};
+ CommandSizeArray cmd_size = {0, 1, 2, 1, 2};
+
+ uint src_data_pos = start;
+ uint last_pos = start + length - 1;
+ uint comp_accumulator = 0; // Used when skipping using copy
+
+ while (true) {
+ data_size_taken.fill({});
+ cmd_args.fill({{}});
+
+ lc_lz2::CheckByteRepeat(rom_data_.data(), data_size_taken, cmd_args,
+ src_data_pos, last_pos);
+ lc_lz2::CheckWordRepeat(rom_data_.data(), data_size_taken, cmd_args,
+ src_data_pos, last_pos);
+ lc_lz2::CheckIncByte(rom_data_.data(), data_size_taken, cmd_args,
+ src_data_pos, last_pos);
+ lc_lz2::CheckIntraCopy(rom_data_.data(), data_size_taken, cmd_args,
+ src_data_pos, last_pos, start);
+
+ uint max_win = 2;
+ uint cmd_with_max = kCommandDirectCopy;
+ lc_lz2::ValidateForByteGain(data_size_taken, cmd_size, max_win,
+ cmd_with_max);
+
+ if (cmd_with_max == kCommandDirectCopy) {
+ // This is the worst case scenario
+ // Progress through the next byte, in case there's a different
+ // compression command we can implement before we hit 32 bytes.
+ src_data_pos++;
+ comp_accumulator++;
+
+ // Arbitrary choice to do a 32 bytes grouping for copy.
+ if (comp_accumulator == 32 || src_data_pos > last_pos) {
+ std::string buffer;
+ for (int i = 0; i < comp_accumulator; ++i) {
+ buffer.push_back(rom_data_[i + src_data_pos - comp_accumulator]);
+ }
+ auto new_comp_piece = std::make_shared(
+ kCommandDirectCopy, comp_accumulator, buffer, comp_accumulator);
+ compressed_chain->next = new_comp_piece;
+ compressed_chain = new_comp_piece;
+ comp_accumulator = 0;
+ }
+ } else {
+ lc_lz2::CompressionCommandAlternative(
+ rom_data_.data(), compressed_chain, cmd_size, cmd_args, src_data_pos,
+ comp_accumulator, cmd_with_max, max_win);
+ }
+
+ if (src_data_pos > last_pos) {
+ printf("Breaking compression loop\n");
+ break;
+ }
+
+ if (check) {
+ RETURN_IF_ERROR(lc_lz2::ValidateCompressionResult(
+ compressed_chain_start, mode, start, src_data_pos))
+ }
+ }
+
+ // Skipping compression chain header
+ lc_lz2::MergeCopy(compressed_chain_start->next);
+ lc_lz2::PrintCompressionChain(compressed_chain_start);
+ return lc_lz2::CreateCompressionString(compressed_chain_start->next, mode);
+}
+
+absl::StatusOr ROM::CompressGraphics(const int pos, const int length) {
+ return Compress(pos, length, kNintendoMode2);
+}
+
+absl::StatusOr ROM::CompressOverworld(const int pos, const int length) {
+ return Compress(pos, length, kNintendoMode1);
+}
+
+absl::StatusOr ROM::Decompress(int offset, int size, int mode) {
+ Bytes buffer(size, 0);
+ uint length = 0;
+ uint buffer_pos = 0;
+ uchar command = 0;
+ uchar header = rom_data_[offset];
+
+ while (header != kSnesByteMax) {
+ if ((header & kExpandedMod) == kExpandedMod) {
+ // Expanded Command
+ command = ((header >> 2) & kCommandMod);
+ length = (((header << 8) | rom_data_[offset + 1]) & kExpandedLengthMod);
+ offset += 2; // Advance 2 bytes in ROM
+ } else {
+ // Normal Command
+ command = ((header >> 5) & kCommandMod);
+ length = (header & kNormalLengthMod);
+ offset += 1; // Advance 1 byte in ROM
+ }
+ length += 1; // each commands is at least of size 1 even if index 00
+
+ switch (command) {
+ case kCommandDirectCopy: // Does not advance in the ROM
+ memcpy(buffer.data() + buffer_pos, rom_data_.data() + offset, length);
+ buffer_pos += length;
+ offset += length;
+ break;
+ case kCommandByteFill:
+ memset(buffer.data() + buffer_pos, (int)(rom_data_[offset]), length);
+ buffer_pos += length;
+ offset += 1; // Advances 1 byte in the ROM
+ break;
+ case kCommandWordFill: {
+ auto a = rom_data_[offset];
+ auto b = rom_data_[offset + 1];
+ for (int i = 0; i < length; i = i + 2) {
+ buffer[buffer_pos + i] = a;
+ if ((i + 1) < length) buffer[buffer_pos + i + 1] = b;
+ }
+ buffer_pos += length;
+ offset += 2; // Advance 2 byte in the ROM
+ } break;
+ case kCommandIncreasingFill: {
+ auto inc_byte = rom_data_[offset];
+ for (int i = 0; i < length; i++) {
+ buffer[buffer_pos] = inc_byte++;
+ buffer_pos++;
+ }
+ offset += 1; // Advance 1 byte in the ROM
+ } break;
+ case kCommandRepeatingBytes: {
+ ushort s1 = ((rom_data_[offset + 1] & kSnesByteMax) << 8);
+ ushort s2 = ((rom_data_[offset] & kSnesByteMax));
+ int addr = (s1 | s2);
+ if (mode == kNintendoMode1) { // Reversed byte order for overworld maps
+ addr = (rom_data_[offset + 1] & kSnesByteMax) |
+ ((rom_data_[offset] & kSnesByteMax) << 8);
+ }
+ if (addr > offset) {
+ return absl::InternalError(absl::StrFormat(
+ "Decompress: Offset for command copy exceeds current position "
+ "(Offset : %#04x | Pos : %#06x)\n",
+ addr, offset));
+ }
+ if (buffer_pos + length >= size) {
+ size *= 2;
+ buffer.resize(size);
+ }
+ memcpy(buffer.data() + buffer_pos, buffer.data() + addr, length);
+ buffer_pos += length;
+ offset += 2;
+ } break;
+ default: {
+ std::cout << absl::StrFormat(
+ "Decompress: Invalid header (Offset : %#06x, Command: %#04x)\n",
+ offset, command);
+ } break;
+ }
+ // check next byte
+ header = rom_data_[offset];
+ }
+
+ return buffer;
+}
+
+absl::StatusOr ROM::DecompressGraphics(int pos, int size) {
+ return Decompress(pos, size, kNintendoMode2);
+}
+
+absl::StatusOr ROM::DecompressOverworld(int pos, int size) {
+ return Decompress(pos, size, kNintendoMode1);
+}
+
+absl::StatusOr ROM::Load2bppGraphics() {
+ Bytes sheet;
+ const uint8_t sheets[] = {113, 114, 218, 219, 220, 221};
+
+ for (const auto& sheet_id : sheets) {
+ auto offset = GetGraphicsAddress(rom_data_.data(), sheet_id);
+ ASSIGN_OR_RETURN(auto decomp_sheet, Decompress(offset))
+ auto converted_sheet = SnesTo8bppSheet(decomp_sheet, 2);
+ for (const auto& each_pixel : converted_sheet) {
+ sheet.push_back(each_pixel);
+ }
+ }
+ return sheet;
+}
+
+// 0-112 -> compressed 3bpp bgr -> (decompressed each) 0x600 chars
+// 113-114 -> compressed 2bpp -> (decompressed each) 0x800 chars
+// 115-126 -> uncompressed 3bpp sprites -> (each) 0x600 chars
+// 127-217 -> compressed 3bpp sprites -> (decompressed each) 0x600 chars
+// 218-222 -> compressed 2bpp -> (decompressed each) 0x800 chars
+absl::Status ROM::LoadAllGraphicsData() {
+ Bytes sheet;
+ bool bpp3 = false;
+
+ for (int i = 0; i < core::NumberOfSheets; i++) {
+ if (i >= 115 && i <= 126) { // uncompressed sheets
+ sheet.resize(core::Uncompressed3BPPSize);
+ auto offset = GetGraphicsAddress(rom_data_.data(), i);
+ for (int j = 0; j < core::Uncompressed3BPPSize; j++) {
+ sheet[j] = rom_data_[j + offset];
+ }
+ bpp3 = true;
+ } else if (i == 113 || i == 114 || i >= 218) {
+ bpp3 = false;
+ } else {
+ auto offset = GetGraphicsAddress(rom_data_.data(), i);
+ ASSIGN_OR_RETURN(sheet, Decompress(offset))
+ bpp3 = true;
+ }
+
+ if (bpp3) {
+ auto converted_sheet = SnesTo8bppSheet(sheet, 3);
+ graphics_bin_[i] =
+ gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
+ core::kTilesheetDepth, converted_sheet.data(), 0x1000);
+ graphics_bin_.at(i).CreateTexture(renderer_);
+
+ for (int j = 0; j < graphics_bin_.at(i).GetSize(); ++j) {
+ graphics_buffer_.push_back(graphics_bin_.at(i).GetByte(j));
+ }
+ } else {
+ for (int j = 0; j < graphics_bin_.at(0).GetSize(); ++j) {
+ graphics_buffer_.push_back(0xFF);
+ }
+ }
+ }
+ return absl::OkStatus();
+}
+
+absl::Status ROM::LoadFromFile(const absl::string_view& filename) {
+ filename_ = filename;
+ std::ifstream file(filename.data(), std::ios::binary);
+ if (!file.is_open()) {
+ return absl::InternalError(
+ absl::StrCat("Could not open ROM file: ", filename));
+ }
+
+ size_ = std::filesystem::file_size(filename);
+ rom_data_.resize(size_);
+ for (auto i = 0; i < size_; ++i) {
+ char byte_to_read = ' ';
+ file.read(&byte_to_read, sizeof(char));
+ rom_data_[i] = byte_to_read;
+ }
+
+ // copy ROM title
+ memcpy(title, rom_data_.data() + kTitleStringOffset, kTitleStringLength);
+
+ file.close();
+ LoadAllPalettes();
+ is_loaded_ = true;
+ return absl::OkStatus();
+}
+
+absl::Status ROM::LoadFromPointer(uchar* data, size_t length) {
+ if (!data)
+ return absl::InvalidArgumentError(
+ "Could not load ROM: parameter `data` is empty.");
+
+ for (int i = 0; i < length; ++i) rom_data_.push_back(data[i]);
+
+ return absl::OkStatus();
+}
+
+absl::Status ROM::LoadFromBytes(const Bytes& data) {
+ if (data.empty()) {
+ return absl::InvalidArgumentError(
+ "Could not load ROM: parameter `data` is empty.");
+ }
+ rom_data_ = data;
+ return absl::OkStatus();
+}
+
+absl::Status ROM::SaveToFile() {
+ std::fstream file(filename_.data(), std::ios::binary | std::ios::out);
+ if (!file.is_open()) {
+ return absl::InternalError(
+ absl::StrCat("Could not open ROM file: ", filename_));
+ }
+ for (auto i = 0; i < size_; ++i) {
+ file << rom_data_[i];
+ }
+ return absl::OkStatus();
+}
+
+void ROM::RenderBitmap(gfx::Bitmap* bitmap) const {
+ bitmap->CreateTexture(renderer_);
+}
+
+gfx::SNESColor ROM::ReadColor(int offset) {
+ short color = toint16(offset);
+ gfx::snes_color new_color;
+ new_color.red = (color & 0x1F) * 8;
+ new_color.green = ((color >> 5) & 0x1F) * 8;
+ new_color.blue = ((color >> 10) & 0x1F) * 8;
+ gfx::SNESColor snes_color(new_color);
+ return snes_color;
+}
+
+gfx::SNESPalette ROM::ReadPalette(int offset, int num_colors) {
+ int color_offset = 0;
+ std::vector colors(num_colors);
+
+ while (color_offset < num_colors) {
+ short color = toint16(offset);
+ gfx::snes_color new_color;
+ new_color.red = (color & 0x1F) * 8;
+ new_color.green = ((color >> 5) & 0x1F) * 8;
+ new_color.blue = ((color >> 10) & 0x1F) * 8;
+ colors[color_offset].setSNES(new_color);
+ color_offset++;
+ offset += 2;
+ }
+
+ gfx::SNESPalette palette(colors);
+ return palette;
+}
+
+void ROM::LoadAllPalettes() {
+ // 35 colors each, 7x5 (0,2 on grid)
+ for (int i = 0; i < 6; i++) {
+ palette_groups_["ow_main"].AddPalette(
+ ReadPalette(core::overworldPaletteMain + (i * (35 * 2)), 35));
+ }
+ // 21 colors each, 7x3 (8,2 and 8,5 on grid)
+ for (int i = 0; i < 20; i++) {
+ palette_groups_["ow_aux"].AddPalette(
+ ReadPalette(core::overworldPaletteAuxialiary + (i * (21 * 2)), 21));
+ }
+ // 7 colors each 7x1 (0,7 on grid)
+ for (int i = 0; i < 14; i++) {
+ palette_groups_["ow_animated"].AddPalette(
+ ReadPalette(core::overworldPaletteAnimated + (i * (7 * 2)), 7));
+ }
+ // 32 colors each 16x2 (0,0 on grid)
+ for (int i = 0; i < 2; i++) {
+ palette_groups_["hud"].AddPalette(
+ ReadPalette(core::hudPalettes + (i * 64), 32));
+ }
+
+ palette_groups_["global_sprites"].AddPalette(
+ ReadPalette(core::globalSpritePalettesLW, 60));
+ palette_groups_["global_sprites"].AddPalette(
+ ReadPalette(core::globalSpritePalettesDW, 60));
+
+ for (int i = 0; i < 5; i++) {
+ palette_groups_["armors"].AddPalette(
+ ReadPalette(core::armorPalettes + (i * 30), 15));
+ }
+ for (int i = 0; i < 4; i++) {
+ palette_groups_["swords"].AddPalette(
+ ReadPalette(core::swordPalettes + (i * 6), 3));
+ }
+ for (int i = 0; i < 3; i++) {
+ palette_groups_["shields"].AddPalette(
+ ReadPalette(core::shieldPalettes + (i * 8), 4));
+ }
+ for (int i = 0; i < 12; i++) {
+ palette_groups_["sprites_aux1"].AddPalette(
+ ReadPalette(core::spritePalettesAux1 + (i * 14), 7));
+ }
+ for (int i = 0; i < 11; i++) {
+ palette_groups_["sprites_aux2"].AddPalette(
+ ReadPalette(core::spritePalettesAux2 + (i * 14), 7));
+ }
+ for (int i = 0; i < 24; i++) {
+ palette_groups_["sprites_aux3"].AddPalette(
+ ReadPalette(core::spritePalettesAux3 + (i * 14), 7));
+ }
+ for (int i = 0; i < 20; i++) {
+ palette_groups_["dungeon_main"].AddPalette(
+ ReadPalette(core::dungeonMainPalettes + (i * 180), 90));
+ }
+
+ palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassLW));
+ palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassDW));
+ palette_groups_["grass"].AddColor(ReadColor(core::hardcodedGrassSpecial));
+
+ palette_groups_["3d_object"].AddPalette(
+ ReadPalette(core::triforcePalette, 8));
+ palette_groups_["3d_object"].AddPalette(ReadPalette(core::crystalPalette, 8));
+
+ for (int i = 0; i < 2; i++) {
+ palette_groups_["ow_mini_map"].AddPalette(
+ ReadPalette(core::overworldMiniMapPalettes + (i * 256), 128));
+ }
+}
+
+absl::Status ROM::ApplyAssembly(const absl::string_view& filename,
+ size_t patch_size) {
+ int count = 0;
+ auto patch = filename.data();
+ auto data = (char*)rom_data_.data();
+ if (int size = size_; !asar_patch(patch, data, patch_size, &size)) {
+ auto asar_error = asar_geterrors(&count);
+ auto full_error = asar_error->fullerrdata;
+ return absl::InternalError(absl::StrCat("ASAR Error: ", full_error));
+ }
+ return absl::OkStatus();
+}
+
+// TODO(scawful): Test me!
+absl::Status ROM::PatchOverworldMosaic(
+ char mosaic_tiles[core::kNumOverworldMaps], int routine_offset) {
+ // Write the data for the mosaic tile array used by the assembly code.
+ for (int i = 0; i < core::kNumOverworldMaps; i++) {
+ if (mosaic_tiles[i]) {
+ rom_data_[core::overworldCustomMosaicArray + i] = 0x01;
+ } else {
+ rom_data_[core::overworldCustomMosaicArray + i] = 0x00;
+ }
+ }
+
+ std::string filename = "assets/asm/mosaic_change.asm";
+ std::fstream file(filename, std::ios::out | std::ios::in);
+ if (!file.is_open()) {
+ return absl::InvalidArgumentError(
+ "Unable to open mosaic change assembly source");
+ }
+
+ std::stringstream assembly;
+ assembly << file.rdbuf();
+ file.close();
+ auto assembly_string = assembly.str();
+
+ if (!core::StringReplace(assembly_string, "", kMosaicChangeOffset)) {
+ return absl::InternalError(
+ "Mosaic template did not have proper `` to replace.");
+ }
+
+ if (!core::StringReplace(
+ assembly_string, "",
+ absl::StrFormat("$%x", routine_offset + kSNESToPCOffset))) {
+ return absl::InternalError(
+ "Mosaic template did not have proper `` to replace.");
+ }
+
+ return ApplyAssembly(filename, assembly_string.size());
+}
+
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/rom.h b/src/app/rom.h
new file mode 100644
index 00000000..5e2a59b6
--- /dev/null
+++ b/src/app/rom.h
@@ -0,0 +1,157 @@
+#ifndef YAZE_APP_ROM_H
+#define YAZE_APP_ROM_H
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "app/core/common.h"
+#include "app/core/constants.h"
+#include "app/gfx/bitmap.h"
+
+#define BUILD_HEADER(command, length) (command << 5) + (length - 1)
+
+namespace yaze {
+namespace app {
+
+constexpr int kCommandDirectCopy = 0;
+constexpr int kCommandByteFill = 1;
+constexpr int kCommandWordFill = 2;
+constexpr int kCommandIncreasingFill = 3;
+constexpr int kCommandRepeatingBytes = 4;
+constexpr int kCommandLongLength = 7;
+constexpr int kMaxLengthNormalHeader = 32;
+constexpr int kMaxLengthCompression = 1024;
+constexpr int kNintendoMode1 = 0;
+constexpr int kNintendoMode2 = 1;
+constexpr int kTile32Num = 4432;
+constexpr int kTitleStringOffset = 0x7FC0;
+constexpr int kTitleStringLength = 20;
+constexpr int kOverworldGraphicsPos1 = 0x4F80;
+constexpr int kOverworldGraphicsPos2 = 0x505F;
+constexpr int kOverworldGraphicsPos3 = 0x513E;
+constexpr int kSnesByteMax = 0xFF;
+constexpr int kCommandMod = 0x07;
+constexpr int kExpandedMod = 0xE0;
+constexpr int kExpandedLengthMod = 0x3FF;
+constexpr int kNormalLengthMod = 0x1F;
+constexpr int kCompressionStringMod = 7 << 5;
+constexpr uchar kGraphicsBitmap[8] = {0x80, 0x40, 0x20, 0x10,
+ 0x08, 0x04, 0x02, 0x01};
+
+const std::string kMosaicChangeOffset = "$02AADB";
+constexpr int kSNESToPCOffset = 0x138000;
+
+using CommandArgumentArray = std::array, 5>;
+using CommandSizeArray = std::array;
+using DataSizeArray = std::array;
+struct CompressionPiece {
+ char command;
+ int length;
+ int argument_length;
+ std::string argument;
+ std::shared_ptr next = nullptr;
+ CompressionPiece() = default;
+ CompressionPiece(int cmd, int len, std::string args, int arg_len)
+ : command(cmd), length(len), argument_length(arg_len), argument(args) {}
+};
+using CompressionPiece = struct CompressionPiece;
+using CompressionPiecePointer = std::shared_ptr;
+
+class ROM {
+ public:
+ absl::StatusOr Compress(const int start, const int length,
+ int mode = 1, bool check = false);
+ absl::StatusOr CompressGraphics(const int pos, const int length);
+ absl::StatusOr CompressOverworld(const int pos, const int length);
+
+ absl::StatusOr Decompress(int offset, int size = 0x800, int mode = 1);
+ absl::StatusOr DecompressGraphics(int pos, int size);
+ absl::StatusOr DecompressOverworld(int pos, int size);
+
+ absl::StatusOr Load2bppGraphics();
+
+ absl::Status LoadAllGraphicsData();
+ absl::Status LoadFromFile(const absl::string_view& filename);
+ absl::Status LoadFromPointer(uchar* data, size_t length);
+ absl::Status LoadFromBytes(const Bytes& data);
+ void LoadAllPalettes();
+
+ absl::Status SaveToFile();
+
+ gfx::SNESColor ReadColor(int offset);
+ gfx::SNESPalette ReadPalette(int offset, int num_colors);
+
+ void RenderBitmap(gfx::Bitmap* bitmap) const;
+
+ absl::Status ApplyAssembly(const absl::string_view& filename,
+ size_t patch_size);
+ absl::Status PatchOverworldMosaic(char mosaic_tiles[core::kNumOverworldMaps],
+ int routine_offset);
+
+ auto GetTitle() const { return title; }
+ auto GetGraphicsBin() const { return graphics_bin_; }
+ auto GetGraphicsBuffer() const { return graphics_buffer_; }
+ auto GetPaletteGroup(std::string group) { return palette_groups_[group]; }
+ void SetupRenderer(std::shared_ptr renderer) {
+ renderer_ = renderer;
+ }
+ auto isLoaded() const { return is_loaded_; }
+ auto begin() { return rom_data_.begin(); }
+ auto end() { return rom_data_.end(); }
+ auto data() { return rom_data_.data(); }
+ auto char_data() { return reinterpret_cast(rom_data_.data()); }
+ auto size() const { return size_; }
+
+ uchar& operator[](int i) {
+ if (i > size_) {
+ std::cout << "ROM: Index out of bounds" << std::endl;
+ return rom_data_[0];
+ }
+ return rom_data_[i];
+ }
+ uchar& operator+(int i) {
+ if (i > size_) {
+ std::cout << "ROM: Index out of bounds" << std::endl;
+ return rom_data_[0];
+ }
+ return rom_data_[i];
+ }
+ const uchar* operator&() { return rom_data_.data(); }
+
+ ushort toint16(int offset) {
+ return (ushort)((rom_data_[offset + 1]) << 8) | rom_data_[offset];
+ }
+
+ private:
+ long size_ = 0;
+ uchar title[21] = "ROM Not Loaded";
+ bool is_loaded_ = false;
+ bool isbpp3[223];
+ std::string filename_;
+
+ Bytes rom_data_;
+ Bytes graphics_buffer_;
+ std::shared_ptr renderer_;
+ std::unordered_map graphics_bin_;
+ std::unordered_map palette_groups_;
+};
+
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/spc700/spc700.def b/src/app/spc700/spc700.def
new file mode 100644
index 00000000..4781c5ef
--- /dev/null
+++ b/src/app/spc700/spc700.def
@@ -0,0 +1,26 @@
+LIBRARY snes_spc
+DESCRIPTION "snes_spc 0.9.0"
+EXPORTS
+ spc_new @1
+ spc_delete @2
+ spc_init_rom @3
+ spc_set_output @4
+ spc_sample_count @5
+ spc_reset @6
+ spc_soft_reset @7
+ spc_read_port @8
+ spc_write_port @9
+ spc_end_frame @10
+ spc_mute_voices @11
+ spc_disable_surround @12
+ spc_set_tempo @13
+ spc_load_spc @14
+ spc_clear_echo @15
+ spc_play @16
+ spc_skip @17
+ spc_filter_new @18
+ spc_filter_delete @19
+ spc_filter_run @20
+ spc_filter_clear @21
+ spc_filter_set_gain @22
+ spc_filter_set_bass @23
\ No newline at end of file
diff --git a/src/app/yaze.cc b/src/app/yaze.cc
new file mode 100644
index 00000000..a6b7e20b
--- /dev/null
+++ b/src/app/yaze.cc
@@ -0,0 +1,31 @@
+#if defined(_WIN32)
+#define main SDL_main
+#endif
+
+#include "absl/debugging/failure_signal_handler.h"
+#include "absl/debugging/symbolize.h"
+#include "app/core/controller.h"
+
+int main(int argc, char** argv) {
+ absl::InitializeSymbolizer(argv[0]);
+
+ absl::FailureSignalHandlerOptions options;
+ absl::InstallFailureSignalHandler(options);
+
+ yaze::app::core::Controller controller;
+
+ auto entry_status = controller.onEntry();
+ if (!entry_status.ok()) {
+ // TODO(@scawful): log the specific error
+ return EXIT_FAILURE;
+ }
+
+ while (controller.isActive()) {
+ controller.onInput();
+ controller.onLoad();
+ controller.doRender();
+ }
+ controller.onExit();
+
+ return EXIT_SUCCESS;
+}
\ No newline at end of file
diff --git a/src/app/zelda3/inventory.cc b/src/app/zelda3/inventory.cc
new file mode 100644
index 00000000..89198cc0
--- /dev/null
+++ b/src/app/zelda3/inventory.cc
@@ -0,0 +1,89 @@
+#include "inventory.h"
+
+#include "app/gfx/bitmap.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "gui/canvas.h"
+
+namespace yaze {
+namespace app {
+namespace zelda3 {
+
+void Inventory::Create() {
+ data_.reserve(256 * 256);
+ for (int i = 0; i < 256 * 256; i++) {
+ data_.push_back(0xFF);
+ }
+ PRINT_IF_ERROR(BuildTileset())
+ for (int i = 0; i < 0x500; i += 0x08) {
+ tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos)));
+ tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos + 0x02)));
+ tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos + 0x04)));
+ tiles_.push_back(gfx::GetTilesInfo(rom_.toint16(i + kBowItemPos + 0x08)));
+ }
+ const int offsets[] = {0x00, 0x08, 0x800, 0x808};
+ auto xx = 0;
+ auto yy = 0;
+
+ int i = 0;
+ for (const auto& tile : tiles_) {
+ int offset = offsets[i];
+ for (auto y = 0; y < 0x08; ++y) {
+ for (auto x = 0; x < 0x08; ++x) {
+ int mx = x;
+ int my = y;
+
+ if (tile.horizontal_mirror_ != 0) {
+ mx = 0x07 - x;
+ }
+
+ if (tile.vertical_mirror_ != 0) {
+ my = 0x07 - y;
+ }
+
+ int xpos = ((tile.id_ % 0x10) * 0x08);
+ int ypos = (((tile.id_ / 0x10)) * 0x400);
+ int source = ypos + xpos + (x + (y * 0x80));
+
+ auto destination = xx + yy + offset + (mx + (my * 0x100));
+ data_[destination] = (test_[source] & 0x0F) + tile.palette_ * 0x08;
+ }
+ }
+
+ if (i == 4) {
+ i = 0;
+ xx += 0x10;
+ if (xx >= 0x100) {
+ yy += 0x1000;
+ xx = 0;
+ }
+ } else {
+ i++;
+ }
+ }
+ bitmap_.Create(256, 256, 128, data_);
+ bitmap_.ApplyPalette(palette_);
+ rom_.RenderBitmap(&bitmap_);
+}
+
+absl::Status Inventory::BuildTileset() {
+ tilesheets_.reserve(6 * 0x2000);
+ for (int i = 0; i < 6 * 0x2000; i++) tilesheets_.push_back(0xFF);
+ ASSIGN_OR_RETURN(tilesheets_, rom_.Load2bppGraphics())
+ Bytes test;
+ for (int i = 0; i < 0x4000; i++) {
+ test_.push_back(tilesheets_[i]);
+ }
+ for (int i = 0x8000; i < +0x8000 + 0x2000; i++) {
+ test_.push_back(tilesheets_[i]);
+ }
+ tilesheets_bmp_.Create(128, 0x130, 64, test_);
+ palette_ = rom_.GetPaletteGroup("hud")[0];
+ tilesheets_bmp_.ApplyPalette(palette_);
+ rom_.RenderBitmap(&tilesheets_bmp_);
+ return absl::OkStatus();
+}
+
+} // namespace zelda3
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/zelda3/inventory.h b/src/app/zelda3/inventory.h
new file mode 100644
index 00000000..4b714f8a
--- /dev/null
+++ b/src/app/zelda3/inventory.h
@@ -0,0 +1,47 @@
+#ifndef YAZE_APP_ZELDA3_INVENTORY_H
+#define YAZE_APP_ZELDA3_INVENTORY_H
+
+#include "app/gfx/bitmap.h"
+#include "app/gfx/snes_tile.h"
+#include "app/gfx/snes_palette.h"
+#include "app/rom.h"
+#include "gui/canvas.h"
+
+namespace yaze {
+namespace app {
+namespace zelda3 {
+
+constexpr int kInventoryStart = 0x6564A;
+constexpr int kBowItemPos = 0x6F631;
+
+class Inventory {
+ public:
+ void SetupROM(ROM& rom) { rom_ = rom; }
+ auto Bitmap() const { return bitmap_; }
+ auto Tilesheet() const { return tilesheets_bmp_; }
+ auto Palette() const { return palette_; }
+
+ void Create();
+
+ private:
+ absl::Status BuildTileset();
+
+ ROM rom_;
+
+ Bytes data_;
+ gfx::Bitmap bitmap_;
+
+ Bytes tilesheets_;
+ Bytes test_;
+ gfx::Bitmap tilesheets_bmp_;
+ gfx::SNESPalette palette_;
+
+ gui::Canvas canvas_;
+ std::vector tiles_;
+};
+
+} // namespace zelda3
+} // namespace app
+} // namespace yaze
+
+#endif
\ No newline at end of file
diff --git a/src/app/zelda3/overworld.cc b/src/app/zelda3/overworld.cc
new file mode 100644
index 00000000..352e14c2
--- /dev/null
+++ b/src/app/zelda3/overworld.cc
@@ -0,0 +1,384 @@
+#include "overworld.h"
+
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+
+namespace yaze {
+namespace app {
+namespace zelda3 {
+
+namespace {
+
+uint GetOwMapGfxHighPtr(const uchar *rom, int index) {
+ int map_high_ptr = core::compressedAllMap32PointersHigh;
+ int p1 = (rom[(map_high_ptr) + 2 + (3 * index)] << 16) +
+ (rom[(map_high_ptr) + 1 + (3 * index)] << 8) +
+ (rom[(map_high_ptr + (3 * index))]);
+ return core::SnesToPc(p1);
+}
+
+uint GetOwMapGfxLowPtr(const uchar *rom, int index) {
+ int map_low_ptr = core::compressedAllMap32PointersLow;
+ int p2 = (rom[(map_low_ptr) + 2 + (3 * index)] << 16) +
+ (rom[(map_low_ptr) + 1 + (3 * index)] << 8) +
+ (rom[(map_low_ptr + (3 * index))]);
+ return core::SnesToPc(p2);
+}
+
+} // namespace
+
+absl::Status Overworld::Load(ROM &rom) {
+ rom_ = rom;
+
+ AssembleMap32Tiles();
+ AssembleMap16Tiles();
+ RETURN_IF_ERROR(DecompressAllMapTiles())
+
+ for (int map_index = 0; map_index < core::kNumOverworldMaps; ++map_index)
+ overworld_maps_.emplace_back(map_index, rom_, tiles16);
+
+ FetchLargeMaps();
+ LoadEntrances();
+ LoadSprites();
+
+ auto size = tiles16.size();
+ for (int i = 0; i < core::kNumOverworldMaps; ++i) {
+ if (i < 64) {
+ RETURN_IF_ERROR(overworld_maps_[i].BuildMap(
+ size, game_state_, 0, map_parent_, map_tiles_.light_world))
+ } else if (i < 0x80 && i >= 0x40) {
+ RETURN_IF_ERROR(overworld_maps_[i].BuildMap(
+ size, game_state_, 1, map_parent_, map_tiles_.dark_world))
+ } else {
+ RETURN_IF_ERROR(overworld_maps_[i].BuildMap(
+ size, game_state_, 2, map_parent_, map_tiles_.special_world))
+ }
+ }
+
+ is_loaded_ = true;
+ return absl::OkStatus();
+}
+
+ushort Overworld::GenerateTile32(int i, int k, int dimension) {
+ return (ushort)(rom_[map32address[dimension] + k + (i)] +
+ (((rom_[map32address[dimension] + (i) + (k <= 1 ? 4 : 5)] >>
+ (k % 2 == 0 ? 4 : 0)) &
+ 0x0F) *
+ 256));
+}
+
+void Overworld::AssembleMap32Tiles() {
+ for (int i = 0; i < 0x33F0; i += 6) {
+ for (int k = 0; k < 4; k++) {
+ tiles32.push_back(gfx::Tile32(
+ /*top-left=*/GenerateTile32(i, k, (int)Dimension::map32TilesTL),
+ /*top-right=*/GenerateTile32(i, k, (int)Dimension::map32TilesTR),
+ /*bottom-left=*/GenerateTile32(i, k, (int)Dimension::map32TilesBL),
+ /*bottom-right=*/GenerateTile32(i, k, (int)Dimension::map32TilesBR)));
+ }
+ }
+ map_tiles_.light_world.resize(kTile32Num);
+ map_tiles_.dark_world.resize(kTile32Num);
+ map_tiles_.special_world.resize(kTile32Num);
+ for (int i = 0; i < kTile32Num; i++) {
+ map_tiles_.light_world[i].resize(kTile32Num);
+ map_tiles_.dark_world[i].resize(kTile32Num);
+ map_tiles_.special_world[i].resize(kTile32Num);
+ }
+}
+
+void Overworld::AssembleMap16Tiles() {
+ int tpos = core::map16Tiles;
+ for (int i = 0; i < 4096; i += 1) {
+ auto t0 = gfx::GetTilesInfo((rom_.toint16(tpos)));
+ tpos += 2;
+ auto t1 = gfx::GetTilesInfo((rom_.toint16(tpos)));
+ tpos += 2;
+ auto t2 = gfx::GetTilesInfo((rom_.toint16(tpos)));
+ tpos += 2;
+ auto t3 = gfx::GetTilesInfo((rom_.toint16(tpos)));
+ tpos += 2;
+ tiles16.emplace_back(t0, t1, t2, t3);
+ }
+}
+
+void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
+ OWBlockset &world) {
+ int position_x1 = (x * 2) + (sx * 32);
+ int position_y1 = (y * 2) + (sy * 32);
+ int position_x2 = (x * 2) + 1 + (sx * 32);
+ int position_y2 = (y * 2) + 1 + (sy * 32);
+ world[position_x1][position_y1] = tiles32[tpos].tile0_;
+ world[position_x2][position_y1] = tiles32[tpos].tile1_;
+ world[position_x1][position_y2] = tiles32[tpos].tile2_;
+ world[position_x2][position_y2] = tiles32[tpos].tile3_;
+}
+
+void Overworld::OrganizeMapTiles(Bytes &bytes, Bytes &bytes2, int i, int sx,
+ int sy, int &ttpos) {
+ for (int y = 0; y < 16; y++) {
+ for (int x = 0; x < 16; x++) {
+ auto tidD = (ushort)((bytes2[ttpos] << 8) + bytes[ttpos]);
+ int tpos = tidD;
+ if (tpos < tiles32.size()) {
+ if (i < 64) {
+ AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world);
+ } else if (i < 128 && i >= 64) {
+ AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world);
+ } else {
+ AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world);
+ }
+ }
+ ttpos += 1;
+ }
+ }
+}
+
+absl::Status Overworld::DecompressAllMapTiles() {
+ int lowest = 0x0FFFFF;
+ int highest = 0x0F8000;
+ int sx = 0;
+ int sy = 0;
+ int c = 0;
+ for (int i = 0; i < 160; i++) {
+ auto p1 = GetOwMapGfxHighPtr(rom_.data(), i);
+ auto p2 = GetOwMapGfxLowPtr(rom_.data(), i);
+ int ttpos = 0;
+
+ if (p1 >= highest) {
+ highest = p1;
+ }
+ if (p2 >= highest) {
+ highest = p2;
+ }
+
+ if (p1 <= lowest && p1 > 0x0F8000) {
+ lowest = p1;
+ }
+ if (p2 <= lowest && p2 > 0x0F8000) {
+ lowest = p2;
+ }
+
+ ASSIGN_OR_RETURN(auto bytes, rom_.DecompressOverworld(p2, 1000))
+ ASSIGN_OR_RETURN(auto bytes2, rom_.DecompressOverworld(p1, 1000))
+ OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos);
+
+ sx++;
+ if (sx >= 8) {
+ sy++;
+ sx = 0;
+ }
+
+ c++;
+ if (c >= 64) {
+ sx = 0;
+ sy = 0;
+ c = 0;
+ }
+ }
+
+ std::cout << "MapPointers(lowest) : " << lowest << std::endl;
+ std::cout << "MapPointers(highest) : " << highest << std::endl;
+ return absl::OkStatus();
+}
+
+void Overworld::FetchLargeMaps() {
+ for (int i = 128; i < 145; i++) {
+ map_parent_[i] = 0;
+ }
+
+ map_parent_[128] = 128;
+ map_parent_[129] = 129;
+ map_parent_[130] = 129;
+ map_parent_[137] = 129;
+ map_parent_[138] = 129;
+ map_parent_[136] = 136;
+ overworld_maps_[136].SetLargeMap(false);
+
+ bool mapChecked[64];
+ for (int i = 0; i < 64; i++) {
+ mapChecked[i] = false;
+ }
+ int xx = 0;
+ int yy = 0;
+ while (true) {
+ int i = xx + (yy * 8);
+ if (mapChecked[i] == false) {
+ if (overworld_maps_[i].IsLargeMap() == true) {
+ mapChecked[i] = true;
+ map_parent_[i] = (uchar)i;
+ map_parent_[i + 64] = (uchar)(i + 64);
+
+ mapChecked[i + 1] = true;
+ map_parent_[i + 1] = (uchar)i;
+ map_parent_[i + 65] = (uchar)(i + 64);
+
+ mapChecked[i + 8] = true;
+ map_parent_[i + 8] = (uchar)i;
+ map_parent_[i + 72] = (uchar)(i + 64);
+
+ mapChecked[i + 9] = true;
+ map_parent_[i + 9] = (uchar)i;
+ map_parent_[i + 73] = (uchar)(i + 64);
+ xx++;
+ } else {
+ map_parent_[i] = (uchar)i;
+ map_parent_[i + 64] = (uchar)(i + 64);
+ mapChecked[i] = true;
+ }
+ }
+
+ xx++;
+ if (xx >= 8) {
+ xx = 0;
+ yy += 1;
+ if (yy >= 8) {
+ break;
+ }
+ }
+ }
+}
+
+void Overworld::LoadEntrances() {
+ for (int i = 0; i < 129; i++) {
+ short mapId = rom_.toint16(core::OWEntranceMap + (i * 2));
+ ushort mapPos = rom_.toint16(core::OWEntrancePos + (i * 2));
+ uchar entranceId = (rom_[core::OWEntranceEntranceId + i]);
+ int p = mapPos >> 1;
+ int x = (p % 64);
+ int y = (p >> 6);
+ bool deleted = false;
+ if (mapPos == 0xFFFF) {
+ deleted = true;
+ }
+ all_entrances_.emplace_back(
+ (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512),
+ (y * 16) + (((mapId % 64) / 8) * 512), entranceId, mapId, mapPos,
+ deleted);
+ }
+
+ for (int i = 0; i < 0x13; i++) {
+ auto mapId = (short)((rom_[core::OWHoleArea + (i * 2) + 1] << 8) +
+ (rom_[core::OWHoleArea + (i * 2)]));
+ auto mapPos = (short)((rom_[core::OWHolePos + (i * 2) + 1] << 8) +
+ (rom_[core::OWHolePos + (i * 2)]));
+ uchar entranceId = (rom_[core::OWHoleEntrance + i]);
+ int p = (mapPos + 0x400) >> 1;
+ int x = (p % 64);
+ int y = (p >> 6);
+ all_holes_.emplace_back(
+ (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512),
+ (y * 16) + (((mapId % 64) / 8) * 512), entranceId, mapId,
+ (ushort)(mapPos + 0x400), true);
+ }
+}
+
+void Overworld::LoadSprites() {
+ // LW[0] = RainState 0 to 63 there's no data for DW
+ // LW[1] = ZeldaState 0 to 128 ; Contains LW and DW <128 or 144 wtf
+ // LW[2] = AgahState 0 to ?? ;Contains data for LW and DW
+
+ for (int i = 0; i < 3; i++) {
+ all_sprites_.emplace_back(std::vector());
+ }
+
+ for (int i = 0; i < 64; i++) {
+ if (map_parent_[i] == i) {
+ // Beginning Sprites
+ int ptrPos = core::overworldSpritesBegining + (i * 2);
+ int spriteAddress = core::SnesToPc((0x09 << 0x10) + rom_.toint16(ptrPos));
+ while (true) {
+ uchar b1 = rom_[spriteAddress];
+ uchar b2 = rom_[spriteAddress + 1];
+ uchar b3 = rom_[spriteAddress + 2];
+ if (b1 == 0xFF) {
+ break;
+ }
+
+ int mapY = (i / 8);
+ int mapX = (i % 8);
+
+ int realX = ((b2 & 0x3F) * 16) + mapX * 512;
+ int realY = ((b1 & 0x3F) * 16) + mapY * 512;
+
+ all_sprites_[0].emplace_back(overworld_maps_[i].AreaGraphics(),
+ (uchar)i, b3, (uchar)(b2 & 0x3F),
+ (uchar)(b1 & 0x3F), realX, realY);
+
+ spriteAddress += 3;
+ }
+ }
+ }
+
+ for (int i = 0; i < 144; i++) {
+ if (map_parent_[i] == i) {
+ // Zelda Saved Sprites
+ int ptrPos = core::overworldSpritesZelda + (i * 2);
+ int spriteAddress = core::SnesToPc((0x09 << 0x10) + rom_.toint16(ptrPos));
+ while (true) {
+ uchar b1 = rom_[spriteAddress];
+ uchar b2 = rom_[spriteAddress + 1];
+ uchar b3 = rom_[spriteAddress + 2];
+ if (b1 == 0xFF) {
+ break;
+ }
+
+ int editorMapIndex = i;
+ if (editorMapIndex >= 128) {
+ editorMapIndex = i - 128;
+ } else if (editorMapIndex >= 64) {
+ editorMapIndex = i - 64;
+ }
+
+ int mapY = (editorMapIndex / 8);
+ int mapX = (editorMapIndex % 8);
+
+ int realX = ((b2 & 0x3F) * 16) + mapX * 512;
+ int realY = ((b1 & 0x3F) * 16) + mapY * 512;
+
+ all_sprites_[1].emplace_back(overworld_maps_[i].AreaGraphics(),
+ (uchar)i, b3, (uchar)(b2 & 0x3F),
+ (uchar)(b1 & 0x3F), realX, realY);
+
+ spriteAddress += 3;
+ }
+ }
+
+ // Agahnim Dead Sprites
+ if (map_parent_[i] == i) {
+ int ptrPos = core::overworldSpritesAgahnim + (i * 2);
+ int spriteAddress = core::SnesToPc((0x09 << 0x10) + rom_.toint16(ptrPos));
+ while (true) {
+ uchar b1 = rom_[spriteAddress];
+ uchar b2 = rom_[spriteAddress + 1];
+ uchar b3 = rom_[spriteAddress + 2];
+ if (b1 == 0xFF) {
+ break;
+ }
+
+ int editorMapIndex = i;
+ if (editorMapIndex >= 128) {
+ editorMapIndex = i - 128;
+ } else if (editorMapIndex >= 64) {
+ editorMapIndex = i - 64;
+ }
+
+ int mapY = (editorMapIndex / 8);
+ int mapX = (editorMapIndex % 8);
+
+ int realX = ((b2 & 0x3F) * 16) + mapX * 512;
+ int realY = ((b1 & 0x3F) * 16) + mapY * 512;
+
+ all_sprites_[2].emplace_back(overworld_maps_[i].AreaGraphics(),
+ (uchar)i, b3, (uchar)(b2 & 0x3F),
+ (uchar)(b1 & 0x3F), realX, realY);
+
+ spriteAddress += 3;
+ }
+ }
+ }
+}
+
+} // namespace zelda3
+} // namespace app
+} // namespace yaze
\ No newline at end of file
diff --git a/src/app/zelda3/overworld.h b/src/app/zelda3/overworld.h
new file mode 100644
index 00000000..0606378e
--- /dev/null
+++ b/src/app/zelda3/overworld.h
@@ -0,0 +1,136 @@
+#ifndef YAZE_APP_DATA_OVERWORLD_H
+#define YAZE_APP_DATA_OVERWORLD_H
+
+#include
+
+#include
+#include
+
+#include "absl/status/status.h"
+#include "app/core/constants.h"
+#include "app/gfx/bitmap.h"
+#include "app/gfx/snes_tile.h"
+#include "app/rom.h"
+#include "app/zelda3/overworld_map.h"
+#include "app/zelda3/sprite.h"
+
+namespace yaze {
+namespace app {
+namespace zelda3 {
+
+class OverworldEntrance {
+ public:
+ int x_;
+ int y_;
+ ushort map_pos_;
+ uchar entrance_id_;
+ uchar area_x_;
+ uchar area_y_;
+ short map_id_;
+ bool is_hole_ = false;
+ bool deleted = false;
+
+ OverworldEntrance(int x, int y, uchar entranceId, short mapId, ushort mapPos,
+ bool hole)
+ : x_(x),
+ y_(y),
+ map_pos_(mapPos),
+ entrance_id_(entranceId),
+ map_id_(mapId),
+ is_hole_(hole) {
+ int mapX = (map_id_ - ((map_id_ / 8) * 8));
+ int mapY = (map_id_ / 8);
+
+ area_x_ = (uchar)((std::abs(x - (mapX * 512)) / 16));
+ area_y_ = (uchar)((std::abs(y - (mapY * 512)) / 16));
+ }
+
+ auto Copy() {
+ return new OverworldEntrance(x_, y_, entrance_id_, map_id_, map_pos_,
+ is_hole_);
+ }
+
+ void updateMapStuff(short mapId) {
+ map_id_ = mapId;
+
+ if (map_id_ >= 64) {
+ map_id_ -= 64;
+ }
+
+ int mapX = (map_id_ - ((map_id_ / 8) * 8));
+ int mapY = (map_id_ / 8);
+
+ area_x_ = (uchar)((std::abs(x_ - (mapX * 512)) / 16));
+ area_y_ = (uchar)((std::abs(y_ - (mapY * 512)) / 16));
+
+ map_pos_ = (ushort)((((area_y_) << 6) | (area_x_ & 0x3F)) << 1);
+ }
+};
+
+class Overworld {
+ public:
+ absl::Status Load(ROM &rom);
+ auto GetTiles16() const { return tiles16; }
+ auto GetOverworldMap(uint index) { return overworld_maps_[index]; }
+ auto GetOverworldMaps() const { return overworld_maps_; }
+ auto Sprites() const { return all_sprites_[game_state_]; }
+ auto AreaGraphics() const {
+ return overworld_maps_[current_map_].AreaGraphics();
+ }
+ auto Entrances() const { return all_entrances_; }
+ auto AreaPalette() const {
+ return overworld_maps_[current_map_].AreaPalette();
+ }
+ auto BitmapData() const { return overworld_maps_[current_map_].BitmapData(); }
+ auto Tile16Blockset() const {
+ return overworld_maps_[current_map_].Tile16Blockset();
+ }
+ auto GameState() const { return game_state_; }
+ auto isLoaded() const { return is_loaded_; }
+ void SetCurrentMap(int i) { current_map_ = i; }
+
+ private:
+ const int map32address[4] = {core::map32TilesTL, core::map32TilesTR,
+ core::map32TilesBL, core::map32TilesBR};
+ enum Dimension {
+ map32TilesTL = 0,
+ map32TilesTR = 1,
+ map32TilesBL = 2,
+ map32TilesBR = 3
+ };
+
+ ushort GenerateTile32(int i, int k, int dimension);
+ void AssembleMap32Tiles();
+ void AssembleMap16Tiles();
+ void AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
+ OWBlockset &world);
+ void OrganizeMapTiles(Bytes &bytes, Bytes &bytes2, int i, int sx, int sy,
+ int &ttpos);
+ absl::Status DecompressAllMapTiles();
+ void FetchLargeMaps();
+ void LoadEntrances();
+ void LoadSprites();
+
+ void LoadOverworldMap();
+
+ int game_state_ = 0;
+ int current_map_ = 0;
+ uchar map_parent_[160];
+ bool is_loaded_ = false;
+
+ ROM rom_;
+ OWMapTiles map_tiles_;
+
+ std::vector tiles16;
+ std::vector tiles32;
+ std::vector overworld_maps_;
+ std::vector all_entrances_;
+ std::vector