1 Commits

Author SHA1 Message Date
scawful
e7470bdfac backend-infra-engineer: Pre-0.2.2 snapshot (2022) 2023-01-01 17:52:09 -06:00
101 changed files with 13685 additions and 3 deletions

41
.github/workflows/cmake.yml vendored Normal file
View File

@@ -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

19
.gitignore vendored Normal file
View File

@@ -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

31
.gitmodules vendored
View File

@@ -1,3 +1,30 @@
[submodule "src/Library/imgui"] [submodule "src/lib/imgui"]
path = src/Library/imgui path = src/lib/imgui
url = https://github.com/ocornut/imgui.git 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

81
CMakeLists.txt Normal file
View File

@@ -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)

15
LICENSE Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.

38
README.md Normal file
View File

@@ -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
--------
![image](https://user-images.githubusercontent.com/47263509/194669806-2b0da68d-9d38-4f52-bcce-c60ee861092c.png)

View File

@@ -0,0 +1,32 @@
org <HOOK>
JML AreaCheck
org <EXPANDED_SPACE>
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

View File

@@ -0,0 +1,160 @@
;
; Credit to Zarby89
;
lorom
!End = $00
!Rest = $C9
!Tie = $C8
macro SetChannelVolume(v)
db $ED, <v>
endmacro
macro SetMasterVolume(v)
db $E5, <v>
endmacro
macro SetTempo(v)
db $E7, <v>
endmacro
macro SetInstrument(v)
db $E0, <v>
endmacro
macro CallSubroutine(addr, repeat)
db $EF
dw <addr>
db <repeat>
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 <v>, $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

Binary file not shown.

BIN
assets/font/DroidSans.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

19
cmake/absl.cmake Normal file
View File

@@ -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
)

33
cmake/asar.cmake Normal file
View File

@@ -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()

35
cmake/imgui.cmake Normal file
View File

@@ -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=<SDL2/SDL_opengl.h> 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=<SDL2/SDL_opengl.h> 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=<SDL2/SDL_opengl.h> 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
)

5
cmake/openssl.cmake Normal file
View File

@@ -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()

26
docs/changelog.md Normal file
View File

@@ -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

17
docs/dev-setup-windows.md Normal file
View File

@@ -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 <package-name>`
`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`

BIN
docs/manual/manual.pdf Normal file

Binary file not shown.

40
docs/manual/manual.tex Normal file
View File

@@ -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}

183
src/CMakeLists.txt Normal file
View File

@@ -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}")

63
src/app/core/common.cc Normal file
View File

@@ -0,0 +1,63 @@
#include "common.h"
#include <cstdint>
#include <string>
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

21
src/app/core/common.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef YAZE_CORE_COMMON_H
#define YAZE_CORE_COMMON_H
#include <cstdint>
#include <string>
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

1604
src/app/core/constants.h Normal file

File diff suppressed because it is too large Load Diff

229
src/app/core/controller.cc Normal file
View File

@@ -0,0 +1,229 @@
#include "controller.h"
#include <SDL.h>
#include <SDL_mixer.h>
#include <imgui/backends/imgui_impl_sdl.h>
#include <imgui/backends/imgui_impl_sdlrenderer.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <memory>
#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<float>(event.window.data1);
io.DisplaySize.y = static_cast<float>(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<float>(mouseX), static_cast<float>(mouseY));
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
io.MouseWheel = static_cast<float>(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_Window, sdl_deleter>(
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_Renderer, sdl_deleter>(
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

57
src/app/core/controller.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef YAZE_APP_CORE_CONTROLLER_H
#define YAZE_APP_CORE_CONTROLLER_H
#include <SDL.h>
#include <imgui/backends/imgui_impl_sdl.h>
#include <imgui/backends/imgui_impl_sdlrenderer.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <memory>
#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<SDL_Window> window_;
std::shared_ptr<SDL_Renderer> renderer_;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_CONTROLLER_H

View File

@@ -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}"
)

54
src/app/delta/client.cc Normal file
View File

@@ -0,0 +1,54 @@
#include "client.h"
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#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

44
src/app/delta/client.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef YAZE_APP_DELTA_CLIENT_H
#define YAZE_APP_DELTA_CLIENT_H
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include <vector>
#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<Repository> repos_;
std::unique_ptr<YazeDelta::Stub> stub_;
};
} // namespace delta
} // namespace app
} // namespace yaze
#endif

32
src/app/delta/delta.cc Normal file
View File

@@ -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;
}

105
src/app/delta/delta.proto Normal file
View File

@@ -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 {}

87
src/app/delta/service.cc Normal file
View File

@@ -0,0 +1,87 @@
#include "service.h"
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include <fstream>
#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<Repository>& repos, const std::string& name) {
for (auto& repo : repos) {
if (repo.project_name() == name) {
return repo.mutable_tree();
}
}
}
auto FindBranch(google::protobuf::RepeatedPtrField<Branch>* 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

48
src/app/delta/service.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef YAZE_APP_DELTA_SERVICE_H
#define YAZE_APP_DELTA_SERVICE_H
#include <filesystem>
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include <vector>
#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<Repository> repos_;
};
} // namespace delta
} // namespace app
} // namespace yaze
#endif

229
src/app/delta/viewer.cc Normal file
View File

@@ -0,0 +1,229 @@
#include "viewer.h"
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#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

45
src/app/delta/viewer.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef YAZE_APP_DELTA_VIEWER_H
#define YAZE_APP_DELTA_VIEWER_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#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

View File

@@ -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<char>(t)),
std::istreambuf_iterator<char>());
text_editor_.SetText(str);
file_is_loaded_ = true;
}
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,38 @@
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <fstream>
#include <istream>
#include <string>
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

View File

@@ -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

View File

@@ -0,0 +1,26 @@
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
#include <imgui/imgui.h>
#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

View File

@@ -0,0 +1,322 @@
#include "master_editor.h"
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#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<SDL_Renderer> 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

View File

@@ -0,0 +1,72 @@
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#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<SDL_Renderer> 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> 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

View File

@@ -0,0 +1,292 @@
#include "music_editor.h"
#include <SDL_mixer.h>
#include <imgui/imgui.h>
#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", &current_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

View File

@@ -0,0 +1,86 @@
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
#include <SDL_mixer.h>
#include <imgui/imgui.h>
#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

View File

@@ -0,0 +1,352 @@
#include "overworld_editor.h"
#include <imgui/imgui.h>
#include <cmath>
#include <unordered_map>
#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", &current_map_);
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(100.f);
ImGui::Combo("##world", &current_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(&current_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

View File

@@ -0,0 +1,118 @@
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#include <imgui/imgui.h>
#include <cmath>
#include <unordered_map>
#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<int, gfx::Bitmap> graphics_bin_;
std::unordered_map<int, gfx::Bitmap> current_graphics_set_;
std::unordered_map<int, gfx::Bitmap> maps_bmp_;
std::unordered_map<int, gfx::Bitmap> sprite_previews_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> 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

View File

@@ -0,0 +1,128 @@
#include "palette_editor.h"
#include <imgui/imgui.h>
#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

View File

@@ -0,0 +1,42 @@
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
#include <imgui/imgui.h>
#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

View File

@@ -0,0 +1,201 @@
#include "app/editor/screen_editor.h"
#include <imgui/imgui.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#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

View File

@@ -0,0 +1,60 @@
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
#include <imgui/imgui.h>
#include <array>
#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<int, core::kNumOverworldMaps>;
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

135
src/app/gfx/bitmap.cc Normal file
View File

@@ -0,0 +1,135 @@
#include "bitmap.h"
#include <SDL.h>
#include <cstdint>
#include <memory>
#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_Surface, SDL_Surface_Deleter>(
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_Surface, SDL_Surface_Deleter>(
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_Surface, SDL_Surface_Deleter>(
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_Surface, SDL_Surface_Deleter>(
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<SDL_Renderer> renderer) {
texture_ = std::shared_ptr<SDL_Texture>{
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

87
src/app/gfx/bitmap.h Normal file
View File

@@ -0,0 +1,87 @@
#ifndef YAZE_APP_GFX_BITMAP_H
#define YAZE_APP_GFX_BITMAP_H
#include <SDL.h>
#include <cstdint>
#include <memory>
#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<SDL_Renderer> 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<SDL_Texture> texture_ = nullptr;
std::shared_ptr<SDL_Surface> surface_ = nullptr;
};
} // namespace gfx
} // namespace app
} // namespace yaze
#endif // YAZE_APP_GFX_BITMAP_H

197
src/app/gfx/snes_palette.cc Normal file
View File

@@ -0,0 +1,197 @@
#include "snes_palette.h"
#include <SDL.h>
#include <imgui/imgui.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <vector>
#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<uchar>(data[i + 1]) << 8;
col.snes = col.snes | static_cast<uchar>(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<ImVec4>& cols) {
for (const auto& each : cols) {
SNESColor scol;
scol.setRgb(each);
colors.push_back(scol);
}
size_ = cols.size();
}
SNESPalette::SNESPalette(const std::vector<snes_color>& cols) {
for (const auto& each : cols) {
SNESColor scol;
scol.setSNES(each);
colors.push_back(scol);
}
size_ = cols.size();
}
SNESPalette::SNESPalette(const std::vector<SNESColor>& cols) {
for (const auto& each : cols) {
colors.push_back(each);
}
size_ = cols.size();
}
void SNESPalette::Create(const std::vector<SNESColor>& 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>();
sdl_palette->ncolors = size_;
auto color = std::vector<SDL_Color>(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

117
src/app/gfx/snes_palette.h Normal file
View File

@@ -0,0 +1,117 @@
#ifndef YAZE_APP_GFX_PALETTE_H
#define YAZE_APP_GFX_PALETTE_H
#include <SDL.h>
#include <imgui/imgui.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <vector>
#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<ImVec4>&);
explicit SNESPalette(const std::vector<snes_color>&);
explicit SNESPalette(const std::vector<SNESColor>&);
void Create(const std::vector<SNESColor>&);
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<SNESColor> 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<SNESPalette> palettes;
};
} // namespace gfx
} // namespace app
} // namespace yaze
#endif // YAZE_APP_GFX_PALETTE_H

29
src/app/gfx/snes_tile.cc Normal file
View File

@@ -0,0 +1,29 @@
#include "snes_tile.h"
#include <cstdint>
#include <vector>
#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

94
src/app/gfx/snes_tile.h Normal file
View File

@@ -0,0 +1,94 @@
#ifndef YAZE_APP_GFX_SNES_TILE_H
#define YAZE_APP_GFX_SNES_TILE_H
#include <cstdint>
#include <vector>
#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<TileInfo> 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

851
src/app/rom.cc Normal file
View File

@@ -0,0 +1,851 @@
#include "rom.h"
#include <SDL.h>
#include <asar/src/asar/interface-lib.h>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#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<CompressionPiece>& 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<CompressionPiece>& 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<CompressionPiece>& 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<CompressionPiece>(
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<CompressionPiece>(
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<std::shared_ptr<CompressionPiece>> SplitCompressionPiece(
std::shared_ptr<CompressionPiece>& piece, int mode) {
std::shared_ptr<CompressionPiece> new_piece;
uint length_left = piece->length - kMaxLengthCompression;
piece->length = kMaxLengthCompression;
switch (piece->command) {
case kCommandByteFill:
case kCommandWordFill:
new_piece = std::make_shared<CompressionPiece>(
piece->command, length_left, piece->argument, piece->argument_length);
break;
case kCommandIncreasingFill:
new_piece = std::make_shared<CompressionPiece>(
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<CompressionPiece>(
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<CompressionPiece>(
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<CompressionPiece>& 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<Bytes> 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<CompressionPiece>(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<CompressionPiece>(
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<Bytes> ROM::CompressGraphics(const int pos, const int length) {
return Compress(pos, length, kNintendoMode2);
}
absl::StatusOr<Bytes> ROM::CompressOverworld(const int pos, const int length) {
return Compress(pos, length, kNintendoMode1);
}
absl::StatusOr<Bytes> 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<Bytes> ROM::DecompressGraphics(int pos, int size) {
return Decompress(pos, size, kNintendoMode2);
}
absl::StatusOr<Bytes> ROM::DecompressOverworld(int pos, int size) {
return Decompress(pos, size, kNintendoMode1);
}
absl::StatusOr<Bytes> 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<gfx::SNESColor> 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, "<HOOK>", kMosaicChangeOffset)) {
return absl::InternalError(
"Mosaic template did not have proper `<HOOK>` to replace.");
}
if (!core::StringReplace(
assembly_string, "<EXPANDED_SPACE>",
absl::StrFormat("$%x", routine_offset + kSNESToPCOffset))) {
return absl::InternalError(
"Mosaic template did not have proper `<EXPANDED_SPACE>` to replace.");
}
return ApplyAssembly(filename, assembly_string.size());
}
} // namespace app
} // namespace yaze

157
src/app/rom.h Normal file
View File

@@ -0,0 +1,157 @@
#ifndef YAZE_APP_ROM_H
#define YAZE_APP_ROM_H
#include <SDL.h>
#include <asar/src/asar/interface-lib.h>
#include <cstddef>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#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<std::array<char, 2>, 5>;
using CommandSizeArray = std::array<uint, 5>;
using DataSizeArray = std::array<uint, 5>;
struct CompressionPiece {
char command;
int length;
int argument_length;
std::string argument;
std::shared_ptr<CompressionPiece> 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<CompressionPiece>;
class ROM {
public:
absl::StatusOr<Bytes> Compress(const int start, const int length,
int mode = 1, bool check = false);
absl::StatusOr<Bytes> CompressGraphics(const int pos, const int length);
absl::StatusOr<Bytes> CompressOverworld(const int pos, const int length);
absl::StatusOr<Bytes> Decompress(int offset, int size = 0x800, int mode = 1);
absl::StatusOr<Bytes> DecompressGraphics(int pos, int size);
absl::StatusOr<Bytes> DecompressOverworld(int pos, int size);
absl::StatusOr<Bytes> 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<SDL_Renderer> 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<char*>(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<SDL_Renderer> renderer_;
std::unordered_map<int, gfx::Bitmap> graphics_bin_;
std::unordered_map<std::string, gfx::PaletteGroup> palette_groups_;
};
} // namespace app
} // namespace yaze
#endif

26
src/app/spc700/spc700.def Normal file
View File

@@ -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

31
src/app/yaze.cc Normal file
View File

@@ -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;
}

View File

@@ -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

View File

@@ -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<gfx::TileInfo> tiles_;
};
} // namespace zelda3
} // namespace app
} // namespace yaze
#endif

384
src/app/zelda3/overworld.cc Normal file
View File

@@ -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<Sprite>());
}
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

136
src/app/zelda3/overworld.h Normal file
View File

@@ -0,0 +1,136 @@
#ifndef YAZE_APP_DATA_OVERWORLD_H
#define YAZE_APP_DATA_OVERWORLD_H
#include <SDL.h>
#include <memory>
#include <vector>
#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<gfx::Tile16> tiles16;
std::vector<gfx::Tile32> tiles32;
std::vector<OverworldMap> overworld_maps_;
std::vector<OverworldEntrance> all_entrances_;
std::vector<OverworldEntrance> all_holes_;
std::vector<std::vector<Sprite>> all_sprites_;
};
} // namespace zelda3
} // namespace app
} // namespace yaze
#endif

View File

@@ -0,0 +1,546 @@
#include "overworld_map.h"
#include <imgui/imgui.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <unordered_map>
#include <vector>
#include "app/core/common.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace zelda3 {
namespace {
void CopyTile8bpp16(int x, int y, int tile, Bytes& bitmap, Bytes& blockset) {
int src_pos =
((tile - ((tile / 0x08) * 0x08)) * 0x10) + ((tile / 0x08) * 2048);
int dest_pos = (x + (y * 0x200));
for (int yy = 0; yy < 0x10; yy++) {
for (int xx = 0; xx < 0x10; xx++) {
bitmap[dest_pos + xx + (yy * 0x200)] =
blockset[src_pos + xx + (yy * 0x80)];
}
}
}
void SetColorsPalette(ROM& rom, int index, gfx::SNESPalette& current,
gfx::SNESPalette main, gfx::SNESPalette animated,
gfx::SNESPalette aux1, gfx::SNESPalette aux2,
gfx::SNESPalette hud, gfx::SNESColor bgrcolor,
gfx::SNESPalette spr, gfx::SNESPalette spr2) {
// Palettes infos, color 0 of a palette is always transparent (the arrays
// contains 7 colors width wide) There is 16 color per line so 16*Y
// Left side of the palette - Main, Animated
std::vector<gfx::SNESColor> new_palette(256);
// Main Palette, Location 0,2 : 35 colors [7x5]
int k = 0;
for (int y = 2; y < 7; y++) {
for (int x = 1; x < 8; x++) {
new_palette[x + (16 * y)] = main[k];
k++;
}
}
// Animated Palette, Location 0,7 : 7colors
for (int x = 1; x < 8; x++) {
new_palette[(16 * 7) + (x)] = animated[(x - 1)];
}
// Right side of the palette - Aux1, Aux2
// Aux1 Palette, Location 8,2 : 21 colors [7x3]
k = 0;
for (int y = 2; y < 5; y++) {
for (int x = 9; x < 16; x++) {
new_palette[x + (16 * y)] = aux1[k];
k++;
}
}
// Aux2 Palette, Location 8,5 : 21 colors [7x3]
k = 0;
for (int y = 5; y < 8; y++) {
for (int x = 9; x < 16; x++) {
new_palette[x + (16 * y)] = aux2[k];
k++;
}
}
// Hud Palette, Location 0,0 : 32 colors [16x2]
for (int i = 0; i < 32; i++) {
new_palette[i] = hud[i];
}
// Hardcoded grass color (that might change to become invisible instead)
for (int i = 0; i < 8; i++) {
new_palette[(i * 16)] = bgrcolor;
new_palette[(i * 16) + 8] = bgrcolor;
}
// Sprite Palettes
k = 0;
for (int y = 8; y < 9; y++) {
for (int x = 1; x < 8; x++) {
new_palette[x + (16 * y)] = rom.GetPaletteGroup("sprites_aux1")[1][k];
k++;
}
}
// Sprite Palettes
k = 0;
for (int y = 8; y < 9; y++) {
for (int x = 9; x < 16; x++) {
new_palette[x + (16 * y)] = rom.GetPaletteGroup("sprites_aux3")[0][k];
k++;
}
}
// Sprite Palettes
k = 0;
for (int y = 9; y < 13; y++) {
for (int x = 1; x < 16; x++) {
new_palette[x + (16 * y)] = rom.GetPaletteGroup("global_sprites")[0][k];
k++;
}
}
// Sprite Palettes
k = 0;
for (int y = 13; y < 14; y++) {
for (int x = 1; x < 8; x++) {
new_palette[x + (16 * y)] = spr[k];
k++;
}
}
// Sprite Palettes
k = 0;
for (int y = 14; y < 15; y++) {
for (int x = 1; x < 8; x++) {
new_palette[x + (16 * y)] = spr2[k];
k++;
}
}
// Sprite Palettes
k = 0;
for (int y = 15; y < 16; y++) {
for (int x = 1; x < 16; x++) {
new_palette[x + (16 * y)] = rom.GetPaletteGroup("armors")[0][k];
k++;
}
}
current.Create(new_palette);
for (int i = 0; i < 256; i++) {
current[(i / 16) * 16].setTransparent(true);
}
}
} // namespace
OverworldMap::OverworldMap(int index, ROM& rom,
std::vector<gfx::Tile16>& tiles16)
: parent_(index), index_(index), rom_(rom), tiles16_(tiles16) {
LoadAreaInfo();
}
absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
uchar* map_parent,
OWBlockset& world_blockset) {
game_state_ = game_state;
world_ = world;
if (large_map_) {
parent_ = map_parent[index_];
if (parent_ != index_ && !initialized_) {
if (index_ >= 0x80 && index_ <= 0x8A && index_ != 0x88) {
area_graphics_ =
rom_[core::overworldSpecialGFXGroup + (parent_ - 0x80)];
area_palette_ = rom_[core::overworldSpecialPALGroup + 1];
} else if (index_ == 0x88) {
area_graphics_ = 0x51;
area_palette_ = 0x00;
} else {
area_graphics_ = rom_[core::mapGfx + parent_];
area_palette_ = rom_[core::overworldMapPalette + parent_];
}
initialized_ = true;
}
}
LoadAreaGraphics();
RETURN_IF_ERROR(BuildTileset())
RETURN_IF_ERROR(BuildTiles16Gfx(count))
LoadPalette();
RETURN_IF_ERROR(BuildBitmap(world_blockset))
built_ = true;
return absl::OkStatus();
}
void OverworldMap::LoadAreaInfo() {
if (index_ != 0x80 && index_ <= 150 &&
rom_[core::overworldMapSize + (index_ & 0x3F)] != 0) {
large_map_ = true;
}
if (index_ < 64) {
area_graphics_ = rom_[core::mapGfx + parent_];
area_palette_ = rom_[core::overworldMapPalette + parent_];
area_music_[0] = rom_[core::overworldMusicBegining + parent_];
area_music_[1] = rom_[core::overworldMusicZelda + parent_];
area_music_[2] = rom_[core::overworldMusicMasterSword + parent_];
area_music_[3] = rom_[core::overworldMusicAgahim + parent_];
sprite_graphics_[0] = rom_[core::overworldSpriteset + parent_];
sprite_graphics_[1] = rom_[core::overworldSpriteset + parent_ + 0x40];
sprite_graphics_[2] = rom_[core::overworldSpriteset + parent_ + 0x80];
sprite_palette_[0] = rom_[core::overworldSpritePalette + parent_];
sprite_palette_[1] = rom_[core::overworldSpritePalette + parent_ + 0x40];
sprite_palette_[2] = rom_[core::overworldSpritePalette + parent_ + 0x80];
} else if (index_ < 0x80) {
area_graphics_ = rom_[core::mapGfx + parent_];
area_palette_ = rom_[core::overworldMapPalette + parent_];
area_music_[0] = rom_[core::overworldMusicDW + (parent_ - 64)];
sprite_graphics_[0] = rom_[core::overworldSpriteset + parent_ + 0x80];
sprite_graphics_[1] = rom_[core::overworldSpriteset + parent_ + 0x80];
sprite_graphics_[2] = rom_[core::overworldSpriteset + parent_ + 0x80];
sprite_palette_[0] = rom_[core::overworldSpritePalette + parent_ + 0x80];
sprite_palette_[1] = rom_[core::overworldSpritePalette + parent_ + 0x80];
sprite_palette_[2] = rom_[core::overworldSpritePalette + parent_ + 0x80];
} else {
if (index_ == 0x94) {
parent_ = 0x80;
} else if (index_ == 0x95) {
parent_ = 0x03;
} else if (index_ == 0x96) {
parent_ = 0x5B; // pyramid bg use 0x5B map
} else if (index_ == 0x97) {
parent_ = 0x00; // pyramid bg use 0x5B map
} else if (index_ == 0x9C) {
parent_ = 0x43;
} else if (index_ == 0x9D) {
parent_ = 0x00;
} else if (index_ == 0x9E) {
parent_ = 0x00;
} else if (index_ == 0x9F) {
parent_ = 0x2C;
} else if (index_ == 0x88) {
parent_ = 0x88;
}
area_palette_ = rom_[core::overworldSpecialPALGroup + parent_ - 0x80];
if (index_ >= 0x80 && index_ <= 0x8A && index_ != 0x88) {
area_graphics_ = rom_[core::overworldSpecialGFXGroup + (parent_ - 0x80)];
area_palette_ = rom_[core::overworldSpecialPALGroup + 1];
} else if (index_ == 0x88) {
area_graphics_ = 0x51;
area_palette_ = 0x00;
} else {
// pyramid bg use 0x5B map
area_graphics_ = rom_[core::mapGfx + parent_];
area_palette_ = rom_[core::overworldMapPalette + parent_];
}
message_id_ = rom_[core::overworldMessages + parent_];
sprite_graphics_[0] = rom_[core::overworldSpriteset + parent_ + 0x80];
sprite_graphics_[1] = rom_[core::overworldSpriteset + parent_ + 0x80];
sprite_graphics_[2] = rom_[core::overworldSpriteset + parent_ + 0x80];
sprite_palette_[0] = rom_[core::overworldSpritePalette + parent_ + 0x80];
sprite_palette_[1] = rom_[core::overworldSpritePalette + parent_ + 0x80];
sprite_palette_[2] = rom_[core::overworldSpritePalette + parent_ + 0x80];
}
}
void OverworldMap::LoadAreaGraphics() {
int world_index = 0x20;
if (parent_ < 0x40) {
world_index = 0x20;
} else if (parent_ >= 0x40 && parent_ < 0x80) {
world_index = 0x21;
} else if (parent_ == 0x88) {
world_index = 0x24;
}
// Sprites Blocksets
static_graphics_[8] = 0x73 + 0x00;
static_graphics_[9] = 0x73 + 0x01;
static_graphics_[10] = 0x73 + 0x06;
static_graphics_[11] = 0x73 + 0x07;
for (int i = 0; i < 4; i++) {
static_graphics_[12 + i] = (rom_[core::kSpriteBlocksetPointer +
(sprite_graphics_[game_state_] * 4) + i] +
0x73);
}
// Main Blocksets
for (int i = 0; i < 8; i++) {
static_graphics_[i] =
rom_[core::overworldgfxGroups2 + (world_index * 8) + i];
}
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4)] != 0) {
static_graphics_[3] = rom_[core::overworldgfxGroups + (area_graphics_ * 4)];
}
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 1] != 0) {
static_graphics_[4] =
rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 1];
}
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 2] != 0) {
static_graphics_[5] =
rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 2];
}
if (rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 3] != 0) {
static_graphics_[6] =
rom_[core::overworldgfxGroups + (area_graphics_ * 4) + 3];
}
// Hardcoded overworld GFX Values, for death mountain
if ((parent_ >= 0x03 && parent_ <= 0x07) ||
(parent_ >= 0x0B && parent_ <= 0x0E)) {
static_graphics_[7] = 0x59;
} else if ((parent_ >= 0x43 && parent_ <= 0x47) ||
(parent_ >= 0x4B && parent_ <= 0x4E)) {
static_graphics_[7] = 0x59;
} else {
static_graphics_[7] = 0x5B;
}
}
void OverworldMap::LoadPalette() {
int previousPalId = 0;
int previousSprPalId = 0;
if (index_ > 0) {
previousPalId = rom_[core::overworldMapPalette + parent_ - 1];
previousSprPalId = rom_[core::overworldSpritePalette + parent_ - 1];
}
if (area_palette_ >= 0xA3) {
area_palette_ = 0xA3;
}
uchar pal0 = 0;
uchar pal1 = rom_[core::overworldMapPaletteGroup + (area_palette_ * 4)];
uchar pal2 =
rom_[core::overworldMapPaletteGroup + (area_palette_ * 4) + 1]; // aux2
uchar pal3 = rom_[core::overworldMapPaletteGroup + (area_palette_ * 4) +
2]; // animated
uchar pal4 = rom_[core::overworldSpritePaletteGroup +
(sprite_palette_[game_state_] * 2)]; // spr3
uchar pal5 = rom_[core::overworldSpritePaletteGroup +
(sprite_palette_[game_state_] * 2) + 1]; // spr4
gfx::SNESPalette aux1;
gfx::SNESPalette aux2;
gfx::SNESPalette main;
gfx::SNESPalette animated;
gfx::SNESPalette hud;
gfx::SNESPalette spr;
gfx::SNESPalette spr2;
gfx::SNESColor bgr = rom_.GetPaletteGroup("grass")[0].GetColor(0);
if (pal1 == 255) {
pal1 = rom_[core::overworldMapPaletteGroup + (previousPalId * 4)];
}
if (pal1 != 255) {
if (pal1 >= 20) {
pal1 = 19;
}
aux1 = rom_.GetPaletteGroup("ow_aux")[pal1];
} else {
aux1 = rom_.GetPaletteGroup("ow_aux")[0];
}
if (pal2 == 255) {
pal2 = rom_[core::overworldMapPaletteGroup + (previousPalId * 4) + 1];
}
if (pal2 != 255) {
if (pal2 >= 20) {
pal2 = 19;
}
aux2 = rom_.GetPaletteGroup("ow_aux")[pal2];
} else {
aux2 = rom_.GetPaletteGroup("ow_aux")[0];
}
if (pal3 == 255) {
pal3 = rom_[core::overworldMapPaletteGroup + (previousPalId * 4) + 2];
}
if (parent_ < 0x40) {
// Default LW Palette
pal0 = 0;
bgr = rom_.GetPaletteGroup("grass")[0].GetColor(0);
if (parent_ == 0x03 || parent_ == 0x05 || parent_ == 0x07) {
pal0 = 2;
}
} else if (parent_ >= 0x40 && parent_ < 0x80) {
// Default DW Palette
pal0 = 1;
bgr = rom_.GetPaletteGroup("grass")[0].GetColor(1);
if (parent_ == 0x43 || parent_ == 0x45 || parent_ == 0x47) {
pal0 = 3;
}
} else if (parent_ >= 128 && parent_ < core::kNumOverworldMaps) {
// Default SP Palette
pal0 = 0;
bgr = rom_.GetPaletteGroup("grass")[0].GetColor(2);
}
if (parent_ == 0x88) {
pal0 = 4;
}
if (pal0 != 255) {
main = rom_.GetPaletteGroup("ow_main")[pal0];
} else {
main = rom_.GetPaletteGroup("ow_main")[0];
}
if (pal3 >= 14) {
pal3 = 13;
}
animated = rom_.GetPaletteGroup("ow_animated")[(pal3)];
hud = rom_.GetPaletteGroup("hud")[0];
if (pal4 == 255) {
pal4 = rom_[core::overworldSpritePaletteGroup +
(previousSprPalId * 2)]; // spr3
}
if (pal4 == 255) {
pal4 = 0;
}
if (pal4 >= 24) {
pal4 = 23;
}
spr = rom_.GetPaletteGroup("sprites_aux3")[pal4];
if (pal5 == 255) {
pal5 = rom_[core::overworldSpritePaletteGroup + (previousSprPalId * 2) +
1]; // spr3
}
if (pal5 == 255) {
pal5 = 0;
}
if (pal5 >= 24) {
pal5 = 23;
}
spr2 = rom_.GetPaletteGroup("sprites_aux3")[pal5];
SetColorsPalette(rom_, parent_, current_palette_, main, animated, aux1, aux2,
hud, bgr, spr, spr2);
}
absl::Status OverworldMap::BuildTileset() {
all_gfx_ = rom_.GetGraphicsBuffer();
current_gfx_.reserve(0x10000);
for (int i = 0; i < 0x10000; i++) {
current_gfx_.push_back(0x00);
}
for (int i = 0; i < 0x10; i++) {
for (int j = 0; j < 0x1000; j++) {
auto byte = all_gfx_[j + (static_graphics_[i] * 0x1000)];
switch (i) {
case 0:
case 3:
case 4:
case 5:
byte += 0x88;
break;
}
current_gfx_[(i * 0x1000) + j] = byte;
}
}
return absl::OkStatus();
}
absl::Status OverworldMap::BuildTiles16Gfx(int count) {
current_blockset_.reserve(0x100000);
for (int i = 0; i < 0x100000; i++) {
current_blockset_.push_back(0x00);
}
const int offsets[] = {0x00, 0x08, 0x400, 0x408};
auto yy = 0;
auto xx = 0;
for (auto i = 0; i < count; i++) {
for (auto tile = 0; tile < 0x04; tile++) {
gfx::TileInfo info = tiles16_[i].tiles_info[tile];
int offset = offsets[tile];
for (auto y = 0; y < 0x08; ++y) {
for (auto x = 0; x < 0x08; ++x) {
int mx = x;
int my = y;
if (info.horizontal_mirror_ != 0) {
mx = 0x07 - x;
}
if (info.vertical_mirror_ != 0) {
my = 0x07 - y;
}
int xpos = ((info.id_ % 0x10) * 0x08);
int ypos = (((info.id_ / 0x10)) * 0x400);
int source = ypos + xpos + (x + (y * 0x80));
auto destination = xx + yy + offset + (mx + (my * 0x80));
current_blockset_[destination] =
(current_gfx_[source] & 0x0F) + (info.palette_ * 0x10);
}
}
}
xx += 0x10;
if (xx >= 0x80) {
yy += 0x800;
xx = 0;
}
}
return absl::OkStatus();
}
absl::Status OverworldMap::BuildBitmap(OWBlockset& world_blockset) {
bitmap_data_.reserve(0x40000);
for (int i = 0; i < 0x40000; i++) {
bitmap_data_.push_back(0x00);
}
int superY = ((index_ - (world_ * 0x40)) / 0x08);
int superX = index_ - (world_ * 0x40) - (superY * 0x08);
for (int y = 0; y < 0x20; y++) {
for (int x = 0; x < 0x20; x++) {
auto xt = x + (superX * 0x20);
auto yt = y + (superY * 0x20);
CopyTile8bpp16((x * 0x10), (y * 0x10), world_blockset[xt][yt],
bitmap_data_, current_blockset_);
}
}
return absl::OkStatus();
}
} // namespace zelda3
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,81 @@
#ifndef YAZE_APP_ZELDA3_OVERWORLD_MAP_H
#define YAZE_APP_ZELDA3_OVERWORLD_MAP_H
#include <imgui/imgui.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace zelda3 {
static constexpr int kTileOffsets[] = {0, 8, 4096, 4104};
class OverworldMap {
public:
OverworldMap(int index, ROM& rom, std::vector<gfx::Tile16>& tiles16);
absl::Status BuildMap(int count, int game_state, int world, uchar* map_parent,
OWBlockset& world_blockset);
auto Tile16Blockset() const { return current_blockset_; }
auto AreaGraphics() const { return current_gfx_; }
auto AreaPalette() const { return current_palette_; }
auto BitmapData() const { return bitmap_data_; }
auto SetLargeMap(bool is_set) { large_map_ = is_set; }
auto IsLargeMap() const { return large_map_; }
auto IsInitialized() const { return initialized_; }
private:
void LoadAreaInfo();
void LoadAreaGraphics();
void LoadPalette();
absl::Status BuildTileset();
absl::Status BuildTiles16Gfx(int count);
absl::Status BuildBitmap(OWBlockset& world_blockset);
int parent_ = 0;
int index_ = 0;
int world_ = 0;
int message_id_ = 0;
int area_graphics_ = 0;
int area_palette_ = 0;
int game_state_ = 0;
uchar sprite_graphics_[3];
uchar sprite_palette_[3];
uchar area_music_[4];
uchar static_graphics_[16];
bool initialized_ = false;
bool built_ = false;
bool large_map_ = false;
ROM rom_;
Bytes all_gfx_;
Bytes current_blockset_;
Bytes current_gfx_;
Bytes bitmap_data_;
OWMapTiles map_tiles_;
gfx::SNESPalette current_palette_;
std::vector<gfx::Tile16> tiles16_;
};
} // namespace zelda3
} // namespace app
} // namespace yaze
#endif

921
src/app/zelda3/sprite.cc Normal file
View File

@@ -0,0 +1,921 @@
#include "sprite.h"
namespace yaze {
namespace app {
namespace zelda3 {
Sprite::Sprite(Bytes src, uchar mapid, uchar id, uchar x, uchar y, int map_x,
int map_y) {
current_gfx_ = src;
overworld_ = true;
map_id_ = mapid;
id_ = id;
x_ = x;
y_ = y;
nx_ = x;
ny_ = y;
name_ = core::kSpriteDefaultNames[id];
map_x_ = map_x;
map_y_ = map_y;
preview_gfx_.reserve(64 * 64);
for (int i = 0; i < 64 * 64; i++) {
preview_gfx_.push_back(0xFF);
}
}
void Sprite::updateBBox() {
lowerX_ = 1;
lowerY_ = 1;
higherX_ = 15;
higherY_ = 15;
}
void Sprite::Draw(bool picker) {
uchar x = nx_;
uchar y = ny_;
picker_ = picker;
if (overlord_ == 0x07) {
if (id_ == 0x1A) {
DrawSpriteTile((x * 16), (y * 16), 14, 6, 11); // bomb
} else if (id_ == 0x05) {
DrawSpriteTile((x * 16), (y * 16) - 12, 12, 16, 12, false, true);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 12, false, true);
} else if (id_ == 0x06) {
DrawSpriteTile((x * 16), (y * 16), 10, 26, 14, true, true, 2,
2); // snek
} else if (id_ == 0x09) {
DrawSpriteTile((x * 16), (y * 16), 6, 26, 14);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 8, 26, 14);
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 27, 14, false, false, 1, 1);
} else if (id_ == 0x14) {
DrawSpriteTile((x * 16), (y * 16) + 8, 12, 06, 12, false, false, 2,
1); // shadow tile
DrawSpriteTile((x * 16), (y * 16) - 8, 3, 29, 8, false, false, 1,
1); // tile
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 3, 29, 8, true, false, 1,
1); // tile
DrawSpriteTile((x * 16), (y * 16), 3, 29, 8, false, true, 1,
1); // tile
DrawSpriteTile((x * 16) + 8, (y * 16), 3, 29, 8, true, true, 1,
1); // tile
} else {
DrawSpriteTile((x * 16), (y * 16), 4, 4, 5);
}
if (nx_ != x || ny_ != y) {
bounding_box_.x = (lowerX_ + (nx_ * 16));
bounding_box_.y = (lowerY_ + (ny_ * 16));
bounding_box_.w = width_;
bounding_box_.h = height_;
} else {
bounding_box_.x = (lowerX_ + (x * 16));
bounding_box_.y = (lowerY_ + (y * 16));
bounding_box_.w = width_;
bounding_box_.h = height_;
}
return;
}
if (id_ == 0x00) {
DrawSpriteTile((x * 16), (y * 16), 4, 28, 10);
} else if (id_ == 0x01) {
DrawSpriteTile((x * 16) - 8, (y * 16), 6, 24, 12, false, false, 2, 2);
DrawSpriteTile((x * 16) + 8, (y * 16), 6, 24, 12, true, false, 2, 2);
} else if (id_ == 0x02) {
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0x04) {
uchar p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x05) {
uchar p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x06) {
uchar p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x07) {
uchar p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x08) {
DrawSpriteTile((x * 16), (y * 16), 0, 24, 6);
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 6, false, false, 1, 1);
} else if (id_ == 0x09) {
DrawSpriteTile((x * 16) - 22, (y * 16) - 24, 12, 24, 12, false, false, 2,
2); // Moldorm tail
DrawSpriteTile((x * 16) - 16, (y * 16) - 20, 8, 24, 12, false, false, 2,
2); // Moldorm b2
DrawSpriteTile((x * 16) - 12, (y * 16) - 16, 4, 24, 12, false, false, 4,
4); // Moldorm b
DrawSpriteTile((x * 16), (y * 16), 0, 24, 12, false, false, 4,
4); // Moldorm head
DrawSpriteTile((x * 16) + 20, (y * 16) + 12, 8, 26, 14, false, false, 2,
2); // Moldorm eye
DrawSpriteTile((x * 16) + 12, (y * 16) + 20, 8, 26, 14, false, false, 2,
2); // Moldorm eye
} else if (id_ == 0x0A) {
DrawSpriteTile((x * 16), (y * 16), 0, 24, 8);
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 8, false, false, 1, 1);
} else if (id_ == 0x0B) {
DrawSpriteTile((x * 16), (y * 16), 10, 30, 10);
} else if (id_ == 0x0C) {
DrawSpriteTile((x * 16), (y * 16), 0, 24, 8);
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 8, false, false, 1, 1);
} else if (id_ == 0x0D) {
DrawSpriteTile((x * 16), (y * 16), 14, 28, 12);
} else if (id_ == 0x0E) {
DrawSpriteTile((x * 16), (y * 16), 8, 18, 10, false, false, 3, 2);
} else if (id_ == 0x0F) {
DrawSpriteTile((x * 16), (y * 16), 14, 24, 8, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 30, 8, 8, true, false, 1, 3);
} else if (id_ == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 12, 31, 8, false, false, 1, 1);
} else if (id_ == 0x11) {
DrawSpriteTile((x * 16), (y * 16) + 16, 6, 16, 8, false, false, 2,
2); // Feet
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 4, 18, 8, false, false, 2,
2); // Body1
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 4, 18, 8, true, false, 2,
2); // Body2
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8, false, false, 2,
2); // Head
} else if (id_ == 0x12) {
DrawSpriteTile((x * 16), (y * 16) + 8, 8, 26, 8);
DrawSpriteTile((x * 16), (y * 16), 6, 24, 8);
} else if (id_ == 0x13) {
DrawSpriteTile((x * 16), (y * 16), 4, 22, 2);
} else if (id_ == 0x15) {
// Antifairy
DrawSpriteTile((x * 16) + 2, (y * 16) + 8, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 2, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 14, (y * 16) + 8, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 14, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 1, 30, 5, false, false, 1,
1); // Middle
} else if (id_ == 0x16) {
DrawSpriteTile((x * 16), (y * 16) + 8, 2, 26, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 26, 2);
} else if (id_ == 0x17) // Bush hoarder
{
DrawSpriteTile((x * 16), (y * 16), 8, 30, 10);
} else if (id_ == 0x18) // Mini moldorm
{
DrawSpriteTile((x * 16) + 13, (y * 16) + 17, 13, 21, 8, false, false, 1,
1); // Tail
DrawSpriteTile((x * 16) + 5, (y * 16) + 8, 2, 22, 8); // Body
DrawSpriteTile((x * 16), (y * 16), 0, 22, 8); // Head
DrawSpriteTile((x * 16), (y * 16) - 4, 13, 20, 8, false, false, 1,
1); // Eyes
DrawSpriteTile((x * 16) - 4, (y * 16), 13, 20, 8, false, false, 1,
1); // Eyes
} else if (id_ == 0x19) // Poe - ghost
{
DrawSpriteTile((x * 16), (y * 16), 6, 31, 2); //
} else if (id_ == 0x1A) // Smith
{
// DrawSpriteTile((x*16), (y *16), 2, 4, 10,true); // Smitty
// DrawSpriteTile((x*16)+12, (y *16) - 7, 0, 6, 10); // Hammer
DrawSpriteTile((x * 16), (y * 16), 4, 22, 10);
} else if (id_ == 0x1C) // Statue
{
DrawSpriteTile((x * 16), (y * 16) + 8, 0, 28, 15);
DrawSpriteTile((x * 16), (y * 16), 2, 28, 15, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 28, 15, true, false, 1, 1);
} else if (id_ == 0x1E) // Crystal switch
{
DrawSpriteTile((x * 16), (y * 16), 4, 30, 5);
} else if (id_ == 0x1F) // Sick kid
{
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 10, 16, 14);
DrawSpriteTile((x * 16) + 16 - 8, (y * 16) + 8, 10, 16, 14, true);
DrawSpriteTile((x * 16) - 8, (y * 16) + 16, 10, 16, 14, false, true, 2, 2);
DrawSpriteTile((x * 16) + 16 - 8, (y * 16) + 16, 10, 16, 14, true, true, 2,
2);
DrawSpriteTile((x * 16), (y * 16) - 4, 14, 16, 10);
} else if (id_ == 0x20) {
DrawSpriteTile((x * 16), (y * 16), 2, 24, 7);
} else if (id_ == 0x21) // Push switch
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 20, 13, 29, 3, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) + 28, 12, 29, 3, false, false, 1, 1);
DrawSpriteTile((x * 16), (y * 16) + 8, 10, 28, 3);
} else if (id_ == 0x22) // Rope
{
DrawSpriteTile((x * 16), (y * 16), 8, 26, 5);
} else if (id_ == 0x23) // Red bari
{
DrawSpriteTile((x * 16), (y * 16), 2, 18, 4, false, false, 1, 2);
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 18, 4, true, false, 1, 2);
} else if (id_ == 0x24) // Blue bari
{
DrawSpriteTile((x * 16), (y * 16), 2, 18, 6, false, false, 1, 2);
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 18, 6, true, false, 1, 2);
} else if (id_ == 0x25) // Talking tree?
{
// TODO: Add something here?
} else if (id_ == 0x26) // Hardhat beetle
{
if ((x & 0x01) == 0x00) {
DrawSpriteTile((x * 16), (y * 16), 4, 20, 8);
DrawSpriteTile((x * 16), (y * 16) - 6, 0, 20, 8);
} else {
DrawSpriteTile((x * 16), (y * 16), 4, 20, 10);
DrawSpriteTile((x * 16), (y * 16) - 6, 0, 20, 10);
}
} else if (id_ == 0x27) // Deadrock
{
DrawSpriteTile((x * 16), (y * 16), 2, 30, 10);
} else if (id_ == 0x28) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x29) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2A) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2B) // ???
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2C) // Lumberjack
{
DrawSpriteTile((x * 16) - 24, (y * 16) + 12, 6, 26, 12, true); // Body
DrawSpriteTile((x * 16) - 24, (y * 16), 8, 26, 12, true); // Head
DrawSpriteTile((x * 16) - 14, (y * 16) + 12, 14, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) - 6, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 2, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 10, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 18, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 26, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 34, (y * 16) + 12, 14, 27, 10, true, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 40, (y * 16) + 12, 4, 26, 12); // Body
DrawSpriteTile((x * 16) + 40, (y * 16), 8, 26, 12); // Head
} else if (id_ == 0x2D) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2E) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2F) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x30) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x31) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x32) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
}
/*
else if (id_== 0x33) // Pull for rupees
{
}
*/
else if (id_ == 0x34) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x35) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x36) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
}
/*
else if (id_== 0x37) // Waterfall
{
DrawSpriteTile((x*16), (y *16), 14, 6, 10);
}
*/
else if (id_ == 0x38) // Arrowtarget
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x39) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3A) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3B) // Dash item
{
} else if (id_ == 0x3C) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3D) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3E) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3F) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x40) // Lightning lock (agah tower)
{
DrawSpriteTile((x * 16) - 24, (y * 16), 10, 28, 2, false, false, 1, 2);
DrawSpriteTile((x * 16) - 16, (y * 16), 6, 30, 2);
DrawSpriteTile((x * 16), (y * 16), 8, 30, 2);
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 30, 2);
DrawSpriteTile((x * 16) + 24, (y * 16), 10, 28, 2, false, false, 1, 2);
} else if (id_ == 0x41) // Blue soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 10, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 10, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 14, 22, 10, false, true, 1,
2); // Sword
} else if (id_ == 0x42) // Green soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 12, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 12, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 14, 22, 12, false, true, 1,
2); // Sword
} else if (id_ == 0x43) // Red spear soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 8, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 8);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 8, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 11, 22, 8, false, true, 1,
2); // Spear
} else if (id_ == 0x44) // Sword blue holding up
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 10);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 14, 22, 10, false, true, 1,
2); // Sword
} else if (id_ == 0x45) // Green spear soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 12, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 12, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 11, 22, 12, false, true, 1,
2); // Spear
} else if (id_ == 0x46) // Blue archer
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 10, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10); // Head
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 16, 10, false, false, 1,
1); // Bow1
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 10, 16, 10, true, false, 1,
1); // Bow2
} else if (id_ == 0x47) // Green archer
{
DrawSpriteTile((x * 16), (y * 16) + 8, 14, 16, 12);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 16, 12, false, false, 1,
1); // Bow1
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 10, 16, 12, true, false, 1,
1); // Bow2
} else if (id_ == 0x48) // Javelin soldier red
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 11, 22, 8, false, true, 1,
2); // Sword
} else if (id_ == 0x49) // Javelin soldier red from bush
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 18, 8); // Head
DrawSpriteTile((x * 16), (y * 16) + 24, 0, 20, 2);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 11, 22, 8, false, true, 1,
2); // Sword
} else if (id_ == 0x4A) // Red bomb soldier
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8); // Head
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 14, 22, 11); // Bomb
} else if (id_ == 0x4B) // Green soldier recruit
{
// 0,4
DrawSpriteTile((x * 16), (y * 16), 6, 24, 12);
DrawSpriteTile((x * 16), (y * 16) - 10, 0, 20, 12);
} else if (id_ == 0x4C) // Jazzhand
{
DrawSpriteTile((x * 16), (y * 16), 0, 26, 14, false, false, 6, 2);
} else if (id_ == 0x4D) // Rabit??
{
DrawSpriteTile((x * 16), (y * 16), 0, 26, 12, false, false, 6, 2);
} else if (id_ == 0x4E) // Popo1
{
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10);
} else if (id_ == 0x4F) // Popo2
{
DrawSpriteTile((x * 16), (y * 16), 2, 20, 10);
} else if (id_ == 0x50) // Canon ball
{
DrawSpriteTile((x * 16), (y * 16), 0, 24, 10);
} else if (id_ == 0x51) // Armos
{
DrawSpriteTile((x * 16), (y * 16), 0, 28, 11, false, false, 2, 4);
} else if (id_ == 0x53) // Armos Knight
{
DrawSpriteTile((x * 16), (y * 16), 0, 28, 10, false, false, 4, 4);
} else if (id_ == 0x54) {
DrawSpriteTile((x * 16), (y * 16), 2, 28, 12);
DrawSpriteTile((x * 16) + 8, (y * 16) + 10, 6, 28, 12);
DrawSpriteTile((x * 16) + 16, (y * 16) + 18, 10, 28, 12);
} else if (id_ == 0x55) // Fireball Zora
{
DrawSpriteTile((x * 16), (y * 16), 4, 26, 11);
} else if (id_ == 0x56) // Zora
{
DrawSpriteTile((x * 16), (y * 16), 10, 20, 2);
DrawSpriteTile((x * 16), (y * 16) + 8, 8, 30, 2);
} else if (id_ == 0x57) // Desert Rocks
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 2, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16), 14, 24, 2, true, false, 2, 4);
} else if (id_ == 0x58) // Crab
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 12);
DrawSpriteTile((x * 16) + 16, (y * 16), 14, 24, 12, true);
} else if (id_ == 0x5B) // Spark
{
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4);
} else if (id_ == 0x5C) // Spark
{
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4, true);
} else if (id_ == 0x5D) // Roller vertical1
{
// Subset3
if (((y * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16) + 8 + (i * 16), (y * 16), 9, 24, 11);
}
DrawSpriteTile((x * 16) + (16 * 7), (y * 16), 8, 24, 11, true);
} else {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 32, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 48, (y * 16), 8, 24, 11, true);
}
} else if (id_ == 0x5E) // Roller vertical2
{
// Subset3
if (((y * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16) + 8 + (i * 16), (y * 16), 9, 24, 11);
}
DrawSpriteTile((x * 16) + (16 * 7), (y * 16), 8, 24, 11, true);
} else {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 32, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 48, (y * 16), 8, 24, 11, true);
}
} else if (id_ == 0x5F) // Roller horizontal
{
if (((x * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + 16, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 48, 14, 24, 11, false, true);
} else {
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16), (y * 16) + i * 16, 14, 25, 11);
}
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + (7 * 16), 14, 24, 11, false, true);
}
} else if (id_ == 0x60) // Roller horizontal2 (right to left)
{
// Subset3
if (((x * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + 16, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 48, 14, 24, 11, false, true);
} else {
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16), (y * 16) + i * 16, 14, 25, 11);
}
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + (7 * 16), 14, 24, 11, false, true);
}
} else if (id_ == 0x61) // Beamos
{
DrawSpriteTile((x * 16), (y * 16) - 16, 8, 20, 14, false, false, 2, 4);
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 10, 20, 14, false, false, 1, 1);
} else if (id_ == 0x63) // Devalant non-shooter
{
DrawSpriteTile((x * 16) - 8, (y * 16) - 8, 2, 16, 2);
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 2, 16, 2, true);
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 2, 16, 2, false, true);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 2, 16, 2, true, true);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0x64) // Devalant non-shooter
{
DrawSpriteTile((x * 16) - 8, (y * 16) - 8, 2, 16, 2);
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 2, 16, 2, true);
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 2, 16, 2, false, true);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 2, 16, 2, true, true);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8);
} else if (id_ == 0x66) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 14, 16, 14, true);
} else if (id_ == 0x67) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 14, 16, 14);
} else if (id_ == 0x68) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 12, 16, 14);
} else if (id_ == 0x69) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 12, 16, 14, false, true);
} else if (id_ == 0x6A) // Chainball soldier
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 14);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 14, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 14); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) - 16, 10, 18, 14); // Ball
} else if (id_ == 0x6B) // Cannon soldier
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 14);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 14, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 14); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 4, 18, 14); // Cannon
} else if (id_ == 0x6C) // Mirror portal
{
// Useless
} else if (id_ == 0x6D) // Rat
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 5);
} else if (id_ == 0x6E) // Rope
{
DrawSpriteTile((x * 16), (y * 16), 10, 26, 5);
} else if (id_ == 0x6F) {
DrawSpriteTile((x * 16), (y * 16), 4, 24, 10);
} else if (id_ == 0x70) // Helma fireball
{
DrawSpriteTile((x * 16), (y * 16), 10, 28, 4);
} else if (id_ == 0x71) // Leever
{
DrawSpriteTile((x * 16), (y * 16), 6, 16, 4);
} else if (id_ == 0x73) // Uncle priest
{
} else if (id_ == 0x79) // Bee
{
DrawSpriteTile((x * 16), (y * 16), 4, 14, 11, false, false, 1, 1);
} else if (id_ == 0x7A) {
DrawSpriteTile((x * 16), (y * 16) - 16, 2, 24, 12, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 2, 24, 12, true, false, 2, 4);
} else if (id_ == 0x7C) // Skull head
{
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0x7D) // Big spike
{
DrawSpriteTile((x * 16), (y * 16), 4, 28, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 4, 28, 11, true);
DrawSpriteTile((x * 16), (y * 16) + 16, 4, 28, 11, false, true);
DrawSpriteTile((x * 16) + 16, (y * 16) + 16, 4, 28, 11, true, true);
} else if (id_ == 0x7E) // Guruguru clockwise
{
DrawSpriteTile((x * 16), (y * 16) - 14, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 28, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 42, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 56, 8, 18, 4);
} else if (id_ == 0x7F) // Guruguru Counterclockwise
{
DrawSpriteTile((x * 16), (y * 16) - 14, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 28, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 42, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 56, 8, 18, 4);
} else if (id_ == 0x80) // Winder (moving firebar)
{
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 14, (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 28, (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 42, (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 56, (y * 16), 8, 18, 4);
} else if (id_ == 0x81) // Water tektite
{
DrawSpriteTile((x * 16), (y * 16), 0, 24, 11);
} else if (id_ == 0x82) // circle antifairy
{
// Antifairy top
DrawSpriteTile((x * 16 + 2) - 4, (y * 16 + 8) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 2) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) - 4, (y * 16 + 8) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 14) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 8) - 16, 1, 30, 5, false, false,
1, 1); // Middle
// Left
DrawSpriteTile((x * 16 + 2) - 16, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 2) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) - 16, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 14) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 8) - 4, 1, 30, 5, false, false,
1, 1); // Middle
DrawSpriteTile((x * 16 + 2) - 4, (y * 16 + 8) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 2) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) - 4, (y * 16 + 8) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 14) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 8) + 8, 1, 30, 5, false, false,
1, 1); // Middle
// Left
DrawSpriteTile((x * 16 + 2) + 8, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 2) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) + 8, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 14) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 8) - 4, 1, 30, 5, false, false,
1, 1); // Middle
} else if (id_ == 0x83) // Green eyegore
{
DrawSpriteTile((x * 16), (y * 16), 12, 24, 14, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 14, true, false, 1, 3);
} else if (id_ == 0x84) // Red eyegore
{
DrawSpriteTile((x * 16), (y * 16), 12, 24, 8, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 8, true, false, 1, 3);
} else if (id_ == 0x85) // Yellow stalfos
{
DrawSpriteTile((x * 16), (y * 16), 10, 16, 11);
DrawSpriteTile((x * 16), (y * 16) - 12, 0, 16, 11); // Head
} else if (id_ == 0x86) // Kodongo
{
DrawSpriteTile((x * 16), (y * 16), 4, 26, 14);
} else if (id_ == 0x88) // Mothula
{
DrawSpriteTile((x * 16), (y * 16), 8, 24, 14, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16), 8, 24, 14, true, false, 2, 4);
} else if (id_ == 0x8A) // Spike
{
DrawSpriteTile((x * 16), (y * 16), 6, 30, 15);
} else if (id_ == 0x8B) // Gibdo
{
DrawSpriteTile((x * 16), (y * 16), 10, 24, 14);
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 24, 14);
} else if (id_ == 0x8C) // Arrghus
{
DrawSpriteTile((x * 16), (y * 16), 0, 24, 14, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16), 0, 24, 14, true, false, 2, 4);
} else if (id_ == 0x8D) // Arrghus spawn
{
DrawSpriteTile((x * 16), (y * 16), 6, 24, 14);
} else if (id_ == 0x8E) // Terrorpin
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 12);
} else if (id_ == 0x8F) // Slime
{
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
} else if (id_ == 0x90) // Wall master
{
DrawSpriteTile((x * 16), (y * 16), 6, 26, 12);
DrawSpriteTile((x * 16) + 16, (y * 16), 15, 26, 12, false, false, 1, 1);
DrawSpriteTile((x * 16) + 16, (y * 16) + 8, 9, 26, 12, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 27, 12, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 8, 27, 12, false, false, 1, 1);
} else if (id_ == 0x91) // Stalfos knight
{
DrawSpriteTile((x * 16) - 2, (y * 16) + 12, 4, 22, 12, false, false, 1, 2);
DrawSpriteTile((x * 16) + 10, (y * 16) + 12, 4, 22, 12, true, false, 1, 2);
DrawSpriteTile((x * 16) - 4, (y * 16) + 4, 1, 22, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 4, 3, 22, 12, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16) - 8, 6, 20, 12);
} else if (id_ == 0x92) // Helmaking
{
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 26, 14);
DrawSpriteTile((x * 16) + 16, (y * 16) + 32, 0, 28, 14);
DrawSpriteTile((x * 16) + 32, (y * 16) + 32, 14, 26, 14, true);
DrawSpriteTile((x * 16), (y * 16) + 16 + 32, 2, 28, 14);
DrawSpriteTile((x * 16) + 16, (y * 16) + 16 + 32, 4, 28, 14);
DrawSpriteTile((x * 16) + 32, (y * 16) + 16 + 32, 2, 28, 14, true);
DrawSpriteTile((x * 16) + 8, (y * 16) + 32 + 32, 6, 28, 14);
DrawSpriteTile((x * 16) + 24, (y * 16) + 32 + 32, 6, 28, 14, true);
} else if (id_ == 0x93) // Bumper
{
DrawSpriteTile((x * 16), (y * 16), 12, 30, 7);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 30, 7, true);
DrawSpriteTile((x * 16) + 16, (y * 16) + 16, 12, 30, 7, true, true);
DrawSpriteTile((x * 16), (y * 16) + 16, 12, 30, 7, false, true);
} else if (id_ == 0x95) // Right laser eye
{
DrawSpriteTile((x * 16), (y * 16), 9, 28, 3, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16) + 16, 9, 28, 3, true, true, 1, 1);
} else if (id_ == 0x96) // Left laser eye
{
DrawSpriteTile((x * 16) + 8, (y * 16) - 4, 9, 28, 3, false, false, 1, 2);
DrawSpriteTile((x * 16) + 8, (y * 16) + 12, 9, 28, 3, false, true, 1, 1);
} else if (id_ == 0x97) // Right laser eye
{
DrawSpriteTile((x * 16), (y * 16), 6, 28, 3, false, false, 2, 1);
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 28, 3, true, false, 1, 1);
} else if (id_ == 0x98) // Right laser eye
{
DrawSpriteTile((x * 16), (y * 16), 6, 28, 3, false, true, 2, 1);
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 28, 3, true, true, 1, 1);
} else if (id_ == 0x99) {
DrawSpriteTile((x * 16), (y * 16), 6, 24, 12);
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 24, 12);
} else if (id_ == 0x9A) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 10, 24, 6);
} else if (id_ == 0x9B) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 6, 24, 11);
DrawSpriteTile((x * 16), (y * 16) - 8, 2, 27, 11, false, false, 2, 1);
} else if (id_ == 0x9C) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 12, 22, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 13, 22, 11, false, false, 1, 2);
} else if (id_ == 0x9D) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 14, 21, 11);
DrawSpriteTile((x * 16), (y * 16) - 16, 14, 20, 11, false, false, 2, 1);
} else if (id_ == 0xA1) {
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 6, 26, 14);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 6, 26, 14, true);
} else if (id_ == 0xA2) {
DrawSpriteTile((x * 16), (y * 16) + 8, 0, 24, 14, false, false, 4, 4);
} else if (id_ == 0xA5) {
DrawSpriteTile((x * 16), (y * 16), 0, 26, 10, false, false, 3, 2);
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 0, 24, 10);
} else if (id_ == 0xA6) {
DrawSpriteTile((x * 16), (y * 16), 0, 26, 8, false, false, 3, 2);
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 0, 24, 8);
} else if (id_ == 0xA7) {
DrawSpriteTile((x * 16), (y * 16) + 12, 12, 16, 10);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0xAC) {
DrawSpriteTile((x * 16), (y * 16), 5, 14, 4);
} else if (id_ == 0xAD) {
DrawSpriteTile((x * 16), (y * 16) + 8, 14, 10, 10);
DrawSpriteTile((x * 16), (y * 16), 12, 10, 10);
} else if (id_ == 0xBA) {
DrawSpriteTile((x * 16), (y * 16), 14, 14, 6);
} else if (id_ == 0xC1) {
DrawSpriteTile((x * 16), (y * 16) - 16, 2, 24, 12, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 2, 24, 12, true, false, 2, 4);
} else if (id_ == 0xC3) {
DrawSpriteTile((x * 16), (y * 16), 10, 14, 12);
} else if (id_ == 0xC4) {
DrawSpriteTile((x * 16), (y * 16), 0, 18, 14);
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 16, 14);
} else if (id_ == 0xC5) {
} else if (id_ == 0xC6) {
DrawSpriteTile((x * 16) + 4, (y * 16) + 14, 3, 30, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) + 14, (y * 16) + 4, 3, 30, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) + 2, 1, 31, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) - 6, (y * 16) + 4, 3, 30, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) - 6, 3, 30, 14, false, false, 1, 1);
} else if (id_ == 0xC7) {
DrawSpriteTile((x * 16), (y * 16), 0, 26, 4);
DrawSpriteTile((x * 16), (y * 16) - 10, 0, 26, 4);
DrawSpriteTile((x * 16), (y * 16) - 20, 0, 26, 4);
DrawSpriteTile((x * 16), (y * 16) - 30, 2, 26, 4);
} else if (id_ == 0xC8) {
DrawSpriteTile((x * 16), (y * 16), 12, 24, 12, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 12, true, false, 1, 3);
} else if (id_ == 0xC9) {
DrawSpriteTile((x * 16), (y * 16), 8, 28, 8, false);
DrawSpriteTile((x * 16) + 16, (y * 16), 8, 28, 8, true);
} else if (id_ == 0xCA) {
DrawSpriteTile((x * 16), (y * 16), 8, 10, 10);
} else if (id_ == 0xD0) {
DrawSpriteTile((x * 16), (y * 16), 7, 14, 11, false, false, 3, 2);
DrawSpriteTile((x * 16), (y * 16) - 10, 8, 12, 11);
} else if (id_ == 0xD1) {
DrawSpriteTile((x * 16) + 2, (y * 16) + 8, 7, 13, 11, true, true, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 2, 7, 13, 11, true, false, 1, 1);
DrawSpriteTile((x * 16) + 14, (y * 16) + 8, 7, 13, 11, true, true, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 14, 7, 13, 11, false, true, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 7, 13, 11, false, false, 1,
1); // Middle
// 6,7 / 13
} else if (id_ == 0xD4) {
DrawSpriteTile((x * 16) - 4, (y * 16), 0, 7, 7, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16), 0, 7, 7, true, false, 1, 1);
} else if (id_ == 0xE3) // Fairy
{
DrawSpriteTile((x * 16), (y * 16), 10, 14, 10);
} else if (id_ == 0xE4) // Key
{
DrawSpriteTile((x * 16), (y * 16), 11, 06, 11, false, false, 1, 2);
} else if (id_ == 0xE7) // Mushroom
{
DrawSpriteTile((x * 16), (y * 16), 14, 30, 16);
} else if (id_ == 0xE8) // Fake ms
{
DrawSpriteTile((x * 16) + 4, (y * 16), 4, 31, 10, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 5, 31, 10, false, false, 1, 1);
} else if (id_ == 0xEB) {
DrawSpriteTile((x * 16), (y * 16), 0, 14, 5);
} else if (id_ == 0xF2) {
DrawSpriteTile((x * 16), (y * 16) - 16, 12, 24, 2, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 12, 24, 2, true, false, 2, 4);
} else if (id_ == 0xF4) {
DrawSpriteTile((x * 16), (y * 16), 12, 28, 5, false, false, 4, 4);
} else {
// stringtodraw.Add(new SpriteName(x, (y *16), sprites_name.name[id]));
DrawSpriteTile((x * 16), (y * 16), 4, 4, 5);
}
bounding_box_.x = (lowerX_ + (x * 16));
bounding_box_.y = (lowerY_ + (y * 16));
bounding_box_.w = width_;
bounding_box_.h = height_;
}
void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
bool mirror_x, bool mirror_y, int sizex, int sizey,
bool iskey) {
x += 16;
y += 16;
int drawid_ = (srcx + (srcy * 16)) + 512;
for (auto yl = 0; yl < sizey * 8; yl++) {
for (auto xl = 0; xl < (sizex * 8) / 2; xl++) {
int mx = xl;
int my = yl;
if (mirror_x) {
mx = (((sizex * 8) / 2) - 1) - xl;
}
if (mirror_y) {
my = (((sizey * 8)) - 1) - yl;
}
// Formula information to get tile index position in the array
//((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
int tx = ((drawid_ / 0x10) * 0x400) +
((drawid_ - ((drawid_ / 0x10) * 0x10)) * 8);
auto pixel = current_gfx_[tx + (yl * 0x80) + xl];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position
int index = (x) + (y * 64) + (mx + (my * 0x80));
if (index >= 0 && index <= 4096) {
preview_gfx_[index] = (uchar)((pixel & 0x0F) + 112 + (pal * 8));
}
}
}
}
} // namespace zelda3
} // namespace app
} // namespace yaze

68
src/app/zelda3/sprite.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef YAZE_APP_ZELDA3_SPRITE_H
#define YAZE_APP_ZELDA3_SPRITE_H
#include <SDL.h>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#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"
namespace yaze {
namespace app {
namespace zelda3 {
class Sprite {
public:
uchar x_, y_, id_;
uchar nx_, ny_;
uchar layer_ = 0;
uchar subtype_ = 0;
uchar overlord_ = 0;
std::string name_;
uchar keyDrop_ = 0;
int sizeMap_ = 512;
bool overworld_ = false;
bool preview_ = false;
uchar map_id_ = 0;
int map_x_ = 0;
int map_y_ = 0;
short room_id_ = 0;
bool picker_ = false;
bool selected_ = false;
SDL_Rect bounding_box_;
Bytes current_gfx_;
Bytes preview_gfx_;
int lowerX_ = 32;
int lowerY_ = 32;
int higherX_ = 0;
int higherY_ = 0;
int width_ = 16;
int height_ = 16;
Sprite(Bytes src, uchar mapid, uchar id, uchar x, uchar y, int map_x,
int map_y);
void updateBBox();
void Draw(bool picker = false);
void DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
bool mirror_x = false, bool mirror_y = false,
int sizex = 2, int sizey = 2, bool iskey = false);
auto PreviewGraphics() { return preview_gfx_; }
};
} // namespace zelda3
} // namespace app
} // namespace yaze
#endif

View File

@@ -0,0 +1,129 @@
#include "title_screen.h"
#include <cstdint>
#include "app/core/common.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace zelda3 {
void TitleScreen::Create() {
tiles8Bitmap.Create(128, 512, 8, 0x20000);
tilesBG1Bitmap.Create(256, 256, 8, 0x80000);
tilesBG2Bitmap.Create(256, 256, 8, 0x80000);
oamBGBitmap.Create(256, 256, 8, 0x80000);
BuildTileset();
LoadTitleScreen();
}
void TitleScreen::BuildTileset() {
uchar staticgfx[16];
// Main Blocksets
// TODO: get the gfx from the GFX class rather than the rom.
// for (int i = 0; i < 8; i++) {
// staticgfx[i] = GfxGroups.mainGfx[titleScreenTilesGFX][i];
// }
staticgfx[8] = 115 + 0;
// staticgfx[9] = (GfxGroups.spriteGfx[titleScreenSpritesGFX][3] + 115);
staticgfx[10] = 115 + 6;
staticgfx[11] = 115 + 7;
// staticgfx[12] = (GfxGroups.spriteGfx[titleScreenSpritesGFX][0] + 115);
staticgfx[13] = 112;
staticgfx[14] = 112;
staticgfx[15] = 112;
// Loaded gfx for the current screen (empty at this point)
uchar* currentmapgfx8Data = tiles8Bitmap.GetData();
// All gfx of the game pack of 2048 bytes (4bpp)
uchar* allgfxData = nullptr; // rom_.GetMasterGraphicsBin();
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 2048; j++) {
uchar mapByte = allgfxData[j + (staticgfx[i] * 2048)];
switch (i) {
case 0:
case 3:
case 4:
case 5:
mapByte += 0x88;
break;
}
currentmapgfx8Data[(i * 2048) + j] = mapByte; // Upload used gfx data
}
}
}
void TitleScreen::LoadTitleScreen() {
int pos =
(rom_[0x138C + 3] << 16) + (rom_[0x1383 + 3] << 8) + rom_[0x137A + 3];
for (int i = 0; i < 1024; i++) {
tilesBG1Buffer[i] = 492;
tilesBG2Buffer[i] = 492;
}
pos = core::SnesToPc(pos);
while ((rom_[pos] & 0x80) != 0x80) {
int dest_addr = pos; // $03 and $04
pos += 2;
short length = pos;
bool increment64 = (length & 0x8000) == 0x8000;
bool fixsource = (length & 0x4000) == 0x4000;
pos += 2;
length = (short)((length & 0x07FF));
int j = 0;
int jj = 0;
int posB = pos;
while (j < (length / 2) + 1) {
ushort tiledata = (ushort)pos;
if (dest_addr >= 0x1000) {
// destAddr -= 0x1000;
if (dest_addr < 0x2000) {
tilesBG1Buffer[dest_addr - 0x1000] = tiledata;
}
} else {
if (dest_addr < 0x1000) {
tilesBG2Buffer[dest_addr] = tiledata;
}
}
if (increment64) {
dest_addr += 32;
} else {
dest_addr++;
}
if (!fixsource) {
pos += 2;
}
jj += 2;
j++;
}
if (fixsource) {
pos += 2;
} else {
pos = posB + jj;
}
}
pal_selected_ = 2;
}
} // namespace zelda3
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,81 @@
#ifndef YAZE_APP_ZELDA3_SCREEN_H
#define YAZE_APP_ZELDA3_SCREEN_H
#include <cstdint>
#include "app/core/common.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace zelda3 {
class TitleScreen {
public:
void Create();
private:
void BuildTileset();
void LoadTitleScreen();
int sword_x_ = 0;
int mx_click_ = 0;
int my_click_ = 0;
int mx_dist_ = 0;
int my_dist_ = 0;
int last_x_ = 0;
int last_y_ = 0;
int x_in_ = 0;
int y_in_ = 0;
int dungmap_selected_tile_ = 0;
int dungmap_selected_ = 0;
int selected_palette_ = 0;
int total_floors_ = 0;
int current_floor_ = 0;
int num_basement_ = 0;
int num_floor_ = 0;
int selected_map_tile = 0;
int current_floor_rooms; // [1][];
int current_floor_gfx; // [1][];
int copied_data_rooms; // 25
int copied_data_gfx; // 25
int pal_selected_;
int addresses[7] = {0x53de4, 0x53e2c, 0x53e08, 0x53e50,
0x53e74, 0x53e98, 0x53ebc};
int addressesgfx[7] = {0x53ee0, 0x53f04, 0x53ef2, 0x53f16,
0x53f28, 0x53f3a, 0x53f4c};
ushort bossRoom = 0x000F;
ushort selected_tile = 0;
ushort tilesBG1Buffer[0x1000]; // 0x1000
ushort tilesBG2Buffer[0x1000]; // 0x1000
uchar mapdata; // 64 * 64
uchar dwmapdata; // 64 * 64
bool mDown = false;
bool swordSelected = false;
bool darkWorld = false;
bool currentDungeonChanged = false;
bool editedFromEditor = false;
bool mouseDown = false;
bool mdown = false;
ROM rom_;
gfx::OAMTile oam_data[10];
gfx::OAMTile selected_oam_tile;
gfx::OAMTile last_selected_oam_tile;
gfx::Bitmap tilesBG1Bitmap; // 0x80000
gfx::Bitmap tilesBG2Bitmap; // 0x80000
gfx::Bitmap oamBGBitmap; // 0x80000
gfx::Bitmap tiles8Bitmap; // 0x20000
};
} // namespace zelda3
} // namespace app
} // namespace yaze
#endif

199
src/gui/canvas.cc Normal file
View File

@@ -0,0 +1,199 @@
#include "canvas.h"
#include <imgui/imgui.h>
#include <cmath>
#include <string>
#include "app/gfx/bitmap.h"
#include "app/rom.h"
namespace yaze {
namespace gui {
void Canvas::DrawBackground(ImVec2 canvas_size) {
canvas_p0_ = ImGui::GetCursorScreenPos();
if (!custom_canvas_size_) canvas_sz_ = ImGui::GetContentRegionAvail();
if (canvas_size.x != 0) canvas_sz_ = canvas_size;
canvas_p1_ = ImVec2(canvas_p0_.x + canvas_sz_.x, canvas_p0_.y + canvas_sz_.y);
// Draw border and background color
draw_list_ = ImGui::GetWindowDrawList();
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, IM_COL32(32, 32, 32, 255));
draw_list_->AddRect(canvas_p0_, canvas_p1_, IM_COL32(255, 255, 255, 255));
}
void Canvas::DrawContextMenu() {
// This will catch our interactions
const ImGuiIO &io = ImGui::GetIO();
ImGui::InvisibleButton(
"canvas", canvas_sz_,
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
const bool is_active = ImGui::IsItemActive(); // Held
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x,
io.MousePos.y - origin.y);
// Pan (we use a zero mouse threshold when there's no context menu)
const float mouse_threshold_for_pan = enable_context_menu_ ? -1.0f : 0.0f;
if (is_active &&
ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
scrolling_.x += io.MouseDelta.x;
scrolling_.y += io.MouseDelta.y;
}
// Context menu (under default mouse threshold)
ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
if (enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f)
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
if (ImGui::BeginPopup("context")) {
ImGui::MenuItem("Show Grid", nullptr, &enable_grid_);
if (ImGui::MenuItem("Reset Position", nullptr, false)) {
scrolling_.x = 0;
scrolling_.y = 0;
}
if (ImGui::MenuItem("Remove all", nullptr, false, points_.Size > 0)) {
points_.clear();
}
ImGui::EndPopup();
}
}
void Canvas::DrawTilePainter(const Bitmap &bitmap, int size) {
const ImGuiIO &io = ImGui::GetIO();
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x,
io.MousePos.y - origin.y);
if (is_hovered) {
if (!points_.empty()) {
points_.clear();
}
ImVec2 draw_tile_outline_pos;
draw_tile_outline_pos.x =
std::floor((double)mouse_pos_in_canvas.x / size) * size;
draw_tile_outline_pos.y =
std::floor((double)mouse_pos_in_canvas.y / size) * size;
points_.push_back(draw_tile_outline_pos);
points_.push_back(
ImVec2(draw_tile_outline_pos.x + size, draw_tile_outline_pos.y + size));
if (bitmap.IsActive()) {
draw_list_->AddImage(
(void *)bitmap.GetTexture(),
ImVec2(origin.x + draw_tile_outline_pos.x,
origin.y + draw_tile_outline_pos.y),
ImVec2(origin.x + draw_tile_outline_pos.x + bitmap.GetWidth(),
origin.y + draw_tile_outline_pos.y + bitmap.GetHeight()));
}
} else {
points_.clear();
}
}
void Canvas::DrawTileSelector(int size) {
const ImGuiIO &io = ImGui::GetIO();
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x,
io.MousePos.y - origin.y);
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
if (!points_.empty()) {
points_.clear();
}
ImVec2 draw_tile_outline_pos;
draw_tile_outline_pos.x =
std::floor((double)mouse_pos_in_canvas.x / size) * size;
draw_tile_outline_pos.y =
std::floor((double)mouse_pos_in_canvas.y / size) * size;
points_.push_back(draw_tile_outline_pos);
points_.push_back(
ImVec2(draw_tile_outline_pos.x + size, draw_tile_outline_pos.y + size));
}
}
void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, bool ready) {
if (ready) {
draw_list_->AddImage(
(void *)bitmap.GetTexture(),
ImVec2(canvas_p0_.x + border_offset, canvas_p0_.y + border_offset),
ImVec2(canvas_p0_.x + (bitmap.GetWidth() * 2),
canvas_p0_.y + (bitmap.GetHeight() * 2)));
}
}
void Canvas::DrawBitmap(const Bitmap &bitmap, int x_offset, int y_offset) {
draw_list_->AddImage(
(void *)bitmap.GetTexture(),
ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
canvas_p0_.y + y_offset + scrolling_.y),
ImVec2(canvas_p0_.x + x_offset + scrolling_.x + (bitmap.GetWidth()),
canvas_p0_.y + y_offset + scrolling_.y + (bitmap.GetHeight())));
}
void Canvas::DrawOutline(int x, int y, int w, int h) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 255));
}
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRectFilled(origin, size,
IM_COL32(color.x, color.y, color.z, color.w));
}
void Canvas::DrawText(std::string text, int x, int y) {
draw_list_->AddText(
ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y),
IM_COL32(255, 255, 255, 255), text.data());
}
void Canvas::DrawGrid(float grid_step) {
// Draw grid + all lines in the canvas
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
if (enable_grid_) {
for (float x = fmodf(scrolling_.x, grid_step); x < canvas_sz_.x;
x += grid_step)
draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y),
ImVec2(canvas_p0_.x + x, canvas_p1_.y),
IM_COL32(200, 200, 200, 40));
for (float y = fmodf(scrolling_.y, grid_step); y < canvas_sz_.y;
y += grid_step)
draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y),
ImVec2(canvas_p1_.x, canvas_p0_.y + y),
IM_COL32(200, 200, 200, 40));
}
}
void Canvas::DrawOverlay() {
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
for (int n = 0; n < points_.Size; n += 2) {
draw_list_->AddRect(
ImVec2(origin.x + points_[n].x, origin.y + points_[n].y),
ImVec2(origin.x + points_[n + 1].x, origin.y + points_[n + 1].y),
IM_COL32(255, 255, 255, 255), 1.0f);
}
draw_list_->PopClipRect();
}
} // namespace gui
} // namespace yaze

67
src/gui/canvas.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef YAZE_GUI_CANVAS_H
#define YAZE_GUI_CANVAS_H
#include <imgui/imgui.h>
#include <cmath>
#include <string>
#include "app/gfx/bitmap.h"
#include "app/rom.h"
namespace yaze {
namespace gui {
using app::gfx::Bitmap;
class Canvas {
public:
Canvas() = default;
explicit Canvas(ImVec2 canvas_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size) {}
void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0));
void DrawContextMenu();
void DrawTilePainter(const Bitmap& bitmap, int size);
void DrawTileSelector(int size);
void DrawBitmap(const Bitmap& bitmap, int border_offset = 0,
bool ready = true);
void DrawBitmap(const Bitmap& bitmap, int x_offset, int y_offset);
void DrawOutline(int x, int y, int w, int h);
void DrawRect(int x, int y, int w, int h, ImVec4 color);
void DrawText(std::string text, int x, int y);
void DrawGrid(float grid_step = 64.0f);
void DrawOverlay(); // last
auto Points() const { return points_; }
auto GetDrawList() const { return draw_list_; }
auto GetZeroPoint() const { return canvas_p0_; }
void SetCanvasSize(ImVec2 canvas_size) {
canvas_sz_ = canvas_size;
custom_canvas_size_ = true;
}
private:
bool enable_grid_ = true;
bool enable_context_menu_ = true;
bool custom_canvas_size_ = false;
bool is_hovered_ = false;
ImDrawList* draw_list_;
ImVector<ImVec2> points_;
ImVec2 scrolling_;
ImVec2 canvas_sz_;
ImVec2 canvas_p0_;
ImVec2 canvas_p1_;
ImVec2 mouse_pos_in_canvas_;
std::vector<app::gfx::Bitmap> changed_tiles_;
app::gfx::Bitmap current_tile_;
std::string title_;
};
} // namespace gui
} // namespace yaze
#endif

84
src/gui/color.cc Normal file
View File

@@ -0,0 +1,84 @@
#include "color.h"
#include <imgui/imgui.h>
#include <cmath>
#include <string>
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
namespace yaze {
namespace gui {
void DisplayPalette(app::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[32] = {};
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;
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::BeginGroup(); // Lock X position
ImGui::Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) {
ImGui::PushID(n);
if ((n % 4) != 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::SameLine();
ImGui::ColorPicker4("##picker", (float*)&color,
misc_flags | ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview);
}
} // namespace gui
} // namespace yaze

20
src/gui/color.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef YAZE_GUI_COLOR_H
#define YAZE_GUI_COLOR_H
#include <imgui/imgui.h>
#include <cmath>
#include <string>
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
namespace yaze {
namespace gui {
void DisplayPalette(app::gfx::SNESPalette& palette, bool loaded);
} // namespace gui
} // namespace yaze
#endif

2192
src/gui/icons.h Normal file

File diff suppressed because it is too large Load Diff

70
src/gui/input.cc Normal file
View File

@@ -0,0 +1,70 @@
#include "input.h"
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "absl/strings/string_view.h"
namespace yaze {
namespace gui {
const int kStepOneHex = 0x01;
const int kStepFastHex = 0x0F;
bool InputHex(const char* label, int* data) {
return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
&kStepFastHex, "%06X",
ImGuiInputTextFlags_CharsHexadecimal);
}
bool InputHexShort(const char* label, int* data) {
return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
&kStepFastHex, "%06X",
ImGuiInputTextFlags_CharsHexadecimal);
}
void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
const ImVec2 lineStart = ImGui::GetCursorScreenPos();
const ImGuiStyle& style = ImGui::GetStyle();
float fullWidth = ImGui::GetContentRegionAvail().x;
float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
ImVec2 textSize = ImGui::CalcTextSize(title.begin(), title.end());
ImRect textRect;
textRect.Min = ImGui::GetCursorScreenPos();
if (flags & ItemLabelFlag::Right) textRect.Min.x = textRect.Min.x + itemWidth;
textRect.Max = textRect.Min;
textRect.Max.x += fullWidth - itemWidth;
textRect.Max.y += textSize.y;
ImGui::SetCursorScreenPos(textRect.Min);
ImGui::AlignTextToFramePadding();
// Adjust text rect manually because we render it directly into a drawlist
// instead of using public functions.
textRect.Min.y += window->DC.CurrLineTextBaseOffset;
textRect.Max.y += window->DC.CurrLineTextBaseOffset;
ImGui::ItemSize(textRect);
if (ImGui::ItemAdd(
textRect, window->GetID(title.data(), title.data() + title.size()))) {
ImGui::RenderTextEllipsis(
ImGui::GetWindowDrawList(), textRect.Min, textRect.Max, textRect.Max.x,
textRect.Max.x, title.data(), title.data() + title.size(), &textSize);
if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
}
if (flags & ItemLabelFlag::Left) {
ImVec2 result;
auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
result.x = textRect.Max.x - other.x;
result.y = textRect.Max.y - other.y;
ImGui::SetCursorScreenPos(result);
ImGui::SameLine();
} else if (flags & ItemLabelFlag::Right)
ImGui::SetCursorScreenPos(lineStart);
}
} // namespace gui
} // namespace yaze

29
src/gui/input.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef YAZE_APP_CORE_INPUT_H
#define YAZE_APP_CORE_INPUT_H
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <cstddef>
#include <cstdint>
#include "absl/strings/string_view.h"
namespace yaze {
namespace gui {
IMGUI_API bool InputHex(const char* label, int* data);
IMGUI_API bool InputHexShort(const char* label, int* data);
using ItemLabelFlags = enum ItemLabelFlag {
Left = 1u << 0u,
Right = 1u << 1u,
Default = Left,
};
IMGUI_API void ItemLabel(absl::string_view title, ItemLabelFlags flags);
} // namespace gui
} // namespace yaze
#endif

108
src/gui/style.cc Normal file
View File

@@ -0,0 +1,108 @@
#include "style.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
namespace yaze {
namespace gui {
void ColorsYaze() {
ImGuiStyle *style = &ImGui::GetStyle();
ImVec4 *colors = style->Colors;
style->WindowPadding = ImVec2(10.f, 10.f);
style->FramePadding = ImVec2(10.f, 3.f);
style->CellPadding = ImVec2(4.f, 5.f);
style->ItemSpacing = ImVec2(10.f, 5.f);
style->ItemInnerSpacing = ImVec2(5.f, 5.f);
style->TouchExtraPadding = ImVec2(0.f, 0.f);
style->IndentSpacing = 20.f;
style->ScrollbarSize = 14.f;
style->GrabMinSize = 15.f;
style->WindowBorderSize = 0.f;
style->ChildBorderSize = 1.f;
style->PopupBorderSize = 1.f;
style->FrameBorderSize = 0.f;
style->TabBorderSize = 0.f;
style->WindowRounding = 0.f;
style->ChildRounding = 0.f;
style->FrameRounding = 5.f;
style->PopupRounding = 0.f;
style->ScrollbarRounding = 5.f;
auto alttpDarkGreen = ImVec4(0.18f, 0.26f, 0.18f, 1.0f);
auto alttpMidGreen = ImVec4(0.28f, 0.36f, 0.28f, 1.0f);
auto allttpLightGreen = ImVec4(0.36f, 0.45f, 0.36f, 1.0f);
auto allttpLightestGreen = ImVec4(0.49f, 0.57f, 0.49f, 1.0f);
colors[ImGuiCol_MenuBarBg] = alttpDarkGreen;
colors[ImGuiCol_TitleBg] = alttpMidGreen;
colors[ImGuiCol_Header] = alttpDarkGreen;
colors[ImGuiCol_HeaderHovered] = allttpLightGreen;
colors[ImGuiCol_HeaderActive] = alttpMidGreen;
colors[ImGuiCol_TitleBgActive] = alttpDarkGreen;
colors[ImGuiCol_TitleBgCollapsed] = alttpMidGreen;
colors[ImGuiCol_Tab] = alttpDarkGreen;
colors[ImGuiCol_TabHovered] = alttpMidGreen;
colors[ImGuiCol_TabActive] = ImVec4(0.347f, 0.466f, 0.347f, 1.000f);
colors[ImGuiCol_Button] = alttpMidGreen;
colors[ImGuiCol_ButtonHovered] = allttpLightestGreen;
colors[ImGuiCol_ButtonActive] = allttpLightGreen;
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.36f, 0.45f, 0.36f, 0.60f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.36f, 0.45f, 0.36f, 0.30f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.36f, 0.45f, 0.36f, 0.40f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.36f, 0.45f, 0.36f, 0.60f);
colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.85f);
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);
colors[ImGuiCol_Border] = allttpLightGreen;
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.28f, 0.36f, 0.28f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.36f, 0.28f, 0.69f);
colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f);
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.36f, 0.45f, 0.36f, 0.60f);
colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 0.60f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.10f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f);
colors[ImGuiCol_TabUnfocused] =
ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f);
colors[ImGuiCol_TabUnfocusedActive] =
ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f);
colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = alttpDarkGreen;
colors[ImGuiCol_TableBorderStrong] = alttpMidGreen;
colors[ImGuiCol_TableBorderLight] =
ImVec4(0.26f, 0.26f, 0.28f, 1.00f); // Prefer using Alpha=1.0 here
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered];
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
}
} // namespace gui
} // namespace yaze

15
src/gui/style.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef YAZE_APP_CORE_STYLE_H
#define YAZE_APP_CORE_STYLE_H
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
namespace yaze {
namespace gui {
void ColorsYaze();
} // namespace gui
} // namespace yaze
#endif

68
src/gui/widgets.cc Normal file
View File

@@ -0,0 +1,68 @@
#include "widgets.h"
#include <ImGuiColorTextEdit/TextEditor.h>
#include "absl/status/status.h"
#include "app/core/constants.h"
namespace yaze {
namespace gui {
namespace widgets {
TextEditor::LanguageDefinition GetAssemblyLanguageDef() {
TextEditor::LanguageDefinition language_65816;
for (auto &k : app::core::kKeywords) language_65816.mKeywords.emplace(k);
for (auto &k : app::core::kIdentifiers) {
TextEditor::Identifier id;
id.mDeclaration = "Built-in function";
language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id));
}
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[ \\t]*#[ \\t]*[a-zA-Z_]+", TextEditor::PaletteIndex::Preprocessor));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"L?\\\"(\\\\.|[^\\\"])*\\\"", TextEditor::PaletteIndex::String));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"\\'\\\\?[^\\']\\'", TextEditor::PaletteIndex::CharLiteral));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?",
TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[+-]?[0-9]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"0[0-7]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?",
TextEditor::PaletteIndex::Number));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[a-zA-Z_][a-zA-Z0-9_]*", TextEditor::PaletteIndex::Identifier));
language_65816.mTokenRegexStrings.push_back(
std::make_pair<std::string, TextEditor::PaletteIndex>(
"[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/"
"\\;\\,\\.]",
TextEditor::PaletteIndex::Punctuation));
language_65816.mCommentStart = "/*";
language_65816.mCommentEnd = "*/";
language_65816.mSingleLineComment = ";";
language_65816.mCaseSensitive = false;
language_65816.mAutoIndentation = true;
language_65816.mName = "65816";
return language_65816;
}
} // namespace widgets
} // namespace gui
} // namespace yaze

19
src/gui/widgets.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef YAZE_GUI_WIDGETS_H
#define YAZE_GUI_WIDGETS_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include "absl/status/status.h"
#include "app/core/constants.h"
namespace yaze {
namespace gui {
namespace widgets {
TextEditor::LanguageDefinition GetAssemblyLanguageDef();
} // namespace widgets
} // namespace gui
} // namespace yaze
#endif

1
src/lib/SDL Submodule

Submodule src/lib/SDL added at 5b904a103a

1
src/lib/SDL_mixer Submodule

Submodule src/lib/SDL_mixer added at 7f73f724f2

1
src/lib/abseil-cpp Submodule

Submodule src/lib/abseil-cpp added at 8c0b94e793

1
src/lib/asar Submodule

Submodule src/lib/asar added at 634d6baf7a

1
src/lib/imgui Submodule

Submodule src/lib/imgui added at e57871bb95

View File

@@ -0,0 +1,742 @@
// Mini memory editor for Dear ImGui (to embed in your game/tools)
// Get latest version at http://www.github.com/ocornut/imgui_club
//
// Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity!
// If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before calling this.
//
// Usage:
// // Create a window and draw memory editor inside it:
// static MemoryEditor mem_edit_1;
// static char data[0x10000];
// size_t data_size = 0x10000;
// mem_edit_1.DrawWindow("Memory Editor", data, data_size);
//
// Usage:
// // If you already have a window, use DrawContents() instead:
// static MemoryEditor mem_edit_2;
// ImGui::Begin("MyWindow")
// mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this);
// ImGui::End();
//
// Changelog:
// - v0.10: initial version
// - v0.23 (2017/08/17): added to github. fixed right-arrow triggering a byte write.
// - v0.24 (2018/06/02): changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61).
// - v0.25 (2018/07/11): fixed wording: all occurrences of "Rows" renamed to "Columns".
// - v0.26 (2018/08/02): fixed clicking on hex region
// - v0.30 (2018/08/02): added data preview for common data types
// - v0.31 (2018/10/10): added OptUpperCaseHex option to select lower/upper casing display [@samhocevar]
// - v0.32 (2018/10/10): changed signatures to use void* instead of unsigned char*
// - v0.33 (2018/10/10): added OptShowOptions option to hide all the interactive option setting.
// - v0.34 (2019/05/07): binary preview now applies endianness setting [@nicolasnoble]
// - v0.35 (2020/01/29): using ImGuiDataType available since Dear ImGui 1.69.
// - v0.36 (2020/05/05): minor tweaks, minor refactor.
// - v0.40 (2020/10/04): fix misuse of ImGuiListClipper API, broke with Dear ImGui 1.79. made cursor position appears on left-side of edit box. option popup appears on mouse release. fix MSVC warnings where _CRT_SECURE_NO_WARNINGS wasn't working in recent versions.
// - v0.41 (2020/10/05): fix when using with keyboard/gamepad navigation enabled.
// - v0.42 (2020/10/14): fix for . character in ASCII view always being greyed out.
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
//
// Todo/Bugs:
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
#pragma once
#include <stdio.h> // sprintf, scanf
#include <stdint.h> // uint8_t, etc.
#ifdef _MSC_VER
#define _PRISizeT "I"
#define ImSnprintf _snprintf
#else
#define _PRISizeT "z"
#define ImSnprintf snprintf
#endif
#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif
struct MemoryEditor
{
enum DataFormat
{
DataFormat_Bin = 0,
DataFormat_Dec = 1,
DataFormat_Hex = 2,
DataFormat_COUNT
};
// Settings
bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using DrawWindow().
bool ReadOnly; // = false // disable any editing.
int Cols; // = 16 // number of columns to display.
bool OptShowOptions; // = true // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them.
bool OptShowDataPreview; // = false // display a footer previewing the decimal/binary/hex/float representation of the currently selected bytes.
bool OptShowHexII; // = false // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X".
bool OptShowAscii; // = true // display ASCII representation on the right side.
bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color.
bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff".
int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols.
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes.
ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
// [Internal State]
bool ContentsWidthChanged;
size_t DataPreviewAddr;
size_t DataEditingAddr;
bool DataEditingTakeFocus;
char DataInputBuf[32];
char AddrInputBuf[32];
size_t GotoAddr;
size_t HighlightMin, HighlightMax;
int PreviewEndianess;
ImGuiDataType PreviewDataType;
MemoryEditor()
{
// Settings
Open = true;
ReadOnly = false;
Cols = 16;
OptShowOptions = true;
OptShowDataPreview = false;
OptShowHexII = false;
OptShowAscii = true;
OptGreyOutZeroes = true;
OptUpperCaseHex = true;
OptMidColsCount = 8;
OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = NULL;
WriteFn = NULL;
HighlightFn = NULL;
// State/Internals
ContentsWidthChanged = false;
DataPreviewAddr = DataEditingAddr = (size_t)-1;
DataEditingTakeFocus = false;
memset(DataInputBuf, 0, sizeof(DataInputBuf));
memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
GotoAddr = (size_t)-1;
HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0;
PreviewDataType = ImGuiDataType_S32;
}
void GotoAddrAndHighlight(size_t addr_min, size_t addr_max)
{
GotoAddr = addr_min;
HighlightMin = addr_min;
HighlightMax = addr_max;
}
struct Sizes
{
int AddrDigitsCount;
float LineHeight;
float GlyphWidth;
float HexCellWidth;
float SpacingBetweenMidCols;
float PosHexStart;
float PosHexEnd;
float PosAsciiStart;
float PosAsciiEnd;
float WindowWidth;
Sizes() { memset(this, 0, sizeof(*this)); }
};
void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr)
{
ImGuiStyle& style = ImGui::GetStyle();
s.AddrDigitsCount = OptAddrDigitsCount;
if (s.AddrDigitsCount == 0)
for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4)
s.AddrDigitsCount++;
s.LineHeight = ImGui::GetTextLineHeight();
s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
if (OptShowAscii)
{
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
}
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
}
// Standalone Memory Editor window
void DrawWindow(const char* title, void* mem_data, size_t mem_size, size_t base_display_addr = 0x0000)
{
Sizes s;
CalcSizes(s, mem_size, base_display_addr);
ImGui::SetNextWindowSize(ImVec2(s.WindowWidth, s.WindowWidth * 0.60f), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX));
Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
{
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("context");
DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged)
{
CalcSizes(s, mem_size, base_display_addr);
ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y));
}
}
ImGui::End();
}
// Memory Editor contents only
void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000)
{
if (Cols < 1)
Cols = 1;
ImU8* mem_data = (ImU8*)mem_data_void;
Sizes s;
CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle();
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y;
float footer_height = OptFooterExtraHeight;
if (OptShowOptions)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false;
if (ReadOnly || DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1;
size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0;
size_t data_editing_addr_next = (size_t)-1;
if (DataEditingAddr != (size_t)-1)
{
// Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
}
// Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii)
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: ";
const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x";
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
{
size_t addr = (size_t)(line_i * Cols);
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n;
if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x);
// Draw highlight
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr));
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
{
ImVec2 pos = ImGui::GetCursorScreenPos();
float highlight_width = s.GlyphWidth * 2;
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1)));
if (is_next_byte_highlighted || (n + 1 == Cols))
{
highlight_width = s.HexCellWidth;
if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0)
highlight_width += s.SpacingBetweenMidCols;
}
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor);
}
if (DataEditingAddr == addr)
{
// Display text input on current byte
bool data_write = false;
ImGui::PushID((void*)addr);
if (DataEditingTakeFocus)
{
ImGui::SetKeyboardFocusHere(0);
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr);
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
}
struct UserData
{
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
static int Callback(ImGuiInputTextCallbackData* data)
{
UserData* user_data = (UserData*)data->UserData;
if (!data->HasSelection())
user_data->CursorPos = data->CursorPos;
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
{
// When not editing a byte, always refresh its InputText content pulled from underlying memory data
// (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there)
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, user_data->CurrentBufOverwrite);
data->SelectionStart = 0;
data->SelectionEnd = 2;
data->CursorPos = 0;
}
return 0;
}
char CurrentBufOverwrite[3]; // Input
int CursorPos; // Output
};
UserData user_data;
user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
#if IMGUI_VERSION_NUM >= 18104
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
#else
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
#endif
ImGui::SetNextItemWidth(s.GlyphWidth * 2);
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data))
data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false;
if (user_data.CursorPos >= 2)
data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false;
unsigned int data_input_value = 0;
if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
{
if (WriteFn)
WriteFn(mem_data, addr, (ImU8)data_input_value);
else
mem_data[addr] = (ImU8)data_input_value;
}
ImGui::PopID();
}
else
{
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
if (OptShowHexII)
{
if ((b >= 32 && b < 128))
ImGui::Text(".%c ", b);
else if (b == 0xFF && OptGreyOutZeroes)
ImGui::TextDisabled("## ");
else if (b == 0x00)
ImGui::Text(" ");
else
ImGui::Text(format_byte_space, b);
}
else
{
if (b == 0 && OptGreyOutZeroes)
ImGui::TextDisabled("00 ");
else
ImGui::Text(format_byte_space, b);
}
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0))
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
}
}
if (OptShowAscii)
{
// Draw ASCII values
ImGui::SameLine(s.PosAsciiStart);
ImVec2 pos = ImGui::GetCursorScreenPos();
addr = line_i * Cols;
ImGui::PushID(line_i);
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight)))
{
DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth);
DataEditingTakeFocus = true;
}
ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{
if (addr == DataEditingAddr)
{
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
pos.x += s.GlyphWidth;
}
}
}
ImGui::PopStyleVar(2);
ImGui::EndChild();
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImGui::SetCursorPosX(s.WindowWidth);
if (data_next && DataEditingAddr + 1 < mem_size)
{
DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1;
DataEditingTakeFocus = true;
}
else if (data_editing_addr_next != (size_t)-1)
{
DataEditingAddr = DataPreviewAddr = data_editing_addr_next;
DataEditingTakeFocus = true;
}
const bool lock_show_data_preview = OptShowDataPreview;
if (OptShowOptions)
{
ImGui::Separator();
DrawOptionsLine(s, mem_data, mem_size, base_display_addr);
}
if (lock_show_data_preview)
{
ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
}
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("context");
if (ImGui::BeginPopup("context"))
{
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
ImGui::Checkbox("Show Data Preview", &OptShowDataPreview);
ImGui::Checkbox("Show HexII", &OptShowHexII);
if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; }
ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes);
ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex);
ImGui::EndPopup();
}
ImGui::SameLine();
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
ImGui::SameLine();
ImGui::SetNextItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f);
if (ImGui::InputText("##addr", AddrInputBuf, IM_ARRAYSIZE(AddrInputBuf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue))
{
size_t goto_addr;
if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1)
{
GotoAddr = goto_addr - base_display_addr;
HighlightMin = HighlightMax = (size_t)-1;
}
}
if (GotoAddr != (size_t)-1)
{
if (GotoAddr < mem_size)
{
ImGui::BeginChild("##scrolling");
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
}
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(base_display_addr);
ImU8* mem_data = (ImU8*)mem_data_void;
ImGuiStyle& style = ImGui::GetStyle();
ImGui::AlignTextToFramePadding();
ImGui::Text("Preview as:");
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{
for (int n = 0; n < ImGuiDataType_COUNT; n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
PreviewDataType = (ImGuiDataType)n;
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0");
char buf[128] = "";
float x = s.GlyphWidth * 6.0f;
bool has_value = DataPreviewAddr != (size_t)-1;
if (has_value)
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf));
ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
if (has_value)
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf));
ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
if (has_value)
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf));
buf[IM_ARRAYSIZE(buf) - 1] = 0;
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
}
// Utilities for Data Preview
const char* DataTypeGetDesc(ImGuiDataType data_type) const
{
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
return descs[data_type];
}
size_t DataTypeGetSize(ImGuiDataType data_type) const
{
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
return sizes[data_type];
}
const char* DataFormatGetDesc(DataFormat data_format) const
{
const char* descs[] = { "Bin", "Dec", "Hex" };
IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT);
return descs[data_format];
}
bool IsBigEndian() const
{
uint16_t x = 1;
char c[2];
memcpy(c, &x, 2);
return c[0] != 0;
}
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
}
else
{
return memcpy(_dst, _src, s);
}
}
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
return memcpy(_dst, _src, s);
}
else
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
}
}
void* EndianessCopy(void* dst, void* src, size_t size) const
{
static void* (*fp)(void*, void*, size_t, int) = NULL;
if (fp == NULL)
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianess);
}
const char* FormatBinary(const uint8_t* buf, int width) const
{
IM_ASSERT(width <= 64);
size_t out_n = 0;
static char out_buf[64 + 8 + 1];
int n = width / 8;
for (int j = n - 1; j >= 0; --j)
{
for (int i = 0; i < 8; ++i)
out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0';
out_buf[out_n++] = ' ';
}
IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf));
out_buf[out_n] = 0;
return out_buf;
}
// [Internal]
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
{
uint8_t buf[8];
size_t elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
if (ReadFn)
for (int i = 0, n = (int)size; i < n; ++i)
buf[i] = ReadFn(mem_data, addr + i);
else
memcpy(buf, mem_data + addr, size);
if (data_format == DataFormat_Bin)
{
uint8_t binbuf[8];
EndianessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return;
}
out_buf[0] = 0;
switch (data_type)
{
case ImGuiDataType_S8:
{
int8_t int8 = 0;
EndianessCopy(&int8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; }
break;
}
case ImGuiDataType_U8:
{
uint8_t uint8 = 0;
EndianessCopy(&uint8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; }
break;
}
case ImGuiDataType_S16:
{
int16_t int16 = 0;
EndianessCopy(&int16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; }
break;
}
case ImGuiDataType_U16:
{
uint16_t uint16 = 0;
EndianessCopy(&uint16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; }
break;
}
case ImGuiDataType_S32:
{
int32_t int32 = 0;
EndianessCopy(&int32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; }
break;
}
case ImGuiDataType_U32:
{
uint32_t uint32 = 0;
EndianessCopy(&uint32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; }
break;
}
case ImGuiDataType_S64:
{
int64_t int64 = 0;
EndianessCopy(&int64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; }
break;
}
case ImGuiDataType_U64:
{
uint64_t uint64 = 0;
EndianessCopy(&uint64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; }
break;
}
case ImGuiDataType_Float:
{
float float32 = 0.0f;
EndianessCopy(&float32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; }
break;
}
case ImGuiDataType_Double:
{
double float64 = 0.0;
EndianessCopy(&float64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; }
break;
}
case ImGuiDataType_COUNT:
break;
} // Switch
IM_ASSERT(0); // Shouldn't reach
}
};
#undef _PRISizeT
#undef ImSnprintf
#ifdef _MSC_VER
#pragma warning (pop)
#endif

1
src/lib/snes_spc Submodule

Submodule src/lib/snes_spc added at ec8ee2bbe3

1
src/lib/sneshacking Submodule

Submodule src/lib/sneshacking added at 3cf5ab8681

BIN
src/yaze.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

1
src/yaze.rc Normal file
View File

@@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "yaze.ico"

BIN
src/yaze.res Normal file

Binary file not shown.

49
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,49 @@
# GoogleTest ------------------------------------------------------------------------------------
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(
yaze_test
yaze_test.cc
rom_test.cc
../src/app/rom.cc
../src/app/gfx/bitmap.cc
../src/app/gfx/snes_tile.cc
../src/app/gfx/snes_palette.cc
../src/app/core/common.cc
${ASAR_STATIC_SRC}
)
target_include_directories(
yaze_test PUBLIC
../src/
../src/lib/
../src/lib/asar/src/asar/
${SDL_INCLUDE_DIRS}
)
target_link_libraries(
yaze_test
SDL2
${ABSL_TARGETS}
${OPENGL_LIBRARIES}
${CMAKE_DL_LIBS}
asar-static
gmock_main
gmock
gtest_main
gtest
)
target_compile_definitions(yaze_test PRIVATE "linux")
target_compile_definitions(yaze_test PRIVATE "stricmp=strcasecmp")
include(GoogleTest)
gtest_discover_tests(yaze_test)

67
test/asm_test.cc Normal file
View File

@@ -0,0 +1,67 @@
#include <asar/interface-lib.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <array>
#include <cstdint>
#include <fstream>
#include <sstream>
#include <string>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "app/asm/script.h"
#include "app/core/constants.h"
#include "app/rom.h"
namespace yaze_test {
namespace asm_test {
using yaze::app::ROM;
using yaze::app::snes_asm::Script;
using ::testing::_;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Return;
class MockScript : public Script {
public:
MOCK_METHOD(absl::Status, ApplyPatchToROM, (ROM & rom));
MOCK_METHOD(absl::Status, PatchOverworldMosaic,
(ROM & rom, char mosaic_tiles[yaze::app::core::kNumOverworldMaps],
int routine_offset, int hook_offset));
};
TEST(ASMTest, ApplyMosaicChangePatchOk) {
ROM rom;
MockScript script;
char mosaic_tiles[yaze::app::core::kNumOverworldMaps];
EXPECT_CALL(script, PatchOverworldMosaic(_, Eq(mosaic_tiles),
Eq(0x1301D0 + 0x138000), 0))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(script, ApplyPatchToROM(_)).WillOnce(Return(absl::OkStatus()));
EXPECT_THAT(
script.PatchOverworldMosaic(rom, mosaic_tiles, 0x1301D0 + 0x138000, 0),
absl::OkStatus());
EXPECT_THAT(script.ApplyPatchToROM(rom), absl::OkStatus());
}
TEST(ASMTest, NoPatchLoadedError) {
ROM rom;
MockScript script;
EXPECT_CALL(script, ApplyPatchToROM(_))
.WillOnce(Return(absl::InvalidArgumentError("No patch loaded!")));
EXPECT_THAT(script.ApplyPatchToROM(rom),
absl::InvalidArgumentError("No patch loaded!"));
}
} // namespace asm_test
} // namespace yaze_test

74
test/delta_test.cc Normal file
View File

@@ -0,0 +1,74 @@
#include <asar/interface-lib.h>
#include <gmock/gmock.h>
#include <google/protobuf/repeated_field.h>
#include <gtest/gtest.h>
#include <array>
#include <cstdint>
#include <fstream>
#include <sstream>
#include <string>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "app/core/constants.h"
#include "app/delta/client.h"
#include "app/delta/service.h"
#include "app/rom.h"
#include "src/app/delta/delta.grpc.pb.h"
#include "src/app/delta/delta.pb.h"
namespace yaze_test {
namespace delta_test {
TEST(DeltaTest, InitRepoAndPushOk) {
yaze::app::delta::DeltaService service;
yaze::app::ROM rom;
Bytes test_bytes;
test_bytes.push_back(0x40);
EXPECT_TRUE(rom.LoadFromBytes(test_bytes).ok());
grpc::ServerContext* context;
InitRequest init_request;
auto repo = init_request.mutable_repo();
repo->set_project_name("test_repo");
Branch branch;
branch.set_branch_name("test_branch");
auto new_mutable_commits = branch.mutable_commits();
new_mutable_commits->Reserve(5);
for (int i = 0; i < 5; ++i) {
auto new_commit = new Commit();
new_commit->set_commit_id(i);
new_mutable_commits->Add();
new_mutable_commits->at(i) = *new_commit;
}
auto mutable_tree = repo->mutable_tree();
mutable_tree->Add();
mutable_tree->at(0) = branch;
InitResponse init_response;
auto init_status = service.Init(context, &init_request, &init_response);
EXPECT_TRUE(init_status.ok());
PushRequest request;
request.set_branch_name("test_branch");
request.set_repository_name("test_repo");
auto mutable_commits = request.mutable_commits();
mutable_commits->Reserve(5);
for (int i = 0; i < 5; ++i) {
auto new_commit = new Commit();
new_commit->set_commit_id(i * 2);
mutable_commits->Add();
mutable_commits->at(i) = *new_commit;
}
PushResponse reply;
auto status = service.Push(context, &request, &reply);
EXPECT_TRUE(status.ok());
auto repos = service.Repos();
auto result_branch = repos.at(0).tree();
std::cerr << result_branch.at(0).DebugString() << std::endl;
}
} // namespace delta_test
} // namespace yaze_test

315
test/rom_test.cc Normal file
View File

@@ -0,0 +1,315 @@
#include "app/rom.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <array>
#include "absl/status/statusor.h"
#define BUILD_HEADER(command, length) (command << 5) + (length - 1)
namespace yaze_test {
namespace rom_test {
using yaze::app::CompressionPiece;
using yaze::app::ROM;
using ::testing::ElementsAreArray;
using ::testing::TypedEq;
namespace {
Bytes ExpectCompressOk(ROM& rom, uchar* in, int in_size) {
auto load_status = rom.LoadFromPointer(in, in_size);
EXPECT_TRUE(load_status.ok());
auto compression_status = rom.Compress(0, in_size);
EXPECT_TRUE(compression_status.ok());
auto compressed_bytes = std::move(*compression_status);
return compressed_bytes;
}
Bytes ExpectDecompressBytesOk(ROM& rom, Bytes& in) {
auto load_status = rom.LoadFromBytes(in);
EXPECT_TRUE(load_status.ok());
auto decompression_status = rom.Decompress(0, in.size());
EXPECT_TRUE(decompression_status.ok());
auto decompressed_bytes = std::move(*decompression_status);
return decompressed_bytes;
}
Bytes ExpectDecompressOk(ROM& rom, uchar* in, int in_size) {
auto load_status = rom.LoadFromPointer(in, in_size);
EXPECT_TRUE(load_status.ok());
auto decompression_status = rom.Decompress(0, in_size);
EXPECT_TRUE(decompression_status.ok());
auto decompressed_bytes = std::move(*decompression_status);
return decompressed_bytes;
}
std::shared_ptr<CompressionPiece> ExpectNewCompressionPieceOk(
const char command, const int length, const std::string args,
const int argument_length) {
auto new_piece = std::make_shared<CompressionPiece>(command, length, args,
argument_length);
EXPECT_TRUE(new_piece != nullptr);
return new_piece;
}
} // namespace
TEST(ROMTest, NewDecompressionPieceOk) {
char command = 1;
int length = 1;
char args[] = "aaa";
int argument_length = 0x02;
CompressionPiece old_piece;
old_piece.command = command;
old_piece.length = length;
old_piece.argument = args;
old_piece.argument_length = argument_length;
old_piece.next = nullptr;
auto new_piece = ExpectNewCompressionPieceOk(0x01, 0x01, "aaa", 0x02);
EXPECT_EQ(old_piece.command, new_piece->command);
EXPECT_EQ(old_piece.length, new_piece->length);
ASSERT_EQ(old_piece.argument_length, new_piece->argument_length);
for (int i = 0; i < old_piece.argument_length; ++i) {
EXPECT_EQ(old_piece.argument[i], new_piece->argument[i]);
}
}
TEST(ROMTest, DecompressionValidCommand) {
ROM rom;
Bytes simple_copy_input = {BUILD_HEADER(0x00, 0x02), 0x2A, 0x45, 0xFF};
uchar simple_copy_output[2] = {0x2A, 0x45};
auto decomp_result = ExpectDecompressBytesOk(rom, simple_copy_input);
EXPECT_THAT(simple_copy_output, ElementsAreArray(decomp_result.data(), 2));
}
TEST(ROMTest, DecompressionMixingCommand) {
ROM rom;
uchar random1_i[11] = {BUILD_HEADER(0x01, 0x03),
0x2A,
BUILD_HEADER(0x00, 0x04),
0x01,
0x02,
0x03,
0x04,
BUILD_HEADER(0x02, 0x02),
0x0B,
0x16,
0xFF};
uchar random1_o[9] = {42, 42, 42, 1, 2, 3, 4, 11, 22};
auto decomp_result = ExpectDecompressOk(rom, random1_i, 11);
EXPECT_THAT(random1_o, ElementsAreArray(decomp_result.data(), 9));
}
TEST(ROMTest, CompressionSingleSet) {
ROM rom;
uchar single_set[5] = {0x2A, 0x2A, 0x2A, 0x2A, 0x2A};
uchar single_set_expected[3] = {BUILD_HEADER(1, 5), 0x2A, 0xFF};
auto comp_result = ExpectCompressOk(rom, single_set, 5);
EXPECT_THAT(single_set_expected, ElementsAreArray(comp_result.data(), 3));
}
TEST(ROMTest, CompressionSingleWord) {
ROM rom;
uchar single_word[6] = {0x2A, 0x01, 0x2A, 0x01, 0x2A, 0x01};
uchar single_word_expected[4] = {BUILD_HEADER(0x02, 0x06), 0x2A, 0x01, 0xFF};
auto comp_result = ExpectCompressOk(rom, single_word, 6);
EXPECT_THAT(single_word_expected, ElementsAreArray(comp_result.data(), 4));
}
TEST(ROMTest, CompressionSingleIncrement) {
ROM rom;
uchar single_inc[3] = {0x01, 0x02, 0x03};
uchar single_inc_expected[3] = {BUILD_HEADER(0x03, 0x03), 0x01, 0xFF};
auto comp_result = ExpectCompressOk(rom, single_inc, 3);
EXPECT_THAT(single_inc_expected, ElementsAreArray(comp_result.data(), 3));
}
TEST(ROMTest, CompressionSingleCopy) {
ROM rom;
uchar single_copy[4] = {0x03, 0x0A, 0x07, 0x14};
uchar single_copy_expected[6] = {
BUILD_HEADER(0x00, 0x04), 0x03, 0x0A, 0x07, 0x14, 0xFF};
auto comp_result = ExpectCompressOk(rom, single_copy, 4);
EXPECT_THAT(single_copy_expected, ElementsAreArray(comp_result.data(), 6));
}
/* Hiding tests until I figure out a better PR to address the bug
TEST(ROMTest, CompressionSingleCopyRepeat) {
ROM rom;
uchar single_copy_repeat[8] = {0x03, 0x0A, 0x07, 0x14, 0x03, 10, 0x07, 0x14};
uchar single_copy_repeat_expected[9] = {
BUILD_HEADER(0x00, 0x04), 0x03, 0x0A, 0x07, 0x14,
BUILD_HEADER(0x04, 0x04), 0x00, 0x00, 0xFF};
auto comp_result = ExpectCompressOk(rom, single_copy_repeat, 8);
EXPECT_THAT(single_copy_repeat_expected,
ElementsAreArray(comp_result.data(), 9));
}
TEST(ROMTest, CompressionSingleOverflowIncrement) {
ROM rom;
uchar overflow_inc[4] = {0xFE, 0xFF, 0x00, 0x01};
uchar overflow_inc_expected[3] = {BUILD_HEADER(0x03, 0x04), 0xFE, 0xFF};
auto comp_result = ExpectCompressOk(rom, overflow_inc, 4);
EXPECT_THAT(overflow_inc_expected, ElementsAreArray(comp_result.data(), 3));
}
TEST(ROMTest, CompressionMixedRepeatIncrement) {
ROM rom;
uchar to_compress_string[28] = {0x05, 0x05, 0x05, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x05, 0x02, 0x05, 0x02,
0x05, 0x02, 0x0A, 0x0B, 0x05, 0x02, 0x05,
0x02, 0x05, 0x02, 0x08, 0x0A, 0x00, 0x05};
uchar repeat_and_inc_copy_expected[7] = {BUILD_HEADER(0x01, 0x04),
0x05,
BUILD_HEADER(0x03, 0x06),
0x06,
BUILD_HEADER(0x00, 0x01),
0x05,
0xFF};
// Mixing, repeat, inc, trailing copy
auto comp_result = ExpectCompressOk(rom, to_compress_string, 28);
EXPECT_THAT(repeat_and_inc_copy_expected,
ElementsAreArray(comp_result.data(), 7));
}
*/
TEST(ROMTest, CompressionMixedIncrementIntraCopyOffset) {
ROM rom;
uchar to_compress_string[] = {0x05, 0x05, 0x05, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x05, 0x02, 0x05, 0x02,
0x05, 0x02, 0x0A, 0x0B, 0x05, 0x02, 0x05,
0x02, 0x05, 0x02, 0x08, 0x0A, 0x00, 0x05};
uchar inc_word_intra_copy_expected[] = {BUILD_HEADER(0x03, 0x07),
0x05,
BUILD_HEADER(0x02, 0x06),
0x05,
0x02,
BUILD_HEADER(0x04, 0x08),
0x05,
0x00,
0xFF};
// "Mixing, inc, alternate, intra copy"
// compress start: 3, length: 21
// compressed length: 9
auto comp_result = ExpectCompressOk(rom, to_compress_string + 3, 21);
EXPECT_THAT(inc_word_intra_copy_expected,
ElementsAreArray(comp_result.data(), 9));
}
TEST(ROMTest, CompressionMixedIncrementIntraCopySource) {
ROM rom;
uchar to_compress_string[] = {0x05, 0x05, 0x05, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x05, 0x02, 0x05, 0x02,
0x05, 0x02, 0x0A, 0x0B, 0x05, 0x02, 0x05,
0x02, 0x05, 0x02, 0x08, 0x0A, 0x00, 0x05};
uchar all_expected[] = {BUILD_HEADER(0x01, 0x04),
0x05,
BUILD_HEADER(0x03, 0x06),
0x06,
BUILD_HEADER(0x02, 0x06),
0x05,
0x02,
BUILD_HEADER(0x04, 0x08),
0x08,
0x00,
BUILD_HEADER(0x00, 0x04),
0x08,
0x0A,
0x00,
0x05,
0xFF};
// "Mixing, inc, alternate, intra copy"
// 0, 28
// 16
auto comp_result = ExpectCompressOk(rom, to_compress_string, 28);
EXPECT_THAT(all_expected, ElementsAreArray(comp_result.data(), 16));
}
TEST(ROMTest, LengthBorderCompression) {
ROM rom;
uchar buffer[3000];
for (unsigned int i = 0; i < 3000; i++) buffer[i] = 0x05;
uchar extended_lenght_expected_42[] = {0b11100100, 0x29, 0x05, 0xFF};
uchar extended_lenght_expected_400[] = {0b11100101, 0x8F, 0x05, 0xFF};
uchar extended_lenght_expected_1050[] = {
0b11100111, 0xFF, 0x05, BUILD_HEADER(0x01, 0x1A), 0x05, 0xFF};
uchar extended_lenght_expected_2050[] = {
0b11100111, 0xFF, 0x05, 0b11100111, 0xFF, 0x05, BUILD_HEADER(0x01, 0x02),
0x05, 0xFF};
// "Extended lenght, 42 repeat of 5"
auto comp_result = ExpectCompressOk(rom, buffer, 42);
EXPECT_THAT(extended_lenght_expected_42,
ElementsAreArray(comp_result.data(), 4));
// "Extended lenght, 400 repeat of 5"
comp_result = ExpectCompressOk(rom, buffer, 400);
EXPECT_THAT(extended_lenght_expected_400,
ElementsAreArray(comp_result.data(), 4));
// "Extended lenght, 1050 repeat of 5"
comp_result = ExpectCompressOk(rom, buffer, 1050);
EXPECT_THAT(extended_lenght_expected_1050,
ElementsAreArray(comp_result.data(), 6));
// "Extended lenght, 2050 repeat of 5"
comp_result = ExpectCompressOk(rom, buffer, 2050);
EXPECT_THAT(extended_lenght_expected_2050,
ElementsAreArray(comp_result.data(), 9));
}
TEST(ROMTest, CompressionExtendedWordCopy) {
ROM rom;
uchar buffer[3000];
for (unsigned int i = 0; i < 3000; i += 2) {
buffer[i] = 0x05;
buffer[i + 1] = 0x06;
}
uchar hightlenght_word_1050[] = {
0b11101011, 0xFF, 0x05, 0x06, BUILD_HEADER(0x02, 0x1A), 0x05, 0x06, 0xFF};
// "Extended word copy"
auto comp_result = ExpectCompressOk(rom, buffer, 1050);
EXPECT_THAT(hightlenght_word_1050, ElementsAreArray(comp_result.data(), 8));
}
/* Extended Header Command is currently unimplemented
TEST(ROMTest, ExtendedHeaderDecompress) {
ROM rom;
Bytes extendedcmd_i = {0b11100100, 0x8F, 0x2A, 0xFF};
uchar extendedcmd_o[50];
for (int i = 0; i < 50; ++i) {
extendedcmd_o[i] = 0x2A;
}
auto decomp_result = ExpectDecompressBytesOk(rom, extendedcmd_i);
ASSERT_THAT(extendedcmd_o, ElementsAreArray(decomp_result.data(), 50));
}
TEST(ROMTest, ExtendedHeaderDecompress2) {
ROM rom;
Bytes extendedcmd_i = {0b11100101, 0x8F, 0x2A, 0xFF};
uchar extendedcmd_o[50];
for (int i = 0; i < 50; i++) {
extendedcmd_o[i] = 0x2A;
}
auto data = ExpectDecompressBytesOk(rom, extendedcmd_i);
for (int i = 0; i < 50; i++) {
ASSERT_EQ(extendedcmd_o[i], data[i]);
}
}
*/
} // namespace rom_test
} // namespace yaze_test

Some files were not shown because too many files have changed in this diff Show More