2 Commits

Author SHA1 Message Date
scawful
8ce29e1436 backend-infra-engineer: Release 0.2.2 snapshot 2024-12-31 21:00:27 -05:00
scawful
18b7fb9abf backend-infra-engineer: Pre-0.2.2 2024 Q4 snapshot 2024-11-28 11:50:47 -05:00
270 changed files with 28415 additions and 11083 deletions

View File

@@ -37,6 +37,12 @@ jobs:
- name: Install Abseil-cpp - name: Install Abseil-cpp
run: sudo apt install libabsl-dev run: sudo apt install libabsl-dev
- name: Install Boost and Boost Python
run: sudo apt install libboost-all-dev libboost-python-dev
- name: Install CPython headers
run: sudo apt install python3-dev libpython3-dev
- name: Configure CMake - 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. # 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 # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type

28
.gitignore vendored
View File

@@ -1,19 +1,21 @@
.favorites.json
build/ build/
.cache/ .cache/
.vscode/ .vscode/
src/lib/SDL2 disasm/
src/lib/cmake src/etc
src/lib/GL src/lib/
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 checks.json
assets/lib/libasar.dll assets/lib/libasar.dll
cmake/yaze.plist.in cmake/yaze.plist.in
etc/
latex/
html/
docs/zarby_algos.md
docs/overworld-expansion.md
assets/asm/EditorCore.asm
src/app/emu/cpu/internal/old_cpu.cc
build-windows
src/lib/libpng
src/lib/zlib
assets/layouts/ow_toolset.zeml

6
.gitmodules vendored
View File

@@ -1,12 +1,6 @@
[submodule "src/lib/imgui"] [submodule "src/lib/imgui"]
path = src/lib/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 "assets/asm/alttp-hacker-workspace"] [submodule "assets/asm/alttp-hacker-workspace"]
path = assets/asm/alttp-hacker-workspace path = assets/asm/alttp-hacker-workspace
url = https://github.com/scawful/alttp-hacker-workspace.git url = https://github.com/scawful/alttp-hacker-workspace.git

View File

@@ -1,12 +1,25 @@
# CMake Specifications
cmake_minimum_required(VERSION 3.10)
# Yet Another Zelda3 Editor # Yet Another Zelda3 Editor
# by scawful # by scawful
project(yaze VERSION 0.10) cmake_minimum_required(VERSION 3.10)
project(yaze VERSION 0.2.2
DESCRIPTION "Yet Another Zelda3 Editor"
LANGUAGES CXX)
configure_file(src/yaze_config.h.in yaze_config.h)
# C++ Standard Specifications # Build Flags
set(CMAKE_CXX_STANDARD 20) set(YAZE_BUILD_APP ON)
set(YAZE_BUILD_LIB ON)
set(YAZE_BUILD_EMU ON)
set(YAZE_BUILD_Z3ED ON)
set(YAZE_BUILD_PYTHON OFF)
set(YAZE_BUILD_TESTS ON)
set(YAZE_INSTALL_LIB OFF)
# libpng features in bitmap.cc
add_definitions("-DYAZE_LIB_PNG=1")
# C++ Standard and CMake Specifications
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
@@ -14,21 +27,32 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF)
set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_FRAMEWORK LAST)
set(CMAKE_SHARED_MODULE_PREFIX "")
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dlinux -Dstricmp=strcasecmp")
endif()
if (MACOS)
set(CMAKE_INSTALL_PREFIX /usr/local)
endif()
if (WIN32)
include(cmake/vcpkg.cmake)
endif()
# Abseil Standard Specifications # Abseil Standard Specifications
include(cmake/absl.cmake) include(cmake/absl.cmake)
# Video Libraries # SDL2 and PNG
find_package(PNG REQUIRED)
include(cmake/sdl2.cmake) include(cmake/sdl2.cmake)
# Asar # Asar
add_subdirectory(src/lib/asar/src)
include(cmake/asar.cmake) include(cmake/asar.cmake)
# ImGui # ImGui
include(cmake/imgui.cmake) include(cmake/imgui.cmake)
# Project Files # Project Files
add_subdirectory(src) add_subdirectory(src)

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = "yaze"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = PROJECT_NUMBER = "0.2.2"
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
@@ -61,20 +61,20 @@ PROJECT_BRIEF = Link to the Past ROM Editor
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory. # the logo to the output directory.
PROJECT_LOGO = PROJECT_LOGO = "src/win32/yaze.ico"
# With the PROJECT_ICON tag one can specify an icon that is included in the tabs # With the PROJECT_ICON tag one can specify an icon that is included in the tabs
# when the HTML document is shown. Doxygen will copy the logo to the output # when the HTML document is shown. Doxygen will copy the logo to the output
# directory. # directory.
PROJECT_ICON = PROJECT_ICON = "src/win32/yaze.ico"
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is # into which the generated documentation will be written. If a relative path is
# entered, it will be relative to the location where doxygen was started. If # entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used. # left blank the current directory will be used.
OUTPUT_DIRECTORY = OUTPUT_DIRECTORY =
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 # If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format # sub-directories (in 2 levels) under the output directory of each output format
@@ -1168,7 +1168,7 @@ FILTER_SOURCE_PATTERNS =
# (index.html). This can be useful if you have a project on for instance GitHub # (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output. # and want to reuse the introduction page also for the doxygen output.
USE_MDFILE_AS_MAINPAGE = USE_MDFILE_AS_MAINPAGE = getting-started.md
# The Fortran standard specifies that for fixed formatted Fortran code all # The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common # characters from position 72 are to be considered as comment. A common
@@ -1190,7 +1190,7 @@ FORTRAN_COMMENT_AFTER = 72
# also VERBATIM_HEADERS is set to NO. # also VERBATIM_HEADERS is set to NO.
# The default value is: NO. # The default value is: NO.
SOURCE_BROWSER = NO SOURCE_BROWSER = YES
# Setting the INLINE_SOURCES tag to YES will include the body of functions, # Setting the INLINE_SOURCES tag to YES will include the body of functions,
# multi-line macros, enums or list initialized variables directly into the # multi-line macros, enums or list initialized variables directly into the
@@ -1284,7 +1284,7 @@ ALPHABETICAL_INDEX = YES
# after removing the prefix. # after removing the prefix.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX = IGNORE_PREFIX = googletest
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to the HTML output # Configuration options related to the HTML output
@@ -1402,7 +1402,7 @@ HTML_COLORSTYLE = AUTO_LIGHT
# Minimum value: 0, maximum value: 359, default value: 220. # Minimum value: 0, maximum value: 359, default value: 220.
# This tag requires that the tag GENERATE_HTML is set to YES. # This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_HUE = 280
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use gray-scales only. A # in the HTML output. For a value of 0 the output will use gray-scales only. A
@@ -2798,7 +2798,7 @@ PLANTUML_INCLUDE_PATH =
# Minimum value: 0, maximum value: 10000, default value: 50. # Minimum value: 0, maximum value: 10000, default value: 50.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_GRAPH_MAX_NODES = 50 DOT_GRAPH_MAX_NODES = 25
# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
# generated by dot. A depth value of 3 means that only nodes reachable from the # generated by dot. A depth value of 3 means that only nodes reachable from the
@@ -2810,7 +2810,7 @@ DOT_GRAPH_MAX_NODES = 50
# Minimum value: 0, maximum value: 1000, default value: 0. # Minimum value: 0, maximum value: 1000, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
MAX_DOT_GRAPH_DEPTH = 0 MAX_DOT_GRAPH_DEPTH = 2
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This # files in one run (i.e. multiple -o and -T options on the command line). This

View File

@@ -1,4 +1,4 @@
Copyright (C) 2022 Justin Scofield Copyright (C) 2024 Justin Scofield
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -13,3 +13,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Dependencies:
- SDL2 <https://www.libsdl.org/license.php>
- ImGui <https://github.com/ocornut/imgui/blob/master/LICENSE.txt>
- Abseil <https://github.com/abseil/abseil-cpp/blob/master/LICENSE>

View File

@@ -1,12 +1,14 @@
# Yet Another Zelda3 Editor # Yet Another Zelda3 Editor
- Platform: Windows, macOS, GNU/Linux - Platform: Windows, macOS, iOS, GNU/Linux
- Dependencies: SDL2, ImGui - Dependencies: SDL2, ImGui, abseil-cpp
## Description ## Description
General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo. General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo.
Provides bindings in C and Python for building custom tools and utilities.
Takes heavy inspiration from ALTTP community efforts such as [Hyrule Magic](https://www.romhacking.net/utilities/200/) and [ZScream](https://github.com/Zarby89/ZScreamDungeon) 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 Building and installation
@@ -14,23 +16,27 @@ Building and installation
[CMake](http://www.cmake.org "CMake") is required to build yaze [CMake](http://www.cmake.org "CMake") is required to build yaze
1. Clone the repository 1. Clone the repository
2. Create the build directory and configuration
3. Build and run the application
4. (Optional) Run the tests
``` ```
git clone --recurse-submodules https://github.com/scawful/yaze.git git clone --recurse-submodules https://github.com/scawful/yaze.git
```
2. Create the build directory and configuration
```
cmake -S . -B build cmake -S . -B build
```
3. Build and run.
```
cmake --build build cmake --build build
``` ```
By default this will build all targets.
- **yaze**: Editor Application
- **yaze_c**: C Library
- **yaze_emu**: SNES Emulator
- **yaze_py**: Python Module
- **yaze_test**: Unit Tests
- **z3ed**: Command Line Interface
Dependencies are included as submodules and will be built automatically. For those who want to reduce compile times, consider installing the dependencies on your system. See [build-instructions.md](docs/build-instructions.md) for more information.
## Documentation ## Documentation
- For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze. - For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze.

File diff suppressed because it is too large Load Diff

22
assets/asm/yaze.asm Normal file
View File

@@ -0,0 +1,22 @@
; =========================================================
; yaze custom assembly code
; =========================================================
namespace yaze
{
!YAZE_CUSTOM_MOSAIC = 1
if !YAZE_CUSTOM_MOSAIC != 0
incsrc "mosaic_change.asm"
endif
!ZS_CUSTOM_OVERWORLD = 1
if !ZS_CUSTOM_OVERWORLD != 0
incsrc "ZSCustomOverworld.asm"
endif
}
namespace off

BIN
assets/yaze.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -1,10 +1,14 @@
find_package(absl) if (MINGW)
add_subdirectory(src/lib/abseil-cpp)
else()
find_package(absl)
endif()
set(ABSL_PROPAGATE_CXX_STD ON) set(ABSL_PROPAGATE_CXX_STD ON)
set(ABSL_CXX_STANDARD 17) set(ABSL_CXX_STANDARD 17)
set(ABSL_USE_GOOGLETEST_HEAD ON) set(ABSL_USE_GOOGLETEST_HEAD ON)
set(ABSL_ENABLE_INSTALL ON) set(ABSL_ENABLE_INSTALL ON)
set( set(
ABSL_TARGETS ABSL_TARGETS
absl::strings absl::strings
absl::flags absl::flags
absl::status absl::status
@@ -17,4 +21,4 @@ set(
absl::raw_logging_internal absl::raw_logging_internal
absl::failure_signal_handler absl::failure_signal_handler
absl::flat_hash_map absl::flat_hash_map
) )

View File

@@ -1,33 +1,39 @@
get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES) # Asar Assembler for 65816 SNES Assembly
target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR}) add_subdirectory(src/lib/asar/src)
set(ASAR_GEN_EXE OFF) set(ASAR_GEN_EXE OFF)
set(ASAR_GEN_DLL ON) set(ASAR_GEN_DLL ON)
set(ASAR_GEN_LIB ON) set(ASAR_GEN_LIB ON)
set(ASAR_GEN_EXE_TEST OFF) set(ASAR_GEN_EXE_TEST OFF)
set(ASAR_GEN_DLL_TEST OFF) set(ASAR_GEN_DLL_TEST OFF)
set(ASAR_STATIC_SRC_DIR "${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar")
get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES)
list(APPEND ASAR_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/src/lib/asar/src")
target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR})
set(ASAR_STATIC_SRC set(ASAR_STATIC_SRC
"../src/lib/asar/src/asar/interface-lib.cpp" "${ASAR_STATIC_SRC_DIR}/interface-lib.cpp"
"../src/lib/asar/src/asar/addr2line.cpp" "${ASAR_STATIC_SRC_DIR}/addr2line.cpp"
"../src/lib/asar/src/asar/arch-65816.cpp" "${ASAR_STATIC_SRC_DIR}/arch-65816.cpp"
"../src/lib/asar/src/asar/arch-spc700.cpp" "${ASAR_STATIC_SRC_DIR}/arch-spc700.cpp"
"../src/lib/asar/src/asar/arch-superfx.cpp" "${ASAR_STATIC_SRC_DIR}/arch-superfx.cpp"
"../src/lib/asar/src/asar/assembleblock.cpp" "${ASAR_STATIC_SRC_DIR}/assembleblock.cpp"
"../src/lib/asar/src/asar/crc32.cpp" "${ASAR_STATIC_SRC_DIR}/crc32.cpp"
"../src/lib/asar/src/asar/libcon.cpp" "${ASAR_STATIC_SRC_DIR}/libcon.cpp"
"../src/lib/asar/src/asar/libsmw.cpp" "${ASAR_STATIC_SRC_DIR}/libsmw.cpp"
"../src/lib/asar/src/asar/libstr.cpp" "${ASAR_STATIC_SRC_DIR}/libstr.cpp"
"../src/lib/asar/src/asar/macro.cpp" "${ASAR_STATIC_SRC_DIR}/macro.cpp"
"../src/lib/asar/src/asar/main.cpp" "${ASAR_STATIC_SRC_DIR}/main.cpp"
"../src/lib/asar/src/asar/asar_math.cpp" "${ASAR_STATIC_SRC_DIR}/asar_math.cpp"
"../src/lib/asar/src/asar/virtualfile.cpp" "${ASAR_STATIC_SRC_DIR}/virtualfile.cpp"
"../src/lib/asar/src/asar/warnings.cpp" "${ASAR_STATIC_SRC_DIR}/warnings.cpp"
"../src/lib/asar/src/asar/errors.cpp" "${ASAR_STATIC_SRC_DIR}/errors.cpp"
"../src/lib/asar/src/asar/platform/file-helpers.cpp" "${ASAR_STATIC_SRC_DIR}/platform/file-helpers.cpp"
) )
if(WIN32 OR MINGW) if(WIN32 OR MINGW)
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/windows/file-helpers-win32.cpp") list(APPEND ASAR_STATIC_SRC "${ASAR_STATIC_SRC_DIR}/platform/windows/file-helpers-win32.cpp")
else() else()
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/linux/file-helpers-linux.cpp") list(APPEND ASAR_STATIC_SRC "${ASAR_STATIC_SRC_DIR}/platform/linux/file-helpers-linux.cpp")
endif() endif()

11
cmake/gtest.cmake Normal file
View File

@@ -0,0 +1,11 @@
# GoogleTest ------------------------------------------------------------------
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.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()

View File

@@ -4,40 +4,25 @@ file(GLOB IMGUI_SOURCES ${IMGUI_PATH}/*.cpp)
add_library("ImGui" STATIC ${IMGUI_SOURCES}) add_library("ImGui" STATIC ${IMGUI_SOURCES})
target_include_directories("ImGui" PUBLIC ${IMGUI_PATH}) target_include_directories("ImGui" PUBLIC ${IMGUI_PATH})
target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR}) target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR})
target_compile_definitions(ImGui PUBLIC 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) IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
set(IMGUI_TEST_ENGINE_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine/imgui_test_engine) set(IMGUI_TEST_ENGINE_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine/imgui_test_engine)
file(GLOB IMGUI_TEST_ENGINE_SOURCES ${IMGUI_TEST_ENGINE_PATH}/*.cpp) file(GLOB IMGUI_TEST_ENGINE_SOURCES ${IMGUI_TEST_ENGINE_PATH}/*.cpp)
add_library("ImGuiTestEngine" STATIC ${IMGUI_TEST_ENGINE_SOURCES}) add_library("ImGuiTestEngine" STATIC ${IMGUI_TEST_ENGINE_SOURCES})
target_include_directories(ImGuiTestEngine PUBLIC ${IMGUI_PATH}) target_include_directories(ImGuiTestEngine PUBLIC ${IMGUI_PATH} ${CMAKE_SOURCE_DIR}/src/lib)
target_link_libraries(ImGuiTestEngine PUBLIC ImGui) target_link_libraries(ImGuiTestEngine PUBLIC ImGui)
target_compile_definitions(ImGuiTestEngine PUBLIC
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
set( set(
IMGUI_SRC IMGUI_SRC
${IMGUI_PATH}/imgui.cpp ${IMGUI_PATH}/imgui.cpp
${IMGUI_PATH}/imgui_demo.cpp ${IMGUI_PATH}/imgui_demo.cpp
${IMGUI_PATH}/imgui_draw.cpp ${IMGUI_PATH}/imgui_draw.cpp
${IMGUI_PATH}/imgui_widgets.cpp ${IMGUI_PATH}/imgui_widgets.cpp
${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp ${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp ${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp
${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp ${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
${IMGUI_FILE_DLG_PATH}/ImGuiFileDialog.cpp )
${IMGUI_COLOR_TEXT_EDIT_PATH}/TextEditor.cpp
) # For integration test
add_definitions("-DIMGUI_ENABLE_TEST_ENGINE -DIMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1")

21
cmake/mingw64.cmake Normal file
View File

@@ -0,0 +1,21 @@
# cmake -DCMAKE_TOOLCHAIN_FILE=./cmake/mingw64.cmake -B build/build-windows && cmake --build ./build/build-windows
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/local/opt/mingw-w64)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -pipe -g -feliminate-unused-debug-types")
# Static link the C++ standard library
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")

View File

@@ -1,6 +1,16 @@
# SDL2 # SDL2
if (UNIX) if (UNIX OR MINGW)
add_subdirectory(src/lib/SDL) add_subdirectory(src/lib/SDL)
else() else()
find_package(SDL2) find_package(SDL2)
endif() endif()
set(SDL_TARGETS SDL2::SDL2)
if(WIN32 OR MINGW)
list(PREPEND SDL_TARGETS SDL2::SDL2main ws2_32)
add_definitions("-DSDL_MAIN_HANDLED")
endif()
# libpng
find_package(PNG REQUIRED)

6
cmake/vcpkg.cmake Normal file
View File

@@ -0,0 +1,6 @@
add_definitions("-DMICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS=0")
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)

223
docs/asm-style-guide.md Normal file
View File

@@ -0,0 +1,223 @@
# Asm Style Guide
65816 Assembly is the assembly language used by the Super Nintendo Entertainment System (SNES) and its Ricoh 5A22 processor. This style guide provides conventions and best practices for writing 65816 assembly code in the context of the yaze project. Following these guidelines will help maintain consistency and readability across the codebase.
This guide is based primarily on the [Oracle of Secrets](https://github.com/scawful/Oracle-of-Secrets) codebase and is meant for the [Asar](https://github.com/RPGHacker/asar) assembler and derives influence from the [Asar 1.9 Manual](https://rpghacker.github.io/asar/asar_19/manual/).
Custom assembly code applied to the game should be included through the `yaze.asm` file found in `assets/asm`. This file can be applied to the ROM by the editor using the Asar library or included into a projects codebase for use with the Asar assembler.
## Table of Contents
- [File Structure](#file-structure)
- [Labels and Symbols](#labels-and-symbols)
- [Comments](#comments)
- [Directives](#directives)
- [Instructions](#instructions)
- [Macros](#macros)
- [Loops and Branching](#loops-and-branching)
- [Data Structures](#data-structures)
- [Code Organization](#code-organization)
- [Custom Code](#custom-code)
## File Structure
- **File Extension**: Use `.asm` as the file extension for 65816 assembly files.
- **Header Comments**: Include a header comment at the beginning of each file describing its purpose and the author.
Example:
```asm
; =========================================================
; Purpose: [Brief description of the files functionality]
; Author: [Your Name]
; =========================================================
```
- **Section Headers**: Use clear and consistent section headers to divide code into logical blocks. Each major section (e.g., sprite properties, main logic, subroutines) should start with a delineated header.
Example:
```asm
; =========================================================
; Minecart Sprite Properties
; =========================================================
```
- **Macro Definitions and Includes**: Place macros and include directives at the beginning of the file to keep them organized and easily accessible.
## Labels and Symbols
- **Naming Conventions**:
- **Global Labels**: Use descriptive names in `PascalCase` for global labels (e.g., `Sprite_Minecart_Main`).
- **Local Labels**: Prefix local labels with a dot (`.`) to indicate their limited scope (e.g., `.check_direction`).
- **Constants and Flags**: Use `ALL_CAPS_WITH_UNDERSCORES` for constants and flags (e.g., `!MINECART_SPEED`, `!HARVESTING_FLAG`).
- **Variables**: Use `CamelCase` for variable names to maintain readability (e.g., `LinkInCart`, `SpriteDirection`).
- **Alignment**: Align labels to the left margin for better readability. Indent instructions and comments to separate them from labels.
Example:
```asm
Sprite_Minecart_Main:
{
JSR HandleTileDirections
JSR HandleDynamicSwitchTileDirections
RTS
}
```
## Comments
- **Purpose**: Comments should explain why the code exists and what it is intended to do, especially for complex logic.
- **Placement**:
- Comments can be placed above the code block they describe for longer explanations.
- Inline comments can be used for single lines of code where the purpose might not be immediately clear.
- **Clarity**: Avoid stating the obvious. Focus on explaining the logic rather than restating the code.
Example:
```asm
LDA $22 : SEC : SBC $3F : STA $31 ; Adjust X position for camera movement
```
## Instructions
- **Single Line Instructions**: Combine multiple instructions on a single line using colons (`:`) where appropriate for related operations.
- **Separation**: Use line breaks to separate distinct sections of code logically, improving readability.
- **Optimization**: Always consider the most efficient instruction for the task at hand, especially in performance-critical sections.
Example:
```asm
LDA #$01 : STA !LinkInCart ; Set Link in cart flag
```
## Macros
- **Naming**: Use `PascalCase` for macro names, with the first letter of each word capitalized (e.g., `InitMovement`, `MoveCart`).
- **Parameters**: Clearly define and document parameters within macros to ensure they are used correctly.
- **Reuse**: Encourage the reuse of macros to avoid code duplication and simplify maintenance.
Example:
```asm
%macro HandlePlayerCamera
LDA $22 : SEC : SBC $3F : STA $31
LDA $20 : SEC : SBC $3E : STA $30
JSL Link_HandleMovingAnimation_FullLongEntry
JSL HandleIndoorCameraAndDoors
RTS
endmacro
```
## Loops and Branching
- **Branch Labels**: Use meaningful names for branch labels, prefixed with a dot (`.`) for local branches.
- **Optimization**: Minimize the number of instructions within loops and branches to improve performance.
Example:
```asm
.loop_start
LDA $00 : CMP #$10 : BEQ .end_loop
INC $00
BRA .loop_start
.end_loop
RTS
```
## Data Structures
- **Alignment**: Align data tables and structures clearly, and use comments to describe the purpose and layout of each.
- **Access**: Ensure that data structures are accessed consistently, with clear boundaries between read and write operations.
Example:
```asm
.DirectionTileLookup
{
db $02, $00, $04, $00 ; North
db $00, $00, $03, $01 ; East
db $00, $02, $00, $04 ; South
db $03, $01, $00, $00 ; West
}
```
- **Structs**: Use structs to group related data together, improving readability and maintainability.
Example:
```asm
struct AncillaAdd_HookshotData $099AF8
.speed_y: skip 4
.speed_x: skip 4
.offset_y: skip 8
.offset_x: skip 8
endstruct
AncillaAdd_Hookshot:
; $099AF0
.speed_y
db -64 ; up
db 64 ; down
db 0 ; left
db 0 ; right
; $099AFC
.speed_x
db 0 ; up
db 0 ; down
db -64 ; left
db 64 ; right
; $099B00
.offset_y
dw 4 ; up
dw 20 ; down
dw 8 ; left
dw 8 ; right
; $099B08
.offset_x
dw 0 ; up
dw 0 ; down
dw -4 ; left
dw 11 ; right
```
## Code Organization
- **Logical Grouping**: Organize code into logical sections, with related routines and macros grouped together.
- **Separation of Concerns**: Ensure that each section of code is responsible for a specific task or set of related tasks, avoiding tightly coupled code.
- **Modularity**: Write code in a modular way, making it easier to reuse and maintain.
Example:
```asm
; =========================================================
; Minecart Sprite Logic
; =========================================================
Sprite_Minecart_Main:
{
PHX
JSR HandleMinecartMovement
PLX
REP #$20
LDA !SpriteDirection : STA $00
SEP #$20
RTS
}
```
## Custom Code
- **Integration**: Include custom assembly code in the `yaze.asm` file to ensure it is applied correctly to the ROM. The module should include a define and conditional statement to allow users to disable the module if needed.
Example:
```asm
!YAZE_CUSTOM_MOSAIC = 1
if !YAZE_CUSTOM_MOSAIC != 0
incsrc "mosaic_change.asm"
endif
```

View File

@@ -1,27 +1,94 @@
# Build Instructions # Build Instructions
For VSCode users, use the following CMake extensions
- https://marketplace.visualstudio.com/items?itemName=twxs.cmake
- https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
Yaze uses CMake to build the project. If you are unexperienced with CMake, please refer to the [CMake documentation](https://cmake.org/documentation/).
The gui editor is built using SDL2 and ImGui. For reference on how to use ImGui, see the [Getting Started](https://github.com/ocornut/imgui/wiki/Getting-Started) guide. For SDL2, see the [SDL2 documentation](https://wiki.libsdl.org/).
For those who want to reduce compile times, consider installing the dependencies on your system.
## Windows ## Windows
For VSCode users, use the following CMake extensions with MinGW-w64 ### vcpkg
https://marketplace.visualstudio.com/items?itemName=twxs.cmake For Visual Studio users, follow the [Install and use packages with CMake](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started) tutorial from Microsoft.
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
https://www.msys2.org/ Define the following dependencies in `vcpkg.json`
```
{
"dependencies": [
"abseil",
"sdl2",
"libpng"
]
}
```
Target the architecture in `CMakePresets.json`
```
{
"name": "vcpkg",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"architecture": {
"value": "arm64/x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"CMAKE_SYSTEM_PROCESSOR": "arm64/x64"
}
}
```
### msys2
[msys2](https://www.msys2.org/) is an alternative you may use for a Unix-like environment on Windows. Beware that this is for more experienced developers who know how to manage their system PATH.
Add to environment variables `C:\msys64\mingw64\bin` Add to environment variables `C:\msys64\mingw64\bin`
Install the following packages using `pacman -S <package-name>` Install the following packages using `pacman -S <package-name>`
`mingw-w64-x86_64-gcc` - `mingw-w64-x86_64-gcc`
`mingw-w64-x86_64-gcc-libs` - `mingw-w64-x86_64-gcc-libs`
`mingw-w64-x86_64-cmake` - `mingw-w64-x86_64-cmake`
`mingw-w64-x86_64-glew` - `mingw-w64-x86_64-sdl2`
`mingw-w64-x86_64-lib-png` - `mingw-w64-x86_64-libpng`
- `mingw-w64-x86_64-abseil-cpp`
For `yaze_py` you will need Boost Python
- `mingw-w64-x86_64-boost`
# macOS # macOS
- Clang 15.0.1 x86_64-apple-darrwin22.5.0 Prefer to use clang provided with XCode command line tools over gcc.
- SDL2 Source v2.26.5
- Removed snes_spc Install the following packages using `brew install <package-name>`
- Removed asar_static
- `cmake`
- `sdl2`
- `zlib`
- `libpng`
- `abseil`
- `boost-python3`
# iOS
Xcode is required to build for iOS. Currently testing with iOS 18 on iPad Pro.
The xcodeproject file is located in the `ios` directory.
You will need to link `SDL2.framework` and `libpng.a` to the project.
# GNU/Linux
You can use your package manager to install the same dependencies as macOS.
I trust you know how to use your package manager.

102
docs/changelog.md Normal file
View File

@@ -0,0 +1,102 @@
# Changelog
## 0.2.2 (12-31-2024)
- DungeonMap editing improvements
- ZSCustomOverworld support
- Cross platform file handling
## 0.2.1 (08-20-2024)
- Improved MessageEditor parsing
- Added integration test window
- Bitmap bug fixes
## 0.2.0 (07-20-2024)
- iOS app support
- Graphics Sheet Browser
- Project Files
## 0.1.0 (05-11-2024)
- Bitmap bug fixes
- Error handling improvements
## 0.0.9 (04-14-2024)
- Documentation updates
- Entrance tile types
- Emulator subsystem overhaul
## 0.0.8 (02-08-2024)
- Hyrule Magic Compression
- Dungeon Room Entrances
- Png Export
## 0.0.7 (01-27-2024)
- OverworldEntities
- Entrances
- Exits
- Items
- Sprites
## 0.0.6 (11-22-2023)
- ScreenEditor DungeonMap
- Tile16 Editor
- Canvas updates
## 0.0.5 (11-21-2023)
- DungeonEditor
- DungeonObjectRenderer
## 0.0.4 (11-11-2023)
- Tile16Editor
- GfxGroupEditor
- Add GfxGroups fns to Rom
- Add Tile16Editor and GfxGroupEditor to OverworldEditor
## 0.0.3 (10-26-2023)
- Emulator subsystem
- Snes Ppu and PpuRegisters
- Direct Memory Access
- Cpu Tests
- Read/Write Tile16 functions
- CompressionV3
- Rom::LoadLinkGraphics
## 0.0.2 (08-26-2023)
- Emulator subsystem
- Spc700
- Emulator loop
- Clock and MockClock
- Ppu and Apu cycling
- Setup Snes initialization
- 65816 Cpu opcodes
- JP Font support
- SCAD Format support for CGX, COL, OBJ files
- Overworld Save
- Overworld Map Tile Editing
## 0.0.1 (07-22-2023)
- GraphicsEditor
- Palette management
- lc_lz2 Compression
- SnesTo8bppSheet
- Bitmap Canvas
## 0.0.0 (06-08-2022)
- Started project
- Added ImGui
- Added SDL2
- Added yaze_test target with gtest

View File

@@ -1,66 +0,0 @@
# LC_LZ2 Compression
The compression algorithm has multiple implementations with varying levels of quality, based primarily on the implementations made in skarsnik/sneshacking, Zarby89/ZScreamDungeon and ZCompress with optimizations made for C++.
Currently, the Compress and Uncompress methods from Hyrule Magic are used and all other compression methods are considered deprecated.
## Key Definitions
### Constants and Macros:
- `BUILD_HEADER(command, length)`: Macro to build a header from a command and a length.
- Command Constants: Constants to represent different commands like `kCommandDirectCopy`, `kCommandByteFill`, etc.
- Length and Mode Constants: Such as `kMaxLengthNormalHeader`, `kNintendoMode1`, etc.
### Data Structures:
#### 1. CompressionCommand:
- **arguments**: 2D array representing the command arguments for each possible command.
- **cmd_size**: Array storing the size of each possible command.
- **data_size**: Array storing the size of the data processed by each possible command.
#### 2. CompressionPiece:
- **command**: Represents the compression command.
- **length**: Length of the compressed data piece.
- **argument_length**: Length of the argument.
- **argument**: Argument as a string.
- **next**: Pointer to the next compression piece.
#### 3. CompressionContext (for Compression V3):
- Contains vectors to store raw and compressed data, compression pieces, and compression string.
- Various counters and flags for compression control.
- Current compression command details.
## Compression Functions
### Version 1:
- **Byte Repeat**: `CheckByteRepeat`
- **Word Repeat**: `CheckWordRepeat`
- **Increasing Byte**: `CheckIncByte`
- **Intra Copy**: `CheckIntraCopy`
- **Validation and Alternatives**: `ValidateForByteGain` & `CompressionCommandAlternative`
### Version 2:
- **Byte Repeat**: `CheckByteRepeatV2`
- **Word Repeat**: `CheckWordRepeatV2`
- **Increasing Byte**: `CheckIncByteV2`
- **Intra Copy**: `CheckIntraCopyV2`
- **Validation and Alternatives**: `ValidateForByteGainV2` & `CompressionCommandAlternativeV2`
### Version 3:
Using `CompressionContext` to handle compression.
- **Initialization**: `InitializeCompression`
- **Command Checks**: Such as `CheckByteRepeatV3`
- **Determining Best Compression**: `DetermineBestCompression`
- **Handling Direct Copy**: `HandleDirectCopy`
- **Adding Compression to Chain**: `AddCompressionToChain`
## Decompression Functions:
- `SetBuffer`: Prepares a buffer from data.
- `memfill`: Fills memory.
- **Decompression**: Such as `DecompressV2`, `DecompressGraphics`, and `DecompressOverworld`.
## Utility Functions:
- **Printing**: Such as `PrintCompressionPiece` and `PrintCompressionChain`.
- **Compression String Creation**: `CreateCompressionString`
- **Compression Result Validation**: Such as `ValidateCompressionResult` and its V3 variant.
- **Compression Piece Manipulation**: Like `SplitCompressionPiece` and its V3 variant.

108
docs/contributing.md Normal file
View File

@@ -0,0 +1,108 @@
# Contributing
This project is looking for contributors to help improve the software and enhance the user experience. If you are interested in contributing, please read the following guidelines and suggestions for areas where you can make a difference.
Discussion on the editor and its development can be found on the [Oracle of Secrets Discord](https://discord.gg/MBFkMTPEmk) server.
## Style Guide
When contributing to the project, please follow these guidelines to ensure consistency and readability across the codebase:
C++ Code should follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) with the following exceptions:
- Boost libraries are allowed, but require cross platform compatibility.
Objective-C Code should follow the [Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.html).
Python Code should follow the [PEP 8 Style Guide](https://pep8.org/).
Assembly code should follow the [65816 Style Guide](docs/asm-style-guide.md).
## Testing Facilities
The project includes the `yaze_test` target which defines unit tests and an integration test window. The unit tests make use of GoogleTest and GoogleMock. The integration test window is an ImGui window build out of the yaze::core::Controller and yaze::test::integration::TestEditor. The integration test window can be accessed by passing the argument `integration` to the target.
New modules should define unit tests in the `src/test` directory and integration tests in the `src/test/integration` directory. The `yaze_test` target will automatically include all tests in these directories.
## Key Areas of Contribution
### 1. Extensions System
Yaze *(stylized as yaze)* emphasizes extensibility. The `yaze_ext` library allows developers to build and integrate extensions using C, C++, or Python. This system is central to yaze's modular design, enabling new features, custom editors, or tools to be added without modifying the core codebase.
- C/C++ Extensions: Utilize the `yaze_extension` interface to integrate custom functionality into the editor. You can add new tabs, manipulate ROM data, or extend the editors capabilities with custom tools.
- Python Extensions: Currently unimplemented, Python extensions will allow developers to write scripts that interact with the editor, modify ROM data, or automate repetitive tasks.
Examples of Extensions:
- UI enhancements like additional menus, panels, or status displays.
- Rom manipulation tools for editing data structures, such as the overworld maps or dungeon objects.
- Custom editors for specific tasks, like file format conversion, data visualization, or event scripting.
### 2. Sprite Builder System
The sprite builder system in yaze is based on the [ZSpriteMaker](https://github.com/Zarby89/ZSpriteMaker/) project and allows users to create custom sprites for use in ROM hacks. The goal is to support ZSM files and provide an intuitive interface for editing sprites without the need for writing assembly code. Contributions to the sprite builder system might include:
- Implementing new features for sprite editing, such as palette management, animation preview, or tileset manipulation.
- Extending the sprite builder interface by writing assembly code for sprite behavior.
### 3. Emulator Subsystem
yaze includes an emulator subsystem that allows developers to test their modifications directly within the editor. The emulator can currently run certain test ROMs but lacks the ability to play any complex games with audio because of timing issues with the APU and Spc700. Contributions to the emulator subsystem might include:
- Improving the accuracy and performance of the emulator to support more games and features.
- Implementing new debugging tools, such as memory viewers, breakpoints, or trace logs.
- Extending the emulator to support additional features, such as save states, cheat codes, or multiplayer modes.
## Building the Project
For detailed instructions on building YAZE, including its dependencies and supported platforms, refer to [build-instructions.md](docs/build-instructions.md).
## Getting Started
1. Clone the Repository:
```bash
git clone https://github.com/yourusername/yaze.git
cd yaze
```
2. Initialize the Submodules:
```bash
git submodule update --init --recursive
```
3. Build the Project:
Follow the instructions in the [build-instructions.md](docs/build-instructions.md). file to configure and build the project on your target platform.
4. Run the Application:
After building, you can run the application on your chosen platform and start exploring the existing features.
## Contributing your Changes
1. Fork the Repository:
Create a fork of the project on GitHub and clone your fork to your local machine.
2. Create a Branch:
Create a new branch for your feature or bugfix.
```bash
git checkout -b feature/my-new-feature
```
3. Implement Your Changes:
Follow the guidelines above to implement new features, extensions, or improvements.
4. Test Your Changes:
Ensure your changes dont introduce new bugs or regressions. Write unit tests where applicable.
5. Submit a Pull Request:
Push your changes to your fork and submit a pull request to the main repository. Provide a clear description of your changes and why they are beneficial.

View File

@@ -1,23 +1,60 @@
# Getting Started with YAZE # Getting Started
This software allows you to modify the classic SNES game "The Legend of Zelda: A Link to the Past" by editing its ROM file. With this editor, you can change various aspects of the game, such as the maps, sprites, items, and more. This software allows you to modify "The Legend of Zelda: A Link to the Past" (US or JP) ROMs.
This editor is built to be compatible with ZScream projects and is designed to be cross platform.
Please note that this project is currently a work in progress, and some features may not be fully implemented or may be subject to change. Please note that this project is currently a work in progress, and some features may not be fully implemented or may be subject to change.
## Prerequisites ## General Tips
Before you start using YAZE, make sure you have the following:
- A copy of "The Legend of Zelda: A Link to the Past" ROM file (US or JP) - Experiment flags determine whether certain features are enabled or not. To change your flags, go to `File` > `Options` > `Experiment Flags` or in the Settings tab.
- Basic knowledge of hexadecimal and binary data - Backup files are enabled by default. Each save will produce a timestamped copy of your ROM before you last saved. You can disable this feature in the settings.
## Usage ## Extending Functionality
To use the Link to the Past ROM Editor, follow these steps:
Open the ROM file using the "File" menu. In addition to the built-in features, this software provides a pure C library interface and a Python module that can be used for building extensions and custom sprites without assembly. In the editor these can be loaded under the `Extensions` menu.
... This feature is still in development and is not yet fully documented.
## Supported Features
| Feature | Status | Details |
|---------|--------|-------------|
| Overworld Maps | Done | Edit and save tile32 data. |
| Overworld Map Properties | Done | Edit and save map properties. |
| Overworld Entrances | Done | Edit and save entrance data. |
| Overworld Exits | Done | Edit and save exit data. |
| Overworld Sprites | In Progress | Edit sprite positions, add and remove sprites. |
| Tile16 Editing | Todo | Edit and save tile16 data. |
| Dungeon | In Progress | View dungeon room metadata and edit room data. |
| Palette | In Progress | Edit and save palettes, palette groups. |
| Graphics Sheets | In Progress | Edit and save graphics sheets. |
| Graphics Groups | Done | Edit and save graphics groups. |
| Sprite | Todo | View-only sprite data. |
| Custom Sprites | Todo | Edit and create custom sprite data. |
| Music | Todo | Edit music data. |
| Dungeon Maps | Todo | Edit dungeon maps. |
| Scad Format | Done-ish | Open and view scad files (SCR, CGX, COL) |
| Hex Editing | Done | View and edit ROM data in hex. |
| Asar Patching | In Progress | Apply Asar patches to your ROM or Project. |
## Command Line Interface
Included with the editor is a command line interface (CLI) that allows you to perform various operations on your ROMs from the command line. This aims to reduce the need for multiple tools in zelda3 hacking like Zcompress, LunarExpand, LunarAddress, Asar, and others.
| Command | Arg | Params | Status |
|---------|-----|--------|--------|
| Apply BPS Patch | -a | rom_file bps_file | In progress |
| Create BPS Patch | -c | bps_file src_file modified_file | Not started |
| Asar Patch | -asar | asm_file rom_file | In progress |
| Open ROM | -o | rom_file | Complete |
| Backup ROM | -b | rom_file [new_file] | In progress |
| Expand ROM | -x | rom_file file_size | Not started |
| Transfer Tile16 | -t | src_rom dest_rom tile32_id_list(csv) | Complete |
| Export Graphics | -e | rom_file bin_file | In progress |
| Import Graphics | -i | bin_file rom_file | Not started |
| SNES to PC Address | -s | address | Complete |
| PC to SNES Address | -p | address | Complete |
Save your changes using the "File" menu.
Backup files are enabled by default. Each save will produce a timestamped copy of your ROM before you last saved.
That's it! With these instructions, you should be able to get started with using YAZE. Happy editing!

View File

@@ -1,140 +1,85 @@
# YAZE Infrastructure Overview # Infrastructure Overview
For developers to reference. For developers to reference.
The goal of yaze is to build a cross platform editor for the Legend of Zelda: A Link to the Past. The project is built using C++20, SDL2, and ImGui. The project is built using CMake and is designed to be modular and extensible. The project is designed to be built on Windows, macOS, iOS, and Linux.
## Targets
- **yaze**: Desktop application for Windows/macOS/Linux
- **z3ed**: Command Line Interface
- **yaze_c**: C Library
- **yaze_py**: Python Module
- **yaze_test**: Unit test executable
- **yaze_ios**: iOS application
## Directory Structure ## Directory Structure
- **.github/workflows**: Contains `yaze_test` workflow config. - **assets**: Hosts assets like fonts, icons, assembly source, etc.
- **assets**: Hosts assets like fonts.
- **cmake**: Contains CMake configurations. - **cmake**: Contains CMake configurations.
- **docs**: Contains documentation for users and developers. - **docs**: Contains documentation for users and developers.
- [Getting Started](./getting-started.md) - **incl**: Contains the public headers for `yaze_c`
- [LC_LZ2 Compression](./compression.md) - **src**: Contains source files.
- **src**: Contains source files. - **app**: Contains the GUI editor `yaze`
- **app**: Contains the GUI editor `yaze` - **app/emu**: Contains a standalone Snes emulator application `yaze_emu`
- **cli**: Contains the command line interface `z3ed` - **cli**: Contains the command line interface `z3ed`
- **lib**: Contains git submodule dependencies. - **cli/python**: Contains the Python module `yaze_py`
- Abseil-cpp - **ios**: Contains the iOS application `yaze_ios`
- Asar - **lib**: Contains the dependencies as git submodules
- ImGui - **test**: Contains testing interface `yaze_test`
- ImGuiFileDialog - **win32**: Contains Windows resource file and icon
- ImGuiColorTextEdit
- imgui_memory_editor
- SDL2
- **test**: Contains testing interface `yaze_test`
### Flow of Control ## Dependencies
- [app/yaze.cc](../src/app/yaze.cc) See [build-instructions.md](docs/build-instructions.md) for more information.
- **SDL2**: Graphics library
- **ImGui**: GUI library
- **Abseil**: C++ library
- **libpng**: Image library
- **Boost**: Python library
## Flow of Control
- app/yaze.cc
- Initializes `absl::FailureSignalHandler` for stack tracing. - Initializes `absl::FailureSignalHandler` for stack tracing.
- Runs the `core::Controller` loop. - Runs the `core::Controller` loop.
- [app/core/controller.cc](../src/app/core/controller.cc) - app/core/controller.cc
- Initializes SDLRenderer and SDLWindow - Initializes SDLRenderer and SDLWindow
- Initializes ImGui, fonts, themes, and clipboard. - Initializes ImGui, fonts, themes, and clipboard.
- Handles user input from keyboard and mouse. - Handles user input from keyboard and mouse.
- Updates `editor::MasterEditor`
- Renders the output to the screen. - Renders the output to the screen.
- Handles the teardown of SDL and ImGui resources. - Handles the teardown of SDL and ImGui resources.
- [app/editor/master_editor.cc](../src/app/editor/master_editor.cc) - app/editor/editor_manager.cc
- Handles the main menu bar. - Handles the main menu bar
- File
- Open - [app::ROM::LoadFromFile](../src/app/rom.cc#l=90)
- Save - [app::ROM::SaveToFile](../src/app/rom.cc#l=301)
- Edit
- View
- Emulator
- HEX Editor
- ASM Editor
- Palette Editor
- Memory Viewer
- ImGui Demo
- GUI Tools
- Runtime Metrics
- Style Editor
- Help
- Handles `absl::Status` errors as popups delivered to the user. - Handles `absl::Status` errors as popups delivered to the user.
- Dispatches messages to the various editors.
- Update all the editors in a tab view. - Update all the editors in a tab view.
- [app/editor/assembly_editor.cc](../src/app/editor/assembly_editor.cc) - app/editor/code/assembly_editor.cc
- [app/editor/dungeon_editor.cc](../src/app/editor/dungeon_editor.cc) - app/editor/dungeon/dungeon_editor.cc
- [app/editor/graphics_editor.cc](../src/app/editor/graphics_editor.cc) - app/editor/graphics/graphics_editor.cc
- [app/editor/music_editor.cc](../src/app/editor/music_editor.cc) - app/editor/graphics/gfx_group_editor.cc
- [app/editor/overworld_editor.cc](../src/app/editor/overworld_editor.cc) - app/editor/graphics/palette_editor.cc
- [app/editor/screen_editor.cc](../src/app/editor/screen_editor.cc) - app/editor/graphics/tile16_editor.cc
- [app/editor/sprite_editor.cc](../src/app/editor/sprite_editor.cc) - app/editor/message/message_editor.cc
- app/editor/music/music_editor.cc
- app/editor/overworld/overworld_editor.cc
- app/editor/graphics/screen_editor.cc
- app/editor/sprite/sprite_editor.cc
- app/editor/system/settings_editor.cc
## ROM ## Rom
- [app/rom.cc](../src/app/rom.cc)
- [app/rom.h](../src/app/rom.h)
---
This `ROM` class provides methods to manipulate and access data from a ROM. - app/rom.cc
- app/rom.h
- **Key Methods**: The Rom class provides methods to manipulate and access data from a ROM.
- `Load2BppGraphics()`: Loads 2BPP graphics data from specified sheets.
- `LoadAllGraphicsData()`: Loads all graphics data, both compressed and uncompressed, converting where necessary.
- `LoadFromFile(const absl::string_view& filename, bool z3_load)`: Loads ROM data from a file. It also handles headers and Zelda 3 specific data if requested.
- `LoadFromPointer(uchar* data, size_t length)`: Loads ROM data from a provided pointer.
- `LoadFromBytes(const Bytes& data)`: Loads ROM data from bytes.
- `LoadAllPalettes()`: Loads all color palettes used in the ROM. This includes palettes for various elements like sprites, shields, swords, etc.
- `UpdatePaletteColor(...)`: Updates a specific color within a named palette group.
- **Internal Data Structures**: Currently implemented as a singleton with SharedRom which is not great but has helped with development velocity. Potential room for improvement is to refactor the editors to take the ROM as a parameter.
- `rom_data_`: A container that holds the ROM data.
- `graphics_bin_`: Holds the graphics data.
- `palette_groups_`: A map containing various palette groups, each having its own set of color palettes.
- **Special Notes**:
- The class interacts with various external functionalities, such as decompression algorithms (`gfx::DecompressV2`) and color conversion (`gfx::SnesTo8bppSheet`).
- Headers in the ROM data, if present, are identified and removed.
- Specific Zelda 3 data can be loaded if specified.
- Palettes are categorized into multiple groups (e.g., `ow_main`, `ow_aux`, `hud`, etc.) and loaded accordingly.
## Bitmap ## Bitmap
- [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc) - app/gfx/bitmap.cc
- [app/gfx/bitmap.h](../src/app/gfx/bitmap.cc) - app/gfx/bitmap.h
---
This class is responsible for creating, managing, and manipulating bitmap data, which can be displayed on the screen using the ImGui library. This class is responsible for creating, managing, and manipulating bitmap data, which can be displayed on the screen using SDL2 Textures and the ImGui draw list. It also provides functions for exporting these bitmaps to the clipboard in PNG format using libpng.
### Key Attributes:
1. **Width, Height, Depth, and Data Size**: These represent the dimensions and data size of the bitmap.
2. **Pixel Data**: Points to the raw data of the bitmap.
3. **Texture and Surface**: Use SDL to manage the graphical representation of the bitmap data. Both these attributes have custom deleters, ensuring proper resource management.
### Main Functions:
1. **Constructors**: Multiple constructors allow for different ways to create a Bitmap instance, like specifying width, height, depth, and data.
2. **Create**: This set of overloaded functions provides ways to create a bitmap from different data sources.
3. **CreateFromSurface**: Allows for the creation of a bitmap from an SDL_Surface.
4. **Apply**: Changes the bitmap's data to a new set of Bytes.
5. **Texture Operations**:
- **CreateTexture**: Creates an SDL_Texture from the bitmap's data for rendering.
- **UpdateTexture**: Updates the SDL_Texture with the latest bitmap data.
6. **SaveSurfaceToFile**: Saves the SDL_Surface to a file.
7. **SetSurface**: Assigns a new SDL_Surface to the bitmap.
8. **Palette Functions**:
- **ApplyPalette (Overloaded)**: This allows for the application of a SNESPalette or a standard SDL_Color palette to the bitmap.
9. **WriteToPixel**: Directly writes a value to a specified position in the pixel data.
## Z3ED cli
| Command | Arg | Params | Status |
|---------|-----|--------|--------|
| Apply BPS Patch | -a | rom_file bps_file | In progress |
| Create BPS Patch | -c | bps_file src_file modified_file | Not started |
| Open ROM | -o | rom_file | Complete |
| Backup ROM | -b | rom_file [new_file] | In progress |
| Expand ROM | -x | rom_file file_size | Not started |
| Transfer Tile16 | -t | src_rom dest_rom tile32_id_list(csv) | Complete |
| Export Graphics | -e | rom_file bin_file | In progress |
| Import Graphics | -i | bin_file rom_file | Not started |
| SNES to PC Address | -s | address | Complete |
| PC to SNES Address | -p | address | Complete |
## Further Development Ideas
- Extend `zelda3` namespace with additional functionalities.
- Optimize program performance.
- Introduce new features in the GUI editor.

63
docs/yaze.org Normal file
View File

@@ -0,0 +1,63 @@
#+TITLE: yaze todo
#+SUBTITLE: yet another zelda3 editor todo list
#+AUTHOR: @scawful
#+TODO: TODO ACTIVE FEEDBACK VERIFY | DONE
* Daily Log
<2024-11-14 Thu>
Been making lots of adjustments and cleaning up old code. Primarily improving the dungeon map editor and supporting bin graphics for my Oracle of Secrets dungeon maps. Additionally, working to support saving for resources like graphics sheets and expanded the project file system.
<2024-09-07 Sat>
Various header cleanup using the LSP in emacs to detect unused includes.
Making adjustments to font loading so the editor can be opened from terminal/emacs.
Currently the font files and the zeml files require the binary to be relative to `assets/layouts` and `assets/fonts`
I've set it up so that the macOS app bundles the resources into the `yaze.app` so that the binary can be run from anywhere. This will need to be adjusted for other platforms.
<2024-09-02 Mon>
Extracted the DisplayPalette function out of the PaletteEditor and into its own standalone function.
<2024-09-01 Sun>
Started learning spacemacs and org-mode.
* Editors
** Overworld
*** TODO ZSCustomOverworld implementation.
**** DONE Custom Overworld Map Settings Inputs
**** DONE Load ZSCOW data from ROM in OverworldMap
**** TODO Add Main Palette support
**** TODO Add Custom Area BG Color support
*** TODO Fix sprite icon draw positions
*** TODO Fix exit icon draw positions
** Dungeon
*** TODO Draw dungeon objects
** Graphics
*** TODO Tile16 Editor
- [ ] Draw tile8 to tile16 quadrant.
*** TODO Fix graphics sheet pencil drawing
** Message
*** TODO Fix Message Parsing
** Palette
*** TODO Persist color changes for saving to ROM.
** Screens
*** ACTIVE Dungeon Maps
*** ACTIVE Inventory Menu
*** TODO Overworld Map
*** TODO Title Screen
*** TODO Naming Screen
* Infrastructure
** File Handling
*** TODO Update recent files manager to bundle the recent files list with the application
*** DONE Create a util for handling file operations from the bundled resources.
** Font Loading
*** TODO Make font sizes variables so they can be reloaded by the user.
** ZEML
*** DONE Package layout files with the executable to avoid relative file lookup

67
incl/dungeon.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef YAZE_BASE_DUNGEON_H_
#define YAZE_BASE_DUNGEON_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
typedef struct z3_object_door {
short id;
uint8_t x;
uint8_t y;
uint8_t size;
uint8_t type;
uint8_t layer;
} z3_object_door;
typedef struct z3_dungeon_destination {
uint8_t index;
uint8_t target;
uint8_t target_layer;
} z3_dungeon_destination;
typedef struct z3_staircase {
uint8_t id;
uint8_t room;
const char *label;
} z3_staircase;
typedef struct z3_chest {
uint8_t x;
uint8_t y;
uint8_t item;
bool picker;
bool big_chest;
} z3_chest;
typedef struct z3_chest_data {
uint8_t id;
bool size;
} z3_chest_data;
typedef enum z3_dungeon_background2 {
Off,
Parallax,
Dark,
OnTop,
Translucent,
Addition,
Normal,
Transparent,
DarkRoom
} z3_dungeon_background2;
typedef struct z3_dungeon_room {
z3_dungeon_background2 bg2;
z3_dungeon_destination pits;
z3_dungeon_destination stairs[4];
} z3_dungeon_room;
#ifdef __cplusplus
}
#endif
#endif // YAZE_BASE_DUNGEON_H_

47
incl/overworld.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef YAZE_OVERWORLD_H
#define YAZE_OVERWORLD_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "sprite.h"
/**
* @brief Primitive of an overworld map.
*/
typedef struct z3_overworld_map {
uint8_t id; /**< ID of the overworld map. */
uint8_t parent_id;
uint8_t quadrant_id;
uint8_t world_id;
uint8_t game_state;
uint8_t area_graphics;
uint8_t area_palette;
uint8_t sprite_graphics[3];
uint8_t sprite_palette[3];
uint8_t area_music[4];
uint8_t static_graphics[16];
} z3_overworld_map;
/**
* @brief Primitive of the overworld.
*/
typedef struct z3_overworld {
void *impl; // yaze::Overworld*
uint8_t *tile32_data; /**< Pointer to the 32x32 tile data. */
uint8_t *tile16_data; /**< Pointer to the 16x16 tile data. */
z3_sprite **sprites; /**< Pointer to the sprites per map. */
z3_overworld_map **maps; /**< Pointer to the overworld maps. */
} z3_overworld;
#ifdef __cplusplus
}
#endif
#endif // YAZE_OVERWORLD_H

32
incl/snes_color.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef YAZE_BASE_SNES_COLOR_H_
#define YAZE_BASE_SNES_COLOR_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* @brief Primitive of 16-bit RGB SNES color.
*/
typedef struct snes_color {
uint16_t red; /**< Red component of the color. */
uint16_t blue; /**< Blue component of the color. */
uint16_t green; /**< Green component of the color. */
} snes_color;
/**
* @brief Primitive of a SNES color palette.
*/
typedef struct snes_palette {
unsigned int id; /**< ID of the palette. */
unsigned int size; /**< Size of the palette. */
snes_color* colors; /**< Pointer to the colors in the palette. */
} snes_palette;
#ifdef __cplusplus
}
#endif
#endif // YAZE_BASE_SNES_COLOR_H_

40
incl/snes_tile.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef YAZE_INCL_SNES_TILE_H
#define YAZE_INCL_SNES_TILE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
typedef struct snes_tile8 {
uint32_t id;
uint32_t palette_id;
uint8_t data[64];
} snes_tile8;
typedef struct snes_tile_info {
uint16_t id;
uint8_t palette;
bool priority;
bool vertical_mirror;
bool horizontal_mirror;
} snes_tile_info;
typedef struct snes_tile16 {
snes_tile_info tiles[4];
} snes_tile16;
typedef struct snes_tile32 {
uint16_t t0;
uint16_t t1;
uint16_t t2;
uint16_t t3;
} snes_tile32;
#ifdef __cplusplus
}
#endif
#endif

23
incl/sprite.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef YAZE_BASE_SPRITE_H_
#define YAZE_BASE_SPRITE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* @brief Primitive of a sprite.
*/
typedef struct z3_sprite {
const char* name; /**< Name of the sprite. */
uint8_t id; /**< ID of the sprite. */
uint8_t subtype; /**< Subtype of the sprite. */
} z3_sprite;
#ifdef __cplusplus
}
#endif
#endif // YAZE_BASE_SPRITE_H_

159
incl/yaze.h Normal file
View File

@@ -0,0 +1,159 @@
#ifndef YAZE_H
#define YAZE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
#include "dungeon.h"
#include "overworld.h"
#include "snes_color.h"
#include "sprite.h"
typedef struct z3_rom z3_rom;
typedef struct yaze_project yaze_project;
typedef struct yaze_command_registry yaze_command_registry;
typedef struct yaze_event_dispatcher yaze_event_dispatcher;
typedef struct yaze_editor_context {
z3_rom* rom;
yaze_project* project;
yaze_command_registry* command_registry;
yaze_event_dispatcher* event_dispatcher;
} yaze_editor_context;
void yaze_check_version(const char* version);
int yaze_init(yaze_editor_context*);
void yaze_cleanup(yaze_editor_context*);
struct yaze_project {
const char* name;
const char* filepath;
const char* rom_filename;
const char* code_folder;
const char* labels_filename;
};
yaze_project yaze_load_project(const char* filename);
struct z3_rom {
const char* filename;
const uint8_t* data;
size_t size;
void* impl; // yaze::Rom*
};
z3_rom* yaze_load_rom(const char* filename);
void yaze_unload_rom(z3_rom* rom);
typedef struct yaze_bitmap {
int width;
int height;
uint8_t bpp;
uint8_t* data;
} yaze_bitmap;
yaze_bitmap yaze_load_bitmap(const char* filename);
snes_color yaze_get_color_from_paletteset(const z3_rom* rom, int palette_set,
int palette, int color);
z3_overworld* yaze_load_overworld(const z3_rom* rom);
z3_dungeon_room* yaze_load_all_rooms(const z3_rom* rom);
struct yaze_command_registry {
void (*register_command)(const char* name, void (*command)(void));
};
struct yaze_event_dispatcher {
void (*register_event_hook)(void (*event_hook)(void));
};
typedef void (*yaze_initialize_func)(yaze_editor_context* context);
typedef void (*yaze_cleanup_func)(void);
typedef void (*yaze_extend_ui_func)(yaze_editor_context* context);
typedef void (*yaze_manipulate_rom_func)(z3_rom* rom);
typedef void (*yaze_command_func)(void);
typedef void (*yaze_event_hook_func)(void);
typedef enum {
YAZE_EVENT_ROM_LOADED,
YAZE_EVENT_ROM_SAVED,
YAZE_EVENT_SPRITE_MODIFIED,
YAZE_EVENT_PALETTE_CHANGED,
} yaze_event_type;
/**
* @brief Extension interface for Yaze.
*
* @details Yaze extensions can be written in C or Python.
*/
typedef struct yaze_extension {
const char* name;
const char* version;
/**
* @brief Function to initialize the extension.
*
* @details This function is called when the extension is loaded. It can be
* used to set up any resources or state needed by the extension.
*/
yaze_initialize_func initialize;
/**
* @brief Function to clean up the extension.
*
* @details This function is called when the extension is unloaded. It can be
* used to clean up any resources or state used by the extension.
*/
yaze_cleanup_func cleanup;
/**
* @brief Function to manipulate the ROM.
*
* @param rom The ROM to manipulate.
*
*/
yaze_manipulate_rom_func manipulate_rom;
/**
* @brief Function to extend the UI.
*
* @param context The editor context.
*
* @details This function is called when the extension is loaded. It can be
* used to add custom UI elements to the editor. The context parameter
* provides access to the project, command registry, event dispatcher, and
* ImGui context.
*/
yaze_extend_ui_func extend_ui;
/**
* @brief Register commands in the yaze_command_registry.
*/
yaze_command_func register_commands;
/**
* @brief Register custom tools in the yaze_command_registry.
*/
yaze_command_func register_custom_tools;
/**
* @brief Register event hooks in the yaze_event_dispatcher.
*/
void (*register_event_hooks)(yaze_event_type event,
yaze_event_hook_func hook);
} yaze_extension;
#ifdef __cplusplus
}
#endif
#endif // YAZE_H

View File

@@ -1,32 +1,3 @@
set(
YAZE_APP_CORE_SRC
app/core/common.cc
app/core/controller.cc
app/core/labeling.cc
app/emu/emulator.cc
)
set(
YAZE_APP_GFX_SRC
app/gfx/bitmap.cc
app/gfx/compression.cc
app/gfx/scad_format.cc
app/gfx/snes_palette.cc
app/gfx/snes_tile.cc
app/gfx/snes_color.cc
app/gfx/tilesheet.cc
)
set(
YAZE_GUI_SRC
app/gui/asset_browser.cc
app/gui/canvas.cc
app/gui/input.cc
app/gui/style.cc
app/gui/color.cc
app/gui/zeml.cc
)
set( set(
YAZE_APP_EMU_SRC YAZE_APP_EMU_SRC
app/emu/audio/apu.cc app/emu/audio/apu.cc
@@ -43,70 +14,126 @@ set(
app/emu/snes.cc app/emu/snes.cc
) )
set(SDL_TARGETS SDL2::SDL2) set(YAZE_RESOURCE_FILES
${CMAKE_SOURCE_DIR}/assets/layouts/overworld.zeml
${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf
${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf
${CMAKE_SOURCE_DIR}/assets/font/Cousine-Regular.ttf
${CMAKE_SOURCE_DIR}/assets/font/DroidSans.ttf
${CMAKE_SOURCE_DIR}/assets/font/NotoSansJP.ttf
${CMAKE_SOURCE_DIR}/assets/font/IBMPlexSansJP-Bold.ttf
${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf
)
if(WIN32 OR MINGW) foreach (FILE ${YAZE_RESOURCE_FILES})
list(PREPEND SDL_TARGETS SDL2::SDL2main ws2_32) file(RELATIVE_PATH NEW_FILE "${CMAKE_SOURCE_DIR}/assets" ${FILE})
add_definitions(-DSDL_MAIN_HANDLED) get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
endif() set_source_files_properties(${FILE}
PROPERTIES
if (WIN32 OR MINGW OR UNIX AND NOT APPLE) MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}"
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/font_loader.cc
app/core/platform/clipboard.cc
) )
endforeach()
if (YAZE_BUILD_APP)
include(app/app.cmake)
endif()
if (YAZE_BUILD_EMU)
include(app/emu/emu.cmake)
endif()
if (YAZE_BUILD_Z3ED)
include(cli/z3ed.cmake)
endif()
if (YAZE_BUILD_PYTHON)
include(cli/python/yaze_py.cmake)
endif()
if (YAZE_BUILD_TESTS)
include(test/CMakeLists.txt)
endif() endif()
if(APPLE) if(MACOS)
list(APPEND YAZE_APP_CORE_SRC set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/win32/yaze.ico)
app/core/platform/file_dialog.mm set_target_properties(yaze
app/core/platform/app_delegate.mm PROPERTIES
app/core/platform/font_loader.mm BUNDLE True
app/core/platform/clipboard.mm ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
app/core/platform/file_path.mm 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
find_library(COCOA_LIBRARY Cocoa) RESOURCE ${YAZE_RESOURCE_FILES}
if(NOT COCOA_LIBRARY)
message(FATAL_ERROR "Cocoa not found")
endif()
set(CMAKE_EXE_LINKER_FLAGS "-framework ServiceManagement -framework Foundation -framework Cocoa")
endif()
include(app/CMakeLists.txt)
# include(cli/CMakeLists.txt) Excluded for now, macOS include breaks action build
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) elseif(UNIX)
set_target_properties(yaze set_target_properties(yaze
PROPERTIES PROPERTIES
BUNDLE True BUNDLE True
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
) )
target_compile_definitions(yaze PRIVATE "linux")
target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
else() else()
set_target_properties(yaze set_target_properties(yaze
PROPERTIES PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res" LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/win32/yaze.res"
) )
endif() endif()
add_subdirectory(test) # Yaze C API
if (YAZE_BUILD_LIB)
add_library(
yaze_c SHARED
./yaze.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
)
target_include_directories(
yaze_c PUBLIC
lib/
app/
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
${PROJECT_BINARY_DIR}
)
target_link_libraries(
yaze_c PRIVATE
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGuiTestEngine
ImGui
)
if (YAZE_INSTALL_LIB)
install(TARGETS yaze_c
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
install(
FILES
yaze.h
incl/sprite.h
incl/snes_tile.h
incl/snes_color.h
incl/overworld.h
incl/dungeon.h
DESTINATION
include
)
endif()
endif()

View File

@@ -1,40 +0,0 @@
include(app/editor/CMakeLists.txt)
include(app/zelda3/CMakeLists.txt)
add_executable(
yaze
app/yaze.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
)
target_include_directories(
yaze PUBLIC
lib/
app/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
target_link_libraries(
yaze PUBLIC
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGuiTestEngine
ImGui
)
if (APPLE)
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
endif()

65
src/app/app.cmake Normal file
View File

@@ -0,0 +1,65 @@
include(app/core/core.cmake)
include(app/editor/editor.cmake)
include(app/gfx/gfx.cmake)
include(app/gui/gui.cmake)
include(app/zelda3/zelda3.cmake)
if (APPLE)
add_executable(
yaze
MACOSX_BUNDLE
app/main.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
# Bundled Resources
${YAZE_RESOURCE_FILES}
)
else()
add_executable(
yaze
app/main.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
)
endif()
target_include_directories(
yaze PUBLIC
lib/
app/
${ASAR_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
${PROJECT_BINARY_DIR}
)
target_link_libraries(
yaze PUBLIC
asar-static
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGui
ImGuiTestEngine
)
if (APPLE)
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
endif()

View File

@@ -1,112 +1,64 @@
#include "common.h" #include "common.h"
#include "imgui/imgui.h" #include <zlib.h>
#include <chrono>
#include <cstdint> #include <cstdint>
#include <functional> #include <cstring>
#include <memory> #include <memory>
#include <stack>
#include <string> #include <string>
#include <vector>
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_; namespace {
std::string UppercaseHexByte(uint8_t byte, bool leading) { void encode(uint64_t data, std::vector<uint8_t> &output) {
if (leading) { while (true) {
std::string result = absl::StrFormat("0x%02X", byte); uint8_t x = data & 0x7f;
return result; data >>= 7;
} if (data == 0) {
std::string result = absl::StrFormat("%02X", byte); output.push_back(0x80 | x);
return result; break;
}
std::string UppercaseHexWord(uint16_t word) {
std::string result = absl::StrFormat("0x%04X", word);
return result;
}
std::string UppercaseHexLong(uint32_t dword) {
std::string result = absl::StrFormat("0x%06X", dword);
return result;
}
uint32_t SnesToPc(uint32_t addr) {
if (addr >= 0x808000) {
addr -= 0x808000;
}
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
return (temp + 0x0);
}
uint32_t PcToSnes(uint32_t addr) {
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
b[2] = static_cast<uint8_t>(b[2] * 2);
if (b[1] >= 0x80) {
b[2] += 1;
} else {
b[1] += 0x80;
}
return addr;
}
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) {
uint32_t result = 0;
result = (bank << 16) | addr;
return result;
}
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;
} }
output.push_back(x);
if (j == ceiling) { data--;
result += value;
continue;
}
result += (value * power16);
power16 *= 16;
} }
return result;
} }
bool StringReplace(std::string &str, const std::string &from, uint64_t decode(const std::vector<uint8_t> &input, size_t &offset) {
const std::string &to) { uint64_t data = 0;
size_t start = str.find(from); uint64_t shift = 1;
if (start == std::string::npos) return false; while (true) {
uint8_t x = input[offset++];
data += (x & 0x7f) * shift;
if (x & 0x80) break;
shift <<= 7;
data += shift;
}
return data;
}
str.replace(start, from.length(), to); uint32_t crc32(const std::vector<uint8_t> &data) {
return true; uint32_t crc = ::crc32(0L, Z_NULL, 0);
return ::crc32(crc, data.data(), data.size());
}
// "load little endian value at the given byte offset and shift to get its
// value relative to the base offset (powers of 256, essentially)"
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
uint32_t v = p_arr[p_index];
v <<= (8 * p_index);
return v;
} }
void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) { void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) {
uint8_t v = (p_val >> (8 * p_index)) & 0xff; uint8_t v = (p_val >> (8 * p_index)) & 0xff;
p_arr[p_index] = v; p_arr[p_index] = v;
} }
@@ -125,25 +77,6 @@ void stle2(uint8_t *const p_arr, unsigned const p_val) {
void stle3(uint8_t *const p_arr, unsigned const p_val) { void stle3(uint8_t *const p_arr, unsigned const p_val) {
stle(p_arr, 3, p_val); stle(p_arr, 3, p_val);
} }
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
stle0(p_arr, p_val);
stle1(p_arr, p_val);
}
// "Store little endian 16-bit value using a byte pointer, offset by an
// index before dereferencing"
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val) {
stle16b(p_arr + (p_index * 2), p_val);
}
// "load little endian value at the given byte offset and shift to get its
// value relative to the base offset (powers of 256, essentially)"
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
uint32_t v = p_arr[p_index];
v <<= (8 * p_index);
return v;
}
// Helper function to get the first byte in a little endian number // Helper function to get the first byte in a little endian number
uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); } uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); }
@@ -156,32 +89,275 @@ uint32_t ldle2(uint8_t const *const p_arr) { return ldle(p_arr, 2); }
// Helper function to get the third byte in a little endian number // Helper function to get the third byte in a little endian number
uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); } uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); }
// Load little endian halfword (16-bit) dereferenced from
uint16_t ldle16b(uint8_t const *const p_arr) {
uint16_t v = 0;
v |= (ldle0(p_arr) | ldle1(p_arr)); void HandleHexStringParams(const std::string &hex,
const HexStringParams &params) {
return v; std::string result = hex;
} switch (params.prefix) {
// Load little endian halfword (16-bit) dereferenced from an arrays of bytes. case HexStringParams::Prefix::kDollar:
// This version provides an index that will be multiplied by 2 and added to the result = absl::StrCat("$", result);
// base address. break;
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) { case HexStringParams::Prefix::kHash:
return ldle16b(p_arr + (2 * p_index)); result = absl::StrCat("#", result);
break;
case HexStringParams::Prefix::k0x:
result = absl::StrCat("0x", result);
case HexStringParams::Prefix::kNone:
default:
break;
}
} }
// Initialize the static member } // namespace
std::stack<ImGuiID> ImGuiIdIssuer::idStack;
std::string HexByte(uint8_t byte, HexStringParams params) {
std::string result;
const static std::string kLowerFormat = "%02x";
const static std::string kUpperFormat = "%02X";
if (params.uppercase) {
result = absl::StrFormat(kUpperFormat.c_str(), byte);
} else {
result = absl::StrFormat(kLowerFormat.c_str(), byte);
}
HandleHexStringParams(result, params);
return result;
}
std::string HexWord(uint16_t word, HexStringParams params) {
std::string result;
const static std::string kLowerFormat = "%04x";
const static std::string kUpperFormat = "%04X";
if (params.uppercase) {
result = absl::StrFormat(kUpperFormat.c_str(), word);
} else {
result = absl::StrFormat(kLowerFormat.c_str(), word);
}
HandleHexStringParams(result, params);
return result;
}
std::string HexLong(uint32_t dword, HexStringParams params) {
std::string result;
const static std::string kLowerFormat = "%06x";
const static std::string kUpperFormat = "%06X";
if (params.uppercase) {
result = absl::StrFormat(kUpperFormat.c_str(), dword);
} else {
result = absl::StrFormat(kLowerFormat.c_str(), dword);
}
HandleHexStringParams(result, params);
return result;
}
std::string HexLongLong(uint64_t qword, HexStringParams params) {
std::string result;
const static std::string kLowerFormat = "%08x";
const static std::string kUpperFormat = "%08X";
if (params.uppercase) {
result = absl::StrFormat(kUpperFormat.c_str(), qword);
} else {
result = absl::StrFormat(kLowerFormat.c_str(), qword);
}
HandleHexStringParams(result, params);
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;
}
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc) { uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc) {
uint32_t ret = (PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr]; uint32_t ret =
(PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr];
if (pc) { if (pc) {
return SnesToPc(ret); return SnesToPc(ret);
} }
return ret; return ret;
} }
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val) {
stle16b(p_arr + (p_index * 2), p_val);
}
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
stle0(p_arr, p_val);
stle1(p_arr, p_val);
}
uint16_t ldle16b(uint8_t const *const p_arr) {
uint16_t v = 0;
v |= (ldle0(p_arr) | ldle1(p_arr));
return v;
}
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
return ldle16b(p_arr + (2 * p_index));
}
void CreateBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &target,
std::vector<uint8_t> &patch) {
patch.clear();
patch.insert(patch.end(), {'B', 'P', 'S', '1'});
encode(source.size(), patch);
encode(target.size(), patch);
encode(0, patch); // No metadata
size_t source_offset = 0;
size_t target_offset = 0;
int64_t source_rel_offset = 0;
int64_t target_rel_offset = 0;
while (target_offset < target.size()) {
if (source_offset < source.size() &&
source[source_offset] == target[target_offset]) {
size_t length = 0;
while (source_offset + length < source.size() &&
target_offset + length < target.size() &&
source[source_offset + length] == target[target_offset + length]) {
length++;
}
encode((length - 1) << 2 | 0, patch); // SourceRead
source_offset += length;
target_offset += length;
} else {
size_t length = 0;
while (
target_offset + length < target.size() &&
(source_offset + length >= source.size() ||
source[source_offset + length] != target[target_offset + length])) {
length++;
}
if (length > 0) {
encode((length - 1) << 2 | 1, patch); // TargetRead
for (size_t i = 0; i < length; i++) {
patch.push_back(target[target_offset + i]);
}
target_offset += length;
}
}
// SourceCopy
if (source_offset < source.size()) {
size_t length = 0;
int64_t offset = source_offset - source_rel_offset;
while (source_offset + length < source.size() &&
target_offset + length < target.size() &&
source[source_offset + length] == target[target_offset + length]) {
length++;
}
if (length > 0) {
encode((length - 1) << 2 | 2, patch);
encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch);
source_offset += length;
target_offset += length;
source_rel_offset = source_offset;
}
}
// TargetCopy
if (target_offset > 0) {
size_t length = 0;
int64_t offset = target_offset - target_rel_offset;
while (target_offset + length < target.size() &&
target[target_offset - 1] == target[target_offset + length]) {
length++;
}
if (length > 0) {
encode((length - 1) << 2 | 3, patch);
encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch);
target_offset += length;
target_rel_offset = target_offset;
}
}
}
patch.resize(patch.size() + 12); // Make space for the checksums
uint32_t source_checksum = crc32(source);
uint32_t target_checksum = crc32(target);
uint32_t patch_checksum = crc32(patch);
memcpy(patch.data() + patch.size() - 12, &source_checksum, sizeof(uint32_t));
memcpy(patch.data() + patch.size() - 8, &target_checksum, sizeof(uint32_t));
memcpy(patch.data() + patch.size() - 4, &patch_checksum, sizeof(uint32_t));
}
void ApplyBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &patch,
std::vector<uint8_t> &target) {
if (patch.size() < 4 || patch[0] != 'B' || patch[1] != 'P' ||
patch[2] != 'S' || patch[3] != '1') {
throw std::runtime_error("Invalid patch format");
}
size_t patch_offset = 4;
uint64_t target_size = decode(patch, patch_offset);
uint64_t metadata_size = decode(patch, patch_offset);
patch_offset += metadata_size;
target.resize(target_size);
size_t source_offset = 0;
size_t target_offset = 0;
int64_t source_rel_offset = 0;
int64_t target_rel_offset = 0;
while (patch_offset < patch.size() - 12) {
uint64_t data = decode(patch, patch_offset);
uint64_t command = data & 3;
uint64_t length = (data >> 2) + 1;
switch (command) {
case 0: // SourceRead
while (length--) {
target[target_offset++] = source[source_offset++];
}
break;
case 1: // TargetRead
while (length--) {
target[target_offset++] = patch[patch_offset++];
}
break;
case 2: // SourceCopy
{
int64_t offsetData = decode(patch, patch_offset);
source_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1);
while (length--) {
target[target_offset++] = source[source_rel_offset++];
}
} break;
case 3: // TargetCopy
{
uint64_t offsetData = decode(patch, patch_offset);
target_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1);
while (length--) {
target[target_offset++] = target[target_rel_offset++];
}
}
default:
throw std::runtime_error("Invalid patch command");
}
}
uint32_t source_checksum;
uint32_t target_checksum;
uint32_t patch_checksum;
memcpy(&source_checksum, patch.data() + patch.size() - 12, sizeof(uint32_t));
memcpy(&target_checksum, patch.data() + patch.size() - 8, sizeof(uint32_t));
memcpy(&patch_checksum, patch.data() + patch.size() - 4, sizeof(uint32_t));
if (source_checksum != crc32(source) || target_checksum != crc32(target) ||
patch_checksum !=
crc32(std::vector<uint8_t>(patch.begin(), patch.end() - 4))) {
throw std::runtime_error("Checksum mismatch");
}
}
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,26 +1,37 @@
#ifndef YAZE_CORE_COMMON_H #ifndef YAZE_CORE_COMMON_H
#define YAZE_CORE_COMMON_H #define YAZE_CORE_COMMON_H
#include "imgui/imgui.h"
#include <chrono>
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <functional>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <stack>
#include <string> #include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
namespace yaze { namespace yaze {
namespace app {
/** /**
* @namespace yaze::app::core * @namespace yaze::core
* @brief Core application logic and utilities. * @brief Core application logic and utilities.
*/ */
namespace core { namespace core {
struct HexStringParams {
enum class Prefix { kNone, kDollar, kHash, k0x } prefix = Prefix::kDollar;
bool uppercase = true;
};
std::string HexByte(uint8_t byte, HexStringParams params = {});
std::string HexWord(uint16_t word, HexStringParams params = {});
std::string HexLong(uint32_t dword, HexStringParams params = {});
std::string HexLongLong(uint64_t qword, HexStringParams params = {});
bool StringReplace(std::string &str, const std::string &from,
const std::string &to);
/** /**
* @class ExperimentFlags * @class ExperimentFlags
* @brief A class to manage experimental feature flags. * @brief A class to manage experimental feature flags.
@@ -28,17 +39,9 @@ namespace core {
class ExperimentFlags { class ExperimentFlags {
public: public:
struct Flags { struct Flags {
// Bitmap manager abstraction to manage graphics bin of Rom.
bool kUseBitmapManager = true;
// Log instructions to the GUI debugger. // Log instructions to the GUI debugger.
bool kLogInstructions = true; bool kLogInstructions = true;
// Flag to enable ImGui input config flags. Currently is
// handled manually by controller class but should be
// ported away from that eventually.
bool kUseNewImGuiInput = false;
// Flag to enable the saving of all palettes to the Rom. // Flag to enable the saving of all palettes to the Rom.
bool kSaveAllPalettes = false; bool kSaveAllPalettes = false;
@@ -56,22 +59,18 @@ class ExperimentFlags {
// Use the new platform specific file dialog wrappers. // Use the new platform specific file dialog wrappers.
bool kNewFileDialogWrapper = true; bool kNewFileDialogWrapper = true;
// Platform specific loading of fonts from the system. Currently
// only supports macOS.
bool kLoadSystemFonts = true;
// Uses texture streaming from SDL for my dynamic updates. // Uses texture streaming from SDL for my dynamic updates.
bool kLoadTexturesAsStreaming = true; bool kLoadTexturesAsStreaming = true;
// Save dungeon map edits to the Rom. // Save dungeon map edits to the Rom.
bool kSaveDungeonMaps = false; bool kSaveDungeonMaps = false;
// Save graphics sheet to the Rom.
bool kSaveGraphicsSheet = false;
// Log to the console. // Log to the console.
bool kLogToConsole = false; bool kLogToConsole = false;
// Load audio device for emulator
bool kLoadAudioDevice = false;
// Overworld flags // Overworld flags
struct Overworld { struct Overworld {
// Load and render overworld sprites to the screen. Unstable. // Load and render overworld sprites to the screen. Unstable.
@@ -91,27 +90,50 @@ class ExperimentFlags {
// Save overworld properties to the Rom. // Save overworld properties to the Rom.
bool kSaveOverworldProperties = true; bool kSaveOverworldProperties = true;
// Load custom overworld data from the ROM and enable UI.
bool kLoadCustomOverworld = false;
} overworld; } overworld;
}; };
ExperimentFlags() = default; static Flags &get() {
virtual ~ExperimentFlags() = default; static Flags instance;
auto flags() const { return instance;
if (!flags_) {
flags_ = std::make_shared<Flags>();
}
Flags *flags = flags_.get();
return flags;
}
Flags *mutable_flags() {
if (!flags_) {
flags_ = std::make_shared<Flags>();
}
return flags_.get();
} }
private: std::string Serialize() const {
static std::shared_ptr<Flags> flags_; std::string result;
result +=
"kLogInstructions: " + std::to_string(get().kLogInstructions) + "\n";
result +=
"kSaveAllPalettes: " + std::to_string(get().kSaveAllPalettes) + "\n";
result += "kSaveGfxGroups: " + std::to_string(get().kSaveGfxGroups) + "\n";
result +=
"kSaveWithChangeQueue: " + std::to_string(get().kSaveWithChangeQueue) +
"\n";
result += "kDrawDungeonRoomGraphics: " +
std::to_string(get().kDrawDungeonRoomGraphics) + "\n";
result += "kNewFileDialogWrapper: " +
std::to_string(get().kNewFileDialogWrapper) + "\n";
result += "kLoadTexturesAsStreaming: " +
std::to_string(get().kLoadTexturesAsStreaming) + "\n";
result +=
"kSaveDungeonMaps: " + std::to_string(get().kSaveDungeonMaps) + "\n";
result += "kLogToConsole: " + std::to_string(get().kLogToConsole) + "\n";
result += "kDrawOverworldSprites: " +
std::to_string(get().overworld.kDrawOverworldSprites) + "\n";
result += "kSaveOverworldMaps: " +
std::to_string(get().overworld.kSaveOverworldMaps) + "\n";
result += "kSaveOverworldEntrances: " +
std::to_string(get().overworld.kSaveOverworldEntrances) + "\n";
result += "kSaveOverworldExits: " +
std::to_string(get().overworld.kSaveOverworldExits) + "\n";
result += "kSaveOverworldItems: " +
std::to_string(get().overworld.kSaveOverworldItems) + "\n";
result += "kSaveOverworldProperties: " +
std::to_string(get().overworld.kSaveOverworldProperties) + "\n";
return result;
}
}; };
/** /**
@@ -160,63 +182,73 @@ class NotifyValue {
T temp_value_; T temp_value_;
}; };
class ImGuiIdIssuer { static bool log_to_console = false;
private: static const std::string kLogFileOut = "yaze_log.txt";
static std::stack<ImGuiID> idStack;
public: template <typename... Args>
// Generate and push a new ID onto the stack static void logf(const absl::FormatSpec<Args...> &format, const Args &...args) {
static ImGuiID GetNewID() { std::string message = absl::StrFormat(format, args...);
static int counter = 1; // Start from 1 to ensure uniqueness if (log_to_console) {
ImGuiID child_id = ImGui::GetID((void *)(intptr_t)counter++); std::cout << message << std::endl;
idStack.push(child_id); }
return child_id; static std::ofstream fout(kLogFileOut, std::ios::out | std::ios::app);
fout << message << std::endl;
}
constexpr uint32_t kFastRomRegion = 0x808000;
inline uint32_t SnesToPc(uint32_t addr) noexcept {
if (addr >= kFastRomRegion) {
addr -= kFastRomRegion;
}
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
return (temp + 0x0);
}
inline uint32_t PcToSnes(uint32_t addr) {
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
b[2] = static_cast<uint8_t>(b[2] * 2);
if (b[1] >= 0x80) {
b[2] += 1;
} else {
b[1] += 0x80;
} }
// Pop all IDs from the stack (can be called explicitly or upon program exit) return addr;
static void Cleanup() { }
while (!idStack.empty()) {
idStack.pop();
}
}
};
class Logger { inline int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept {
public: return (bank << 16) | (high << 8) | low;
static void log(std::string message) { }
static std::ofstream fout("log.txt", std::ios::out | std::ios::app);
fout << message << std::endl;
}
// log to console inline uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) noexcept {
static void logc(std::string message) { logs.emplace_back(message); } uint32_t result = 0;
result = (bank << 16) | addr;
return result;
}
static std::vector<std::string> logs; uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true);
};
std::string UppercaseHexByte(uint8_t byte, bool leading = false);
std::string UppercaseHexWord(uint16_t word);
std::string UppercaseHexLong(uint32_t dword);
uint32_t SnesToPc(uint32_t addr);
uint32_t PcToSnes(uint32_t addr);
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t 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);
/**
* @brief Store little endian 16-bit value using a byte pointer, offset by an
* index before dereferencing
*/
void stle16b_i(uint8_t *const p_arr, size_t const p_index, void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val); uint16_t const p_val);
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
uint16_t ldle16b(uint8_t const *const p_arr);
void stle16b(uint8_t *const p_arr, uint16_t const p_val); void stle16b(uint8_t *const p_arr, uint16_t const p_val);
/**
* @brief Load little endian halfword (16-bit) dereferenced from an arrays of
* bytes. This version provides an index that will be multiplied by 2 and added
* to the base address.
*/
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
// Load little endian halfword (16-bit) dereferenced from
uint16_t ldle16b(uint8_t const *const p_arr);
struct FolderItem { struct FolderItem {
std::string name; std::string name;
std::vector<FolderItem> subfolders; std::vector<FolderItem> subfolders;
@@ -225,10 +257,15 @@ struct FolderItem {
typedef struct FolderItem FolderItem; typedef struct FolderItem FolderItem;
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true); void CreateBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &target,
std::vector<uint8_t> &patch);
void ApplyBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &patch,
std::vector<uint8_t> &target);
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

View File

@@ -1,15 +1,6 @@
#ifndef YAZE_APP_CORE_CONSTANTS_H #ifndef YAZE_APP_CORE_CONSTANTS_H
#define YAZE_APP_CORE_CONSTANTS_H #define YAZE_APP_CORE_CONSTANTS_H
#include <vector>
#include "absl/strings/string_view.h"
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
#define END_TAB_BAR() \
ImGui::EndTabBar(); \
}
#define TAB_ITEM(w) if (ImGui::BeginTabItem(w)) { #define TAB_ITEM(w) if (ImGui::BeginTabItem(w)) {
#define END_TAB_ITEM() \ #define END_TAB_ITEM() \
ImGui::EndTabItem(); \ ImGui::EndTabItem(); \
@@ -121,732 +112,5 @@
using ushort = unsigned short; using ushort = unsigned short;
using uint = unsigned int; using uint = unsigned int;
using uchar = unsigned char; using uchar = unsigned char;
using Bytes = std::vector<uint8_t>;
using OWBlockset = std::vector<std::vector<uint16_t>>;
struct OWMapTiles {
OWBlockset light_world; // 64 maps
OWBlockset dark_world; // 64 maps
OWBlockset special_world; // 32 maps
};
using OWMapTiles = struct OWMapTiles;
namespace yaze {
namespace app {
namespace core {
constexpr uint32_t kRedPen = 0xFF0000FF;
constexpr float kYazeVersion = 0.2;
// ============================================================================
// Magic numbers
// ============================================================================
/// Bit set for object priority
constexpr ushort TilePriorityBit = 0x2000;
/// Bit set for object hflip
constexpr ushort TileHFlipBit = 0x4000;
/// Bit set for object vflip
constexpr ushort TileVFlipBit = 0x8000;
/// Bits used for tile name
constexpr ushort TileNameMask = 0x03FF;
constexpr int Uncompressed3BPPSize = 0x0600;
constexpr int UncompressedSheetSize = 0x0800;
constexpr int NumberOfRooms = 296;
constexpr int NumberOfColors = 3143;
// ============================================================================
// Game Graphics
// ============================================================================
constexpr int tile_address = 0x1B52; // JP = Same
constexpr int tile_address_floor = 0x1B5A; // JP = Same
constexpr int subtype1_tiles = 0x8000; // JP = Same
constexpr int subtype2_tiles = 0x83F0; // JP = Same
constexpr int subtype3_tiles = 0x84F0; // JP = Same
constexpr int gfx_animated_pointer = 0x10275; // JP 0x10624 //long pointer
constexpr int hud_palettes = 0xDD660;
constexpr int maxGfx = 0xC3FB5;
constexpr int kTilesheetWidth = 128;
constexpr int kTilesheetHeight = 32;
constexpr int kTilesheetDepth = 8;
// TEXT EDITOR RELATED CONSTANTS
constexpr int gfx_font = 0x70000; // 2bpp format
constexpr int text_data = 0xE0000;
constexpr int text_data2 = 0x75F40;
constexpr int pointers_dictionaries = 0x74703;
constexpr int characters_width = 0x74ADF;
constexpr int entrance_gfx_group = 0x5D97;
// ============================================================================
// Gravestones related variables
// ============================================================================
constexpr int GravesYTilePos = 0x49968; // short (0x0F entries)
constexpr int GravesXTilePos = 0x49986; // short (0x0F entries)
constexpr int GravesTilemapPos = 0x499A4; // short (0x0F entries)
constexpr int GravesGFX = 0x499C2; // short (0x0F entries)
constexpr int GravesXPos = 0x4994A; // short (0x0F entries)
constexpr int GravesYLine = 0x4993A; // short (0x08 entries)
constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
constexpr int GraveLinkSpecialHole = 0x46DD9; // short
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
// ============================================================================
// Names
// ============================================================================
static const std::string RoomEffect[] = {"Nothing",
"Nothing",
"Moving Floor",
"Moving Water",
"Trinexx Shell",
"Red Flashes",
"Light Torch to See Floor",
"Ganon's Darkness"};
static const std::string RoomTag[] = {"Nothing",
"NW Kill Enemy to Open",
"NE Kill Enemy to Open",
"SW Kill Enemy to Open",
"SE Kill Enemy to Open",
"W Kill Enemy to Open",
"E Kill Enemy to Open",
"N Kill Enemy to Open",
"S Kill Enemy to Open",
"Clear Quadrant to Open",
"Clear Full Tile to Open",
"NW Push Block to Open",
"NE Push Block to Open",
"SW Push Block to Open",
"SE Push Block to Open",
"W Push Block to Open",
"E Push Block to Open",
"N Push Block to Open",
"S Push Block to Open",
"Push Block to Open",
"Pull Lever to Open",
"Collect Prize to Open",
"Hold Switch Open Door",
"Toggle Switch to Open Door",
"Turn off Water",
"Turn on Water",
"Water Gate",
"Water Twin",
"Moving Wall Right",
"Moving Wall Left",
"Crash",
"Crash",
"Push Switch Exploding Wall",
"Holes 0",
"Open Chest (Holes 0)",
"Holes 1",
"Holes 2",
"Defeat Boss for Dungeon Prize",
"SE Kill Enemy to Push Block",
"Trigger Switch Chest",
"Pull Lever Exploding Wall",
"NW Kill Enemy for Chest",
"NE Kill Enemy for Chest",
"SW Kill Enemy for Chest",
"SE Kill Enemy for Chest",
"W Kill Enemy for Chest",
"E Kill Enemy for Chest",
"N Kill Enemy for Chest",
"S Kill Enemy for Chest",
"Clear Quadrant for Chest",
"Clear Full Tile for Chest",
"Light Torches to Open",
"Holes 3",
"Holes 4",
"Holes 5",
"Holes 6",
"Agahnim Room",
"Holes 7",
"Holes 8",
"Open Chest for Holes 8",
"Push Block for Chest",
"Clear Room for Triforce Door",
"Light Torches for Chest",
"Kill Boss Again"};
static const std::string SecretItemNames[] = {
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
"Bomb", "Heart ", "Blue Rupee",
"Key", "Arrow", "Bomb", "Heart", "Magic",
"Full Magic", "Cucco", "Green Soldier", "Bush Stal", "Blue Soldier",
"Landmine", "Heart", "Fairy", "Heart",
"Nothing ", // 22
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
static const std::string TileTypeNames[] = {
"$00 Nothing (standard floor)",
"$01 Collision",
"$02 Collision",
"$03 Collision",
"$04 Collision",
"$05 Nothing (unused?)",
"$06 Nothing (unused?)",
"$07 Nothing (unused?)",
"$08 Deep water",
"$09 Shallow water",
"$0A Unknown? Possibly unused",
"$0B Collision (different in Overworld and unknown)",
"$0C Overlay mask",
"$0D Spike floor",
"$0E GT ice",
"$0F Ice palace ice",
"$10 Slope ◤",
"$11 Slope ◥",
"$12 Slope ◣",
"$13 Slope ◢",
"$14 Nothing (unused?)",
"$15 Nothing (unused?)",
"$16 Nothing (unused?)",
"$17 Nothing (unused?)",
"$18 Slope ◤",
"$19 Slope ◥",
"$1A Slope ◣",
"$1B Slope ◢",
"$1C Layer 2 overlay",
"$1D North single-layer auto stairs",
"$1E North layer-swap auto stairs",
"$1F North layer-swap auto stairs",
"$20 Pit",
"$21 Nothing (unused?)",
"$22 Manual stairs",
"$23 Pot switch",
"$24 Pressure switch",
"$25 Nothing (unused but referenced by somaria blocks)",
"$26 Collision (near stairs?)",
"$27 Brazier/Fence/Statue/Block/General hookable things",
"$28 North ledge",
"$29 South ledge",
"$2A East ledge",
"$2B West ledge",
"$2C ◤ ledge",
"$2D ◣ ledge",
"$2E ◥ ledge",
"$2F ◢ ledge",
"$30 Straight inter-room stairs south/up 0",
"$31 Straight inter-room stairs south/up 1",
"$32 Straight inter-room stairs south/up 2",
"$33 Straight inter-room stairs south/up 3",
"$34 Straight inter-room stairs north/down 0",
"$35 Straight inter-room stairs north/down 1",
"$36 Straight inter-room stairs north/down 2",
"$37 Straight inter-room stairs north/down 3",
"$38 Straight inter-room stairs north/down edge",
"$39 Straight inter-room stairs south/up edge",
"$3A Star tile (inactive on load)",
"$3B Star tile (active on load)",
"$3C Nothing (unused?)",
"$3D South single-layer auto stairs",
"$3E South layer-swap auto stairs",
"$3F South layer-swap auto stairs",
"$40 Thick grass",
"$41 Nothing (unused?)",
"$42 Gravestone / Tower of hera ledge shadows??",
"$43 Skull Woods entrance/Hera columns???",
"$44 Spike",
"$45 Nothing (unused?)",
"$46 Desert Tablet",
"$47 Nothing (unused?)",
"$48 Diggable ground",
"$49 Nothing (unused?)",
"$4A Diggable ground",
"$4B Warp tile",
"$4C Nothing (unused?) | Something unknown in overworld",
"$4D Nothing (unused?) | Something unknown in overworld",
"$4E Square corners in EP overworld",
"$4F Square corners in EP overworld",
"$50 Green bush",
"$51 Dark bush",
"$52 Gray rock",
"$53 Black rock",
"$54 Hint tile/Sign",
"$55 Big gray rock",
"$56 Big black rock",
"$57 Bonk rocks",
"$58 Chest 0",
"$59 Chest 1",
"$5A Chest 2",
"$5B Chest 3",
"$5C Chest 4",
"$5D Chest 5",
"$5E Spiral stairs",
"$5F Spiral stairs",
"$60 Rupee tile",
"$61 Nothing (unused?)",
"$62 Bombable floor",
"$63 Minigame chest",
"$64 Nothing (unused?)",
"$65 Nothing (unused?)",
"$66 Crystal peg down",
"$67 Crystal peg up",
"$68 Upwards conveyor",
"$69 Downwards conveyor",
"$6A Leftwards conveyor",
"$6B Rightwards conveyor",
"$6C North vines",
"$6D South vines",
"$6E West vines",
"$6F East vines",
"$70 Pot/Hammer peg/Push block 00",
"$71 Pot/Hammer peg/Push block 01",
"$72 Pot/Hammer peg/Push block 02",
"$73 Pot/Hammer peg/Push block 03",
"$74 Pot/Hammer peg/Push block 04",
"$75 Pot/Hammer peg/Push block 05",
"$76 Pot/Hammer peg/Push block 06",
"$77 Pot/Hammer peg/Push block 07",
"$78 Pot/Hammer peg/Push block 08",
"$79 Pot/Hammer peg/Push block 09",
"$7A Pot/Hammer peg/Push block 0A",
"$7B Pot/Hammer peg/Push block 0B",
"$7C Pot/Hammer peg/Push block 0C",
"$7D Pot/Hammer peg/Push block 0D",
"$7E Pot/Hammer peg/Push block 0E",
"$7F Pot/Hammer peg/Push block 0F",
"$80 North/South door",
"$81 East/West door",
"$82 North/South shutter door",
"$83 East/West shutter door",
"$84 North/South layer 2 door",
"$85 East/West layer 2 door",
"$86 North/South layer 2 shutter door",
"$87 East/West layer 2 shutter door",
"$88 Some type of door (?)",
"$89 East/West transport door",
"$8A Some type of door (?)",
"$8B Some type of door (?)",
"$8C Some type of door (?)",
"$8D Some type of door (?)",
"$8E Entrance door",
"$8F Entrance door",
"$90 Layer toggle shutter door (?)",
"$91 Layer toggle shutter door (?)",
"$92 Layer toggle shutter door (?)",
"$93 Layer toggle shutter door (?)",
"$94 Layer toggle shutter door (?)",
"$95 Layer toggle shutter door (?)",
"$96 Layer toggle shutter door (?)",
"$97 Layer toggle shutter door (?)",
"$98 Layer+Dungeon toggle shutter door (?)",
"$99 Layer+Dungeon toggle shutter door (?)",
"$9A Layer+Dungeon toggle shutter door (?)",
"$9B Layer+Dungeon toggle shutter door (?)",
"$9C Layer+Dungeon toggle shutter door (?)",
"$9D Layer+Dungeon toggle shutter door (?)",
"$9E Layer+Dungeon toggle shutter door (?)",
"$9F Layer+Dungeon toggle shutter door (?)",
"$A0 North/South Dungeon swap door",
"$A1 Dungeon toggle door (?)",
"$A2 Dungeon toggle door (?)",
"$A3 Dungeon toggle door (?)",
"$A4 Dungeon toggle door (?)",
"$A5 Dungeon toggle door (?)",
"$A6 Nothing (unused?)",
"$A7 Nothing (unused?)",
"$A8 Layer+Dungeon toggle shutter door (?)",
"$A9 Layer+Dungeon toggle shutter door (?)",
"$AA Layer+Dungeon toggle shutter door (?)",
"$AB Layer+Dungeon toggle shutter door (?)",
"$AC Layer+Dungeon toggle shutter door (?)",
"$AD Layer+Dungeon toggle shutter door (?)",
"$AE Layer+Dungeon toggle shutter door (?)",
"$AF Layer+Dungeon toggle shutter door (?)",
"$B0 Somaria ─",
"$B1 Somaria │",
"$B2 Somaria ┌",
"$B3 Somaria └",
"$B4 Somaria ┐",
"$B5 Somaria ┘",
"$B6 Somaria ⍰ 1 way",
"$B7 Somaria ┬",
"$B8 Somaria ┴",
"$B9 Somaria ├",
"$BA Somaria ┤",
"$BB Somaria ┼",
"$BC Somaria ⍰ 2 way",
"$BD Somaria ┼ crossover",
"$BE Pipe entrance",
"$BF Nothing (unused?)",
"$C0 Torch 00",
"$C1 Torch 01",
"$C2 Torch 02",
"$C3 Torch 03",
"$C4 Torch 04",
"$C5 Torch 05",
"$C6 Torch 06",
"$C7 Torch 07",
"$C8 Torch 08",
"$C9 Torch 09",
"$CA Torch 0A",
"$CB Torch 0B",
"$CC Torch 0C",
"$CD Torch 0D",
"$CE Torch 0E",
"$CF Torch 0F",
"$D0 Nothing (unused?)",
"$D1 Nothing (unused?)",
"$D2 Nothing (unused?)",
"$D3 Nothing (unused?)",
"$D4 Nothing (unused?)",
"$D5 Nothing (unused?)",
"$D6 Nothing (unused?)",
"$D7 Nothing (unused?)",
"$D8 Nothing (unused?)",
"$D9 Nothing (unused?)",
"$DA Nothing (unused?)",
"$DB Nothing (unused?)",
"$DC Nothing (unused?)",
"$DD Nothing (unused?)",
"$DE Nothing (unused?)",
"$DF Nothing (unused?)",
"$E0 Nothing (unused?)",
"$E1 Nothing (unused?)",
"$E2 Nothing (unused?)",
"$E3 Nothing (unused?)",
"$E4 Nothing (unused?)",
"$E5 Nothing (unused?)",
"$E6 Nothing (unused?)",
"$E7 Nothing (unused?)",
"$E8 Nothing (unused?)",
"$E9 Nothing (unused?)",
"$EA Nothing (unused?)",
"$EB Nothing (unused?)",
"$EC Nothing (unused?)",
"$ED Nothing (unused?)",
"$EE Nothing (unused?)",
"$EF Nothing (unused?)",
"$F0 Door 0 bottom",
"$F1 Door 1 bottom",
"$F2 Door 2 bottom",
"$F3 Door 3 bottom",
"$F4 Door X bottom? (unused?)",
"$F5 Door X bottom? (unused?)",
"$F6 Door X bottom? (unused?)",
"$F7 Door X bottom? (unused?)",
"$F8 Door 0 top",
"$F9 Door 1 top",
"$FA Door 2 top",
"$FB Door 3 top",
"$FC Door X top? (unused?)",
"$FD Door X top? (unused?)",
"$FE Door X top? (unused?)",
"$FF Door X top? (unused?)"};
static const std::string kSpriteDefaultNames[]{
"00 Raven",
"01 Vulture",
"02 Flying Stalfos Head",
"03 No Pointer (Empty",
"04 Pull Switch (good",
"05 Pull Switch (unused",
"06 Pull Switch (bad",
"07 Pull Switch (unused",
"08 Octorock (one way",
"09 Moldorm (Boss",
"0A Octorock (four way",
"0B Chicken",
"0C Octorock (?",
"0D Buzzblock",
"0E Snapdragon",
"0F Octoballoon",
"10 Octoballon Hatchlings",
"11 Hinox",
"12 Moblin",
"13 Mini Helmasaure",
"14 Gargoyle's Domain Gate",
"15 Antifairy",
"16 Sahasrahla / Aginah",
"17 Bush Hoarder",
"18 Mini Moldorm",
"19 Poe",
"1A Dwarves",
"1B Arrow in wall",
"1C Statue",
"1D Weathervane",
"1E Crystal Switch",
"1F Bug-Catching Kid",
"20 Sluggula",
"21 Push Switch",
"22 Ropa",
"23 Red Bari",
"24 Blue Bari",
"25 Talking Tree",
"26 Hardhat Beetle",
"27 Deadrock",
"28 Storytellers",
"29 Blind Hideout attendant",
"2A Sweeping Lady",
"2B Storytellers",
"2C Lumberjacks",
"2D Telepathic Stones",
"2E Multipurpose Sprite",
"2F Race Npc",
"30 Person?",
"31 Fortune Teller",
"32 Angry Brothers",
"33 Pull for items",
"34 Scared Girl",
"35 Innkeeper",
"36 Witch",
"37 Waterfall",
"38 Arrow Target",
"39 Average Middle",
"3A Half Magic Bat",
"3B Dash Item",
"3C Village Kid",
"3D Signs? Chicken lady also showed up / Scared ladies outside houses.",
"3E Rock Hoarder",
"3F Tutorial Soldier",
"40 Lightning Lock",
"41 Blue Sword Soldier / Used by guards to detect player",
"42 Green Sword Soldier",
"43 Red Spear Soldier",
"44 Assault Sword Soldier",
"45 Green Spear Soldier",
"46 Blue Archer",
"47 Green Archer",
"48 Red Javelin Soldier",
"49 Red Javelin Soldier 2",
"4A Red Bomb Soldiers",
"4B Green Soldier Recruits",
"4C Geldman",
"4D Rabbit",
"4E Popo",
"4F Popo 2",
"50 Cannon Balls",
"51 Armos",
"52 Giant Zora",
"53 Armos Knights (Boss",
"54 Lanmolas (Boss",
"55 Fireball Zora",
"56 Walking Zora",
"57 Desert Palace Barriers",
"58 Crab",
"59 Bird",
"5A Squirrel",
"5B Spark (Left to Right",
"5C Spark (Right to Left",
"5D Roller (vertical moving",
"5E Roller (vertical moving",
"5F Roller",
"60 Roller (horizontal moving",
"61 Beamos",
"62 Master Sword",
"63 Devalant (Non",
"64 Devalant (Shooter",
"65 Shooting Gallery Proprietor",
"66 Moving Cannon Ball Shooters (Right",
"67 Moving Cannon Ball Shooters (Left",
"68 Moving Cannon Ball Shooters (Down",
"69 Moving Cannon Ball Shooters (Up",
"6A Ball N' Chain Trooper",
"6B Cannon Soldier",
"6C Mirror Portal",
"6D Rat",
"6E Rope",
"6F Keese",
"70 Helmasaur King Fireball",
"71 Leever",
"72 Activator for the ponds (where you throw in items",
"73 Uncle / Priest",
"74 Running Man",
"75 Bottle Salesman",
"76 Princess Zelda",
"77 Antifairy (Alternate",
"78 Village Elder",
"79 Bee",
"7A Agahnim",
"7B Agahnim Energy Ball",
"7C Hyu",
"7D Big Spike Trap",
"7E Guruguru Bar (Clockwise",
"7F Guruguru Bar (Counter Clockwise",
"80 Winder",
"81 Water Tektite",
"82 Antifairy Circle",
"83 Green Eyegore",
"84 Red Eyegore",
"85 Yellow Stalfos",
"86 Kodongos",
"87 Flames",
"88 Mothula (Boss",
"89 Mothula's Beam",
"8A Spike Trap",
"8B Gibdo",
"8C Arrghus (Boss",
"8D Arrghus spawn",
"8E Terrorpin",
"8F Slime",
"90 Wallmaster",
"91 Stalfos Knight",
"92 Helmasaur King",
"93 Bumper",
"94 Swimmers",
"95 Eye Laser (Right",
"96 Eye Laser (Left",
"97 Eye Laser (Down",
"98 Eye Laser (Up",
"99 Pengator",
"9A Kyameron",
"9B Wizzrobe",
"9C Tadpoles",
"9D Tadpoles",
"9E Ostrich (Haunted Grove",
"9F Flute",
"A0 Birds (Haunted Grove",
"A1 Freezor",
"A2 Kholdstare (Boss",
"A3 Kholdstare's Shell",
"A4 Falling Ice",
"A5 Zazak Fireball",
"A6 Red Zazak",
"A7 Stalfos",
"A8 Bomber Flying Creatures from Darkworld",
"A9 Bomber Flying Creatures from Darkworld",
"AA Pikit",
"AB Maiden",
"AC Apple",
"AD Lost Old Man",
"AE Down Pipe",
"AF Up Pipe",
"B0 Right Pip",
"B1 Left Pipe",
"B2 Good bee again?",
"B3 Hylian Inscription",
"B4 Thief?s chest (not the one that follows you",
"B5 Bomb Salesman",
"B6 Kiki",
"B7 Maiden following you in Blind Dungeon",
"B8 Monologue Testing Sprite",
"B9 Feuding Friends on Death Mountain",
"BA Whirlpool",
"BB Salesman / chestgame guy / 300 rupee giver guy / Chest game thief",
"BC Drunk in the inn",
"BD Vitreous (Large Eyeball",
"BE Vitreous (Small Eyeball",
"BF Vitreous' Lightning",
"C0 Monster in Lake of Ill Omen / Quake Medallion",
"C1 Agahnim teleporting Zelda to dark world",
"C2 Boulders",
"C3 Gibo",
"C4 Thief",
"C5 Medusa",
"C6 Four Way Fireball Spitters (spit when you use your sword",
"C7 Hokku",
"C8 Big Fairy who heals you",
"C9 Tektite",
"CA Chain Chomp",
"CB Trinexx",
"CC Another part of trinexx",
"CD Yet another part of trinexx",
"CE Blind The Thief (Boss)",
"CF Swamola",
"D0 Lynel",
"D1 Bunny Beam",
"D2 Flopping fish",
"D3 Stal",
"D4 Landmine",
"D5 Digging Game Proprietor",
"D6 Ganon",
"D7 Copy of Ganon",
"D8 Heart",
"D9 Green Rupee",
"DA Blue Rupee",
"DB Red Rupee",
"DC Bomb Refill (1)",
"DD Bomb Refill (4)",
"DE Bomb Refill (8)",
"DF Small Magic Refill",
"E0 Full Magic Refill",
"E1 Arrow Refill (5)",
"E2 Arrow Refill (10)",
"E3 Fairy",
"E4 Key",
"E5 Big Key",
"E6 Shield",
"E7 Mushroom",
"E8 Fake Master Sword",
"E9 Magic Shop dude / His items",
"EA Heart Container",
"EB Heart Piece",
"EC Bushes",
"ED Cane Of Somaria Platform",
"EE Mantle",
"EF Cane of Somaria Platform (Unused)",
"F0 Cane of Somaria Platform (Unused)",
"F1 Cane of Somaria Platform (Unused)",
"F2 Medallion Tablet",
"F3",
"F4 Falling Rocks",
"F5",
"F6",
"F7",
"F8",
"F9",
"FA",
"FB",
"FC",
"FD",
"FE",
"FF",
};
static const std::string overlordnames[] = {
"Overlord_SpritePositionTarget",
"Overlord_AllDirectionMetalBallFactory",
"Overlord_CascadeMetalBallFactory",
"Overlord_StalfosFactory",
"Overlord_StalfosTrap",
"Overlord_SnakeTrap",
"Overlord_MovingFloor",
"Overlord_ZolFactory",
"Overlord_WallMasterFactory",
"Overlord_CrumbleTilePath 1",
"Overlord_CrumbleTilePath 2",
"Overlord_CrumbleTilePath 3",
"Overlord_CrumbleTilePath 4",
"Overlord_CrumbleTilePath 5",
"Overlord_CrumbleTilePath 6",
"Overlord_PirogusuFactory 1",
"Overlord_PirogusuFactory 2",
"Overlord_PirogusuFactory 3",
"Overlord_PirogusuFactory 4",
"Overlord_FlyingTileFactory",
"Overlord_WizzrobeFactory",
"Overlord_ZoroFactory",
"Overlord_StalfosTrapTriggerWindow",
"Overlord_RedStalfosTrap",
"Overlord_ArmosCoordinator",
"Overlord_BombTrap",
};
} // namespace core
} // namespace app
} // namespace yaze
#endif #endif

View File

@@ -2,264 +2,80 @@
#include <SDL.h> #include <SDL.h>
#include "imgui/backends/imgui_impl_sdl2.h" #include <filesystem>
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#if defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
#include "imgui/backends/imgui_impl_metal.h"
#elif TARGET_OS_IPHONE == 1
#include "imgui/backends/imgui_impl_metal.h"
#elif TARGET_OS_MAC == 1
#endif
#endif
#include <memory> #include <memory>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/core/platform/font_loader.h" #include "app/core/platform/font_loader.h"
#include "app/editor/master_editor.h" #include "app/editor/editor_manager.h"
#include "app/gui/icons.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "core/utils/file_util.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
namespace { absl::Status Controller::OnEntry(std::string filename) {
#if defined(__APPLE__) && defined(__MACH__)
constexpr ImGuiWindowFlags kMainEditorFlags = #if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | platform_ = Platform::kiOS;
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | #elif TARGET_OS_MAC == 1
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar; platform_ = Platform::kMacOS;
#endif
using ::ImVec2; #elif defined(_WIN32)
using ImGui::Begin; platform_ = Platform::kWindows;
using ImGui::End; #elif defined(__linux__)
using ImGui::GetIO; platform_ = Platform::kLinux;
using ImGui::NewFrame; #else
using ImGui::SetNextWindowPos; platform_ = Platform::kUnknown;
using ImGui::SetNextWindowSize; #endif
RETURN_IF_ERROR(CreateWindow())
void NewMasterFrame() { RETURN_IF_ERROR(CreateRenderer())
const ImGuiIO &io = GetIO(); RETURN_IF_ERROR(CreateGuiContext())
ImGui_ImplSDLRenderer2_NewFrame(); RETURN_IF_ERROR(LoadAudioDevice())
ImGui_ImplSDL2_NewFrame(); editor_manager_.Initialize(filename);
NewFrame(); active_ = true;
SetNextWindowPos(gui::kZeroPos); return absl::OkStatus();
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
SetNextWindowSize(dimensions, ImGuiCond_Always);
if (!Begin("##YazeMain", nullptr, kMainEditorFlags)) {
End();
return;
}
} }
void InitializeKeymap() { void Controller::OnInput() {
int wheel = 0;
SDL_Event event;
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.KeyMap[ImGuiKey_LeftSuper] = SDL_GetScancodeFromKey(SDLK_LGUI);
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_LeftArrow] = SDL_GetScancodeFromKey(SDLK_LEFT);
io.KeyMap[ImGuiKey_RightArrow] = SDL_GetScancodeFromKey(SDLK_RIGHT);
io.KeyMap[ImGuiKey_Delete] = SDL_GetScancodeFromKey(SDLK_DELETE);
io.KeyMap[ImGuiKey_Escape] = SDL_GetScancodeFromKey(SDLK_ESCAPE);
io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
io.KeyMap[ImGuiKey_PageUp] = SDL_GetScancodeFromKey(SDLK_PAGEUP);
io.KeyMap[ImGuiKey_PageDown] = SDL_GetScancodeFromKey(SDLK_PAGEDOWN);
io.KeyMap[ImGuiKey_Home] = SDL_GetScancodeFromKey(SDLK_HOME);
io.KeyMap[ImGuiKey_Space] = SDL_GetScancodeFromKey(SDLK_SPACE);
io.KeyMap[ImGuiKey_1] = SDL_GetScancodeFromKey(SDLK_1);
io.KeyMap[ImGuiKey_2] = SDL_GetScancodeFromKey(SDLK_2);
io.KeyMap[ImGuiKey_3] = SDL_GetScancodeFromKey(SDLK_3);
io.KeyMap[ImGuiKey_4] = SDL_GetScancodeFromKey(SDLK_4);
io.KeyMap[ImGuiKey_5] = SDL_GetScancodeFromKey(SDLK_5);
io.KeyMap[ImGuiKey_6] = SDL_GetScancodeFromKey(SDLK_6);
io.KeyMap[ImGuiKey_7] = SDL_GetScancodeFromKey(SDLK_7);
io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8);
io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9);
io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0);
io.KeyMap[ImGuiKey_A] = SDL_GetScancodeFromKey(SDLK_a);
io.KeyMap[ImGuiKey_B] = SDL_GetScancodeFromKey(SDLK_b);
io.KeyMap[ImGuiKey_C] = SDL_GetScancodeFromKey(SDLK_c);
io.KeyMap[ImGuiKey_D] = SDL_GetScancodeFromKey(SDLK_d);
io.KeyMap[ImGuiKey_E] = SDL_GetScancodeFromKey(SDLK_e);
io.KeyMap[ImGuiKey_F] = SDL_GetScancodeFromKey(SDLK_f);
io.KeyMap[ImGuiKey_G] = SDL_GetScancodeFromKey(SDLK_g);
io.KeyMap[ImGuiKey_H] = SDL_GetScancodeFromKey(SDLK_h);
io.KeyMap[ImGuiKey_I] = SDL_GetScancodeFromKey(SDLK_i);
io.KeyMap[ImGuiKey_J] = SDL_GetScancodeFromKey(SDLK_j);
io.KeyMap[ImGuiKey_K] = SDL_GetScancodeFromKey(SDLK_k);
io.KeyMap[ImGuiKey_L] = SDL_GetScancodeFromKey(SDLK_l);
io.KeyMap[ImGuiKey_M] = SDL_GetScancodeFromKey(SDLK_m);
io.KeyMap[ImGuiKey_N] = SDL_GetScancodeFromKey(SDLK_n);
io.KeyMap[ImGuiKey_O] = SDL_GetScancodeFromKey(SDLK_o);
io.KeyMap[ImGuiKey_P] = SDL_GetScancodeFromKey(SDLK_p);
io.KeyMap[ImGuiKey_Q] = SDL_GetScancodeFromKey(SDLK_q);
io.KeyMap[ImGuiKey_R] = SDL_GetScancodeFromKey(SDLK_r);
io.KeyMap[ImGuiKey_S] = SDL_GetScancodeFromKey(SDLK_s);
io.KeyMap[ImGuiKey_T] = SDL_GetScancodeFromKey(SDLK_t);
io.KeyMap[ImGuiKey_U] = SDL_GetScancodeFromKey(SDLK_u);
io.KeyMap[ImGuiKey_V] = SDL_GetScancodeFromKey(SDLK_v);
io.KeyMap[ImGuiKey_W] = SDL_GetScancodeFromKey(SDLK_w);
io.KeyMap[ImGuiKey_X] = SDL_GetScancodeFromKey(SDLK_x);
io.KeyMap[ImGuiKey_Y] = SDL_GetScancodeFromKey(SDLK_y);
io.KeyMap[ImGuiKey_Z] = SDL_GetScancodeFromKey(SDLK_z);
io.KeyMap[ImGuiKey_F1] = SDL_GetScancodeFromKey(SDLK_F1);
io.KeyMap[ImGuiKey_F2] = SDL_GetScancodeFromKey(SDLK_F2);
io.KeyMap[ImGuiKey_F3] = SDL_GetScancodeFromKey(SDLK_F3);
io.KeyMap[ImGuiKey_F4] = SDL_GetScancodeFromKey(SDLK_F4);
io.KeyMap[ImGuiKey_F5] = SDL_GetScancodeFromKey(SDLK_F5);
io.KeyMap[ImGuiKey_F6] = SDL_GetScancodeFromKey(SDLK_F6);
io.KeyMap[ImGuiKey_F7] = SDL_GetScancodeFromKey(SDLK_F7);
io.KeyMap[ImGuiKey_F8] = SDL_GetScancodeFromKey(SDLK_F8);
io.KeyMap[ImGuiKey_F9] = SDL_GetScancodeFromKey(SDLK_F9);
io.KeyMap[ImGuiKey_F10] = SDL_GetScancodeFromKey(SDLK_F10);
io.KeyMap[ImGuiKey_F11] = SDL_GetScancodeFromKey(SDLK_F11);
io.KeyMap[ImGuiKey_F12] = SDL_GetScancodeFromKey(SDLK_F12);
}
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) { while (SDL_PollEvent(&event)) {
SDL_SetClipboardText(text); ImGui_ImplSDL2_ProcessEvent(&event);
} switch (event.type) {
case SDL_KEYDOWN:
const char *ImGui_ImplSDL2_GetClipboardText(void *user_data) { case SDL_KEYUP: {
return SDL_GetClipboardText(); ImGuiIO &io = ImGui::GetIO();
} io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
void InitializeClipboard() { io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
ImGuiIO &io = ImGui::GetIO(); io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
io.ClipboardUserData = nullptr;
}
void HandleKeyDown(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO();
io.KeysDown[event.key.keysym.scancode] = (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);
switch (event.key.keysym.sym) {
case SDLK_BACKSPACE:
case SDLK_LSHIFT:
case SDLK_LCTRL:
case SDLK_TAB:
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
break; break;
case SDLK_z: }
editor.emulator().snes().SetButtonState(1, 0, true); case SDL_WINDOWEVENT:
break; switch (event.window.event) {
case SDLK_a: case SDL_WINDOWEVENT_CLOSE:
editor.emulator().snes().SetButtonState(1, 1, true); active_ = false;
break; break;
case SDLK_RSHIFT: case SDL_WINDOWEVENT_SIZE_CHANGED:
editor.emulator().snes().SetButtonState(1, 2, true); io.DisplaySize.x = static_cast<float>(event.window.data1);
break; io.DisplaySize.y = static_cast<float>(event.window.data2);
case SDLK_RETURN: break;
editor.emulator().snes().SetButtonState(1, 3, true); default:
break; break;
case SDLK_UP: }
editor.emulator().snes().SetButtonState(1, 4, true);
break;
case SDLK_DOWN:
editor.emulator().snes().SetButtonState(1, 5, true);
break;
case SDLK_LEFT:
editor.emulator().snes().SetButtonState(1, 6, true);
break;
case SDLK_RIGHT:
editor.emulator().snes().SetButtonState(1, 7, true);
break;
case SDLK_x:
editor.emulator().snes().SetButtonState(1, 8, true);
break;
case SDLK_s:
editor.emulator().snes().SetButtonState(1, 9, true);
break;
case SDLK_d:
editor.emulator().snes().SetButtonState(1, 10, true);
break;
case SDLK_c:
editor.emulator().snes().SetButtonState(1, 11, true);
break; break;
default: default:
break; break;
}
} }
}
void HandleKeyUp(SDL_Event &event, editor::MasterEditor &editor) {
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);
switch (event.key.keysym.sym) {
case SDLK_z:
editor.emulator().snes().SetButtonState(1, 0, false);
break;
case SDLK_a:
editor.emulator().snes().SetButtonState(1, 1, false);
break;
case SDLK_RSHIFT:
editor.emulator().snes().SetButtonState(1, 2, false);
break;
case SDLK_RETURN:
editor.emulator().snes().SetButtonState(1, 3, false);
break;
case SDLK_UP:
editor.emulator().snes().SetButtonState(1, 4, false);
break;
case SDLK_DOWN:
editor.emulator().snes().SetButtonState(1, 5, false);
break;
case SDLK_LEFT:
editor.emulator().snes().SetButtonState(1, 6, false);
break;
case SDLK_RIGHT:
editor.emulator().snes().SetButtonState(1, 7, false);
break;
case SDLK_x:
editor.emulator().snes().SetButtonState(1, 8, false);
break;
case SDLK_s:
editor.emulator().snes().SetButtonState(1, 9, false);
break;
case SDLK_d:
editor.emulator().snes().SetButtonState(1, 10, false);
break;
case SDLK_c:
editor.emulator().snes().SetButtonState(1, 11, false);
break;
default:
break;
}
}
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 mouseX;
int mouseY; int mouseY;
const int buttons = SDL_GetMouseState(&mouseX, &mouseY); const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
@@ -272,169 +88,88 @@ void HandleMouseMovement(int &wheel) {
io.MouseWheel = static_cast<float>(wheel); io.MouseWheel = static_cast<float>(wheel);
} }
} // namespace absl::Status Controller::OnLoad() {
if (editor_manager_.quit()) {
absl::Status Controller::OnEntry(std::string filename) { active_ = false;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_IPHONE_SIMULATOR == 1
/* iOS in Xcode simulator */
platform_ = Platform::kiOS;
#elif TARGET_OS_IPHONE == 1
/* iOS */
platform_ = Platform::kiOS;
#elif TARGET_OS_MAC == 1
/* macOS */
platform_ = Platform::kMacOS;
#endif
#elif defined(_WIN32)
platform_ = Platform::kWindows;
#elif defined(__linux__)
platform_ = Platform::kLinux;
#else
platform_ = Platform::kUnknown;
#endif
RETURN_IF_ERROR(CreateSDL_Window())
RETURN_IF_ERROR(CreateRenderer())
RETURN_IF_ERROR(CreateGuiContext())
if (flags()->kLoadAudioDevice) {
RETURN_IF_ERROR(LoadAudioDevice())
master_editor_.emulator().set_audio_buffer(audio_buffer_);
master_editor_.emulator().set_audio_device_id(audio_device_);
} }
InitializeKeymap(); #if TARGET_OS_IPHONE != 1
master_editor_.SetupScreen(renderer_, filename); constexpr ImGuiWindowFlags kMainEditorFlags =
active_ = true; ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
const ImGuiIO &io = ImGui::GetIO();
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
ImGui::SetNextWindowPos(gui::kZeroPos);
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
ImGui::End();
}
#endif
RETURN_IF_ERROR(editor_manager_.Update());
#if TARGET_OS_IPHONE != 1
ImGui::End();
#endif
return absl::OkStatus(); return absl::OkStatus();
} }
void Controller::OnInput() { absl::Status Controller::OnTestLoad() {
int wheel = 0; RETURN_IF_ERROR(test_editor_->Update());
SDL_Event event;
ImGuiIO &io = ImGui::GetIO();
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
HandleKeyDown(event, master_editor_);
break;
case SDL_KEYUP:
HandleKeyUp(event, master_editor_);
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);
}
absl::Status Controller::OnLoad() {
if (master_editor_.quit()) {
active_ = false;
}
NewMasterFrame();
RETURN_IF_ERROR(master_editor_.Update());
return absl::OkStatus(); return absl::OkStatus();
} }
void Controller::DoRender() const { void Controller::DoRender() const {
ImGui::Render(); ImGui::Render();
SDL_RenderClear(renderer_.get()); SDL_RenderClear(Renderer::GetInstance().renderer());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get()); ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(),
SDL_RenderPresent(renderer_.get()); Renderer::GetInstance().renderer());
SDL_RenderPresent(Renderer::GetInstance().renderer());
} }
void Controller::OnExit() { void Controller::OnExit() {
ImGui::DestroyContext(); SDL_PauseAudioDevice(audio_device_, 1);
if (flags()->kLoadAudioDevice) { SDL_CloseAudioDevice(audio_device_);
SDL_PauseAudioDevice(audio_device_, 1); ImGui_ImplSDLRenderer2_Shutdown();
SDL_CloseAudioDevice(audio_device_); ImGui_ImplSDL2_Shutdown();
delete audio_buffer_;
}
switch (platform_) {
case Platform::kMacOS:
case Platform::kWindows:
case Platform::kLinux:
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
break;
case Platform::kiOS:
// Deferred
break;
default:
break;
}
ImGui::DestroyContext(); ImGui::DestroyContext();
SDL_Quit(); SDL_Quit();
} }
absl::Status Controller::CreateSDL_Window() { absl::Status Controller::CreateWindow() {
auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_TIMER; auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
if (flags()->kUseNewImGuiInput) {
sdl_flags |= SDL_INIT_GAMECONTROLLER;
}
if (flags()->kLoadAudioDevice) {
sdl_flags |= SDL_INIT_AUDIO;
}
if (SDL_Init(sdl_flags) != 0) { if (SDL_Init(sdl_flags) != 0) {
return absl::InternalError( return absl::InternalError(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError())); absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
} else {
SDL_DisplayMode displayMode;
SDL_GetCurrentDisplayMode(0, &displayMode);
int screenWidth = displayMode.w * 0.8;
int screenHeight = displayMode.h * 0.8;
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
screenWidth, // width, in pixels
screenHeight, // height, in pixels
SDL_WINDOW_RESIZABLE),
sdl_deleter());
if (window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
} }
SDL_DisplayMode display_mode;
SDL_GetCurrentDisplayMode(0, &display_mode);
int screen_width = display_mode.w * 0.8;
int screen_height = display_mode.h * 0.8;
window_ = std::unique_ptr<SDL_Window, core::SDL_Deleter>(
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
screen_width, // width, in pixels
screen_height, // height, in pixels
SDL_WINDOW_RESIZABLE),
core::SDL_Deleter());
if (window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Controller::CreateRenderer() { absl::Status Controller::CreateRenderer() {
renderer_ = std::unique_ptr<SDL_Renderer, sdl_deleter>( return Renderer::GetInstance().CreateRenderer(window_.get());
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() { absl::Status Controller::CreateGuiContext() {
@@ -444,19 +179,12 @@ absl::Status Controller::CreateGuiContext() {
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
if (flags()->kUseNewImGuiInput) { // Initialize ImGui based on the backend
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; ImGui_ImplSDL2_InitForSDLRenderer(window_.get(),
} Renderer::GetInstance().renderer());
ImGui_ImplSDLRenderer2_Init(Renderer::GetInstance().renderer());
// Initialize ImGui for SDL RETURN_IF_ERROR(LoadFontFamilies());
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
ImGui_ImplSDLRenderer2_Init(renderer_.get());
if (flags()->kLoadSystemFonts) {
LoadSystemFonts();
} else {
RETURN_IF_ERROR(LoadFontFamilies());
}
// Set the default style // Set the default style
gui::ColorsYaze(); gui::ColorsYaze();
@@ -469,94 +197,8 @@ absl::Status Controller::CreateGuiContext() {
} }
absl::Status Controller::LoadFontFamilies() const { absl::Status Controller::LoadFontFamilies() const {
ImGuiIO &io = ImGui::GetIO(); // LoadSystemFonts();
return LoadPackageFonts();
const char *font_path = "assets/font/";
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 14.0f;
static const float FONT_SIZE_DROID_SANS = 16.0f;
static const float ICON_FONT_SIZE = 18.0f;
// Icon configuration
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;
// Japanese font configuration
ImFontConfig japanese_font_config;
japanese_font_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
// List of fonts to be loaded
std::vector<const char *> font_paths = {KARLA_REGULAR, ROBOTO_MEDIUM,
COUSINE_REGULAR, IBM_PLEX_JP};
// Load fonts with associated icon and Japanese merges
for (const auto &font_path : font_paths) {
float font_size =
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
std::string actual_font_path;
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
actual_font_path = kBundlePath + font_path;
#else
actual_font_path = std::filesystem::absolute(font_path).string();
#endif
#else
actual_font_path = font_path;
#endif
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
// Merge icon set
std::string actual_icon_font_path = "";
const char *icon_font_path = FONT_ICON_FILE_NAME_MD;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kIconBundlePath = GetBundleResourcePath();
actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf";
#else
actual_icon_font_path = std::filesystem::absolute(icon_font_path).string();
#endif
#else
#endif
io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(), ICON_FONT_SIZE,
&icons_config, icons_ranges);
// Merge Japanese font
std::string actual_japanese_font_path = "";
const char *japanese_font_path = NOTO_SANS_JP;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kJapaneseBundlePath = GetBundleResourcePath();
actual_japanese_font_path = kJapaneseBundlePath + japanese_font_path;
#else
actual_japanese_font_path =
std::filesystem::absolute(japanese_font_path).string();
#endif
#else
#endif
io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f,
&japanese_font_config,
io.Fonts->GetGlyphRangesJapanese());
}
return absl::OkStatus();
} }
absl::Status Controller::LoadAudioDevice() { absl::Status Controller::LoadAudioDevice() {
@@ -566,18 +208,47 @@ absl::Status Controller::LoadAudioDevice() {
want.format = AUDIO_S16; want.format = AUDIO_S16;
want.channels = 2; want.channels = 2;
want.samples = 2048; want.samples = 2048;
want.callback = NULL; // Uses the queue want.callback = NULL; // Uses the queue
audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (audio_device_ == 0) { if (audio_device_ == 0) {
return absl::InternalError( return absl::InternalError(
absl::StrFormat("Failed to open audio: %s\n", SDL_GetError())); absl::StrFormat("Failed to open audio: %s\n", SDL_GetError()));
} }
audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4]; // audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
master_editor_.emulator().set_audio_buffer(audio_buffer_); audio_buffer_ = std::make_shared<int16_t>(audio_frequency_ / 50 * 4);
SDL_PauseAudioDevice(audio_device_, 0); SDL_PauseAudioDevice(audio_device_, 0);
editor_manager_.emulator().set_audio_buffer(audio_buffer_.get());
editor_manager_.emulator().set_audio_device_id(audio_device_);
return absl::OkStatus(); return absl::OkStatus();
} }
} // namespace core absl::Status Controller::LoadConfigFiles() {
} // namespace app // Create and load a dotfile for the application
} // namespace yaze // This will store the user's preferences and settings
std::string config_directory = GetConfigDirectory(platform_);
// Create the directory if it doesn't exist
if (!std::filesystem::exists(config_directory)) {
if (!std::filesystem::create_directory(config_directory)) {
return absl::InternalError(absl::StrFormat(
"Failed to create config directory %s", config_directory));
}
}
// Check if the config file exists
std::string config_file = config_directory + "yaze.cfg";
if (!std::filesystem::exists(config_file)) {
// Create the file if it doesn't exist
std::ofstream file(config_file);
if (!file.is_open()) {
return absl::InternalError(
absl::StrFormat("Failed to create config file %s", config_file));
}
file.close();
}
return absl::OkStatus();
}
} // namespace core
} // namespace yaze

View File

@@ -2,29 +2,24 @@
#define YAZE_APP_CORE_CONTROLLER_H #define YAZE_APP_CORE_CONTROLLER_H
#include <SDL.h> #include <SDL.h>
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imconfig.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include <memory> #include <memory>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/common.h" #include "app/core/platform/renderer.h"
#include "app/editor/master_editor.h" #include "app/core/utils/file_util.h"
#include "app/editor/utils/editor.h" #include "app/editor/editor.h"
#include "app/gui/icons.h" #include "app/editor/editor_manager.h"
#include "app/gui/style.h" #include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imconfig.h"
#include "imgui/imgui.h"
int main(int argc, char **argv); int main(int argc, char **argv);
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
/** /**
* @brief Main controller for the application. * @brief Main controller for the application.
* *
@@ -37,49 +32,44 @@ class Controller : public ExperimentFlags {
absl::Status OnEntry(std::string filename = ""); absl::Status OnEntry(std::string filename = "");
void OnInput(); void OnInput();
absl::Status OnLoad(); absl::Status OnLoad();
absl::Status OnTestLoad();
void DoRender() const; void DoRender() const;
void OnExit(); void OnExit();
absl::Status CreateSDL_Window(); absl::Status CreateWindow();
absl::Status CreateRenderer(); absl::Status CreateRenderer();
absl::Status CreateGuiContext(); absl::Status CreateGuiContext();
absl::Status LoadFontFamilies() const; absl::Status LoadFontFamilies() const;
absl::Status LoadAudioDevice(); absl::Status LoadAudioDevice();
absl::Status LoadConfigFiles();
auto master_editor() -> editor::MasterEditor & { return master_editor_; } void SetupScreen(std::string filename = "") {
editor_manager_.Initialize(filename);
}
auto editor_manager() -> editor::EditorManager & { return editor_manager_; }
auto renderer() -> SDL_Renderer * {
return Renderer::GetInstance().renderer();
}
auto window() -> SDL_Window * { return window_.get(); }
void init_test_editor(editor::Editor *editor) { test_editor_ = editor; }
void set_active(bool active) { active_ = active; }
auto active() { return active_; }
private: private:
struct sdl_deleter {
void operator()(SDL_Window *p) const {
if (p) {
SDL_DestroyWindow(p);
}
}
void operator()(SDL_Renderer *p) const {
if (p) {
SDL_DestroyRenderer(p);
}
}
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
};
void CloseWindow() { active_ = false; }
friend int ::main(int argc, char **argv); friend int ::main(int argc, char **argv);
bool active_; bool active_ = false;
Platform platform_; Platform platform_;
editor::MasterEditor master_editor_; editor::Editor *test_editor_ = nullptr;
editor::EditorManager editor_manager_;
int audio_frequency_ = 48000; int audio_frequency_ = 48000;
int16_t *audio_buffer_;
SDL_AudioDeviceID audio_device_; SDL_AudioDeviceID audio_device_;
std::shared_ptr<int16_t> audio_buffer_;
std::shared_ptr<SDL_Window> window_; std::shared_ptr<SDL_Window> window_;
std::shared_ptr<SDL_Renderer> renderer_;
}; };
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_CONTROLLER_H #endif // YAZE_APP_CORE_CONTROLLER_H

33
src/app/core/core.cmake Normal file
View File

@@ -0,0 +1,33 @@
set(
YAZE_APP_CORE_SRC
app/core/common.cc
app/core/controller.cc
app/emu/emulator.cc
app/core/project.cc
app/core/utils/file_util.cc
)
if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/font_loader.cc
app/core/platform/clipboard.cc
app/core/platform/file_dialog.cc
)
endif()
if(APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/file_dialog.mm
app/core/platform/app_delegate.mm
app/core/platform/font_loader.cc
app/core/platform/font_loader.mm
app/core/platform/clipboard.mm
app/core/platform/file_path.mm
)
find_library(COCOA_LIBRARY Cocoa)
if(NOT COCOA_LIBRARY)
message(FATAL_ERROR "Cocoa not found")
endif()
set(CMAKE_EXE_LINKER_FLAGS "-framework ServiceManagement -framework Foundation -framework Cocoa")
endif()

View File

@@ -1,53 +0,0 @@
#ifndef YAZE_APP_CORE_LABELING_H_
#define YAZE_APP_CORE_LABELING_H_
#include <cstdint>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/common.h"
#include "app/core/constants.h"
namespace yaze {
namespace app {
namespace core {
// Default types
static constexpr absl::string_view kDefaultTypes[] = {
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
struct ResourceLabelManager {
bool LoadLabels(const std::string& filename);
bool SaveLabels();
void DisplayLabels(bool* p_open);
void EditLabel(const std::string& type, const std::string& key,
const std::string& newValue);
void SelectableLabelWithNameEdit(bool selected, const std::string& type,
const std::string& key,
const std::string& defaultValue);
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
const std::string& defaultValue);
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
const absl::string_view& defaultValue);
bool labels_loaded_ = false;
std::string filename_;
struct ResourceType {
std::string key_name;
std::string display_description;
};
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
labels_;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_LABELING_H_

View File

@@ -1,13 +1,57 @@
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H #ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H #define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#ifdef TARGET_OS_MAC #if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#import <CoreText/CoreText.h>
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */
#import <PencilKit/PencilKit.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate,
UIDocumentPickerDelegate,
UITabBarControllerDelegate,
PKCanvasViewDelegate>
@property(strong, nonatomic) UIWindow *window;
@property UIDocumentPickerViewController *documentPicker;
@property(nonatomic, copy) void (^completionHandler)(NSString *selectedFile);
- (void)PresentDocumentPickerWithCompletionHandler:
(void (^)(NSString *selectedFile))completionHandler;
// TODO: Setup a tab bar controller for multiple yaze instances
@property(nonatomic) UITabBarController *tabBarController;
// TODO: Setup a font picker for the text editor and display settings
@property(nonatomic) UIFontPickerViewController *fontPicker;
// TODO: Setup the pencil kit for drawing
@property PKToolPicker *toolPicker;
@property PKCanvasView *canvasView;
// TODO: Setup the file manager for file operations
@property NSFileManager *fileManager;
@end
#elif TARGET_OS_MAC == 1
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void InitializeCocoa(); /**
* @brief Initialize the Cocoa application.
*/
void yaze_initialize_cocoa();
/**
* @brief Run the Cocoa application delegate.
*/
void yaze_run_cocoa_app_delegate(const char *filename);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
@@ -15,4 +59,6 @@ void InitializeCocoa();
#endif // TARGET_OS_MAC #endif // TARGET_OS_MAC
#endif // defined(__APPLE__) && defined(__MACH__)
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H #endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H

View File

@@ -2,7 +2,7 @@
#import "app/core/platform/app_delegate.h" #import "app/core/platform/app_delegate.h"
#import "app/core/controller.h" #import "app/core/controller.h"
#import "app/core/platform/file_dialog.h" #import "app/core/platform/file_dialog.h"
#import "app/editor/utils/editor.h" #import "app/editor/editor.h"
#import "app/rom.h" #import "app/rom.h"
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
@@ -11,12 +11,9 @@
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1 #if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */ /* iOS in Xcode simulator */
#elif TARGET_OS_IPHONE == 1
/* iOS */
#elif TARGET_OS_MAC == 1 #elif TARGET_OS_MAC == 1
/* macOS */ /* macOS */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@@ -209,7 +206,8 @@
} }
- (void)openFileAction:(id)sender { - (void)openFileAction:(id)sender {
if (!yaze::app::SharedRom::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog()) if (!yaze::SharedRom::shared_rom_
->LoadFromFile(yaze::core::FileDialogWrapper::ShowOpenFileDialog())
.ok()) { .ok()) {
NSAlert *alert = [[NSAlert alloc] init]; NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Error"]; [alert setMessageText:@"Error"];
@@ -227,7 +225,9 @@
NSLog(@"Open Recent File action triggered"); NSLog(@"Open Recent File action triggered");
} }
extern "C" void InitializeCocoa() { @end
extern "C" void yaze_initialize_cococa() {
@autoreleasepool { @autoreleasepool {
AppDelegate *delegate = [[AppDelegate alloc] init]; AppDelegate *delegate = [[AppDelegate alloc] init];
[NSApplication sharedApplication]; [NSApplication sharedApplication];
@@ -236,7 +236,21 @@ extern "C" void InitializeCocoa() {
} }
} }
@end extern "C" void yaze_run_cocoa_app_delegate(const char *filename) {
yaze_initialize_cococa();
yaze::core::Controller controller;
RETURN_VOID_IF_ERROR(controller.OnEntry(filename));
while (controller.IsActive()) {
@autoreleasepool {
controller.OnInput();
if (auto status = controller.OnLoad(); !status.ok()) {
break;
}
controller.DoRender();
}
}
controller.OnExit();
}
#endif #endif

View File

@@ -3,6 +3,12 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
namespace yaze {
namespace core {
void CopyImageToClipboard(const std::vector<uint8_t>& data) {} void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
int& height) {} int& height) {}
} // namespace core
} // namespace yaze

View File

@@ -1,27 +1,16 @@
#ifndef YAZE_APP_CORE_PLATFORM_CLIPBOARD_H #ifndef YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
#define YAZE_APP_CORE_PLATFORM_CLIPBOARD_H #define YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
#ifdef _WIN32
void CopyImageToClipboard(const std::vector<uint8_t>& data);
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#elif defined(__APPLE__)
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
void CopyImageToClipboard(const std::vector<uint8_t>& data); namespace yaze {
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height); namespace core {
#elif defined(__linux__) void CopyImageToClipboard(const std::vector<uint8_t> &data);
void GetImageFromClipboard(std::vector<uint8_t> &data, int &width, int &height);
#include <cstdint> } // namespace core
#include <vector> } // namespace yaze
void CopyImageToClipboard(const std::vector<uint8_t>& data);
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#endif
#endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H #endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H

View File

@@ -6,7 +6,7 @@
#ifdef TARGET_OS_MAC #ifdef TARGET_OS_MAC
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) { void yaze::core::CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()]; NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()];
NSImage* image = [[NSImage alloc] initWithData:data]; NSImage* image = [[NSImage alloc] initWithData:data];
@@ -15,7 +15,7 @@ void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
[pasteboard writeObjects:@[ image ]]; [pasteboard writeObjects:@[ image ]];
} }
void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) { void yaze::core::GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSArray* classArray = [NSArray arrayWithObject:[NSImage class]]; NSArray* classArray = [NSArray arrayWithObject:[NSImage class]];
NSDictionary* options = [NSDictionary dictionary]; NSDictionary* options = [NSDictionary dictionary];

View File

@@ -0,0 +1,143 @@
#include "file_dialog.h"
#ifdef _WIN32
// Include Windows-specific headers
#include <shobjidl.h>
#include <windows.h>
#endif // _WIN32
namespace yaze {
namespace core {
#ifdef _WIN32
std::string FileDialogWrapper::ShowOpenFileDialog() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
reinterpret_cast<void **>(&pfd));
std::string file_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the file path
PWSTR pszFilePath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
char str[128];
wcstombs(str, pszFilePath, 128);
file_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFilePath);
}
}
pfd->Release();
}
CoUninitialize();
return file_path_windows;
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
reinterpret_cast<void **>(&pfd));
std::string folder_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
DWORD dwOptions;
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr)) {
hr = pfd->SetOptions(dwOptions | FOS_PICKFOLDERS);
if (SUCCEEDED(hr)) {
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the folder path
PWSTR pszFolderPath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFolderPath);
char str[128];
wcstombs(str, pszFolderPath, 128);
folder_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFolderPath);
}
}
}
}
pfd->Release();
}
CoUninitialize();
return folder_path_windows;
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string &folder_path) {
std::vector<std::string> subdirectories;
WIN32_FIND_DATA findFileData;
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (strcmp(findFileData.cFileName, ".") != 0 &&
strcmp(findFileData.cFileName, "..") != 0) {
subdirectories.push_back(findFileData.cFileName);
}
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
return subdirectories;
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string &folder_path) {
std::vector<std::string> files;
WIN32_FIND_DATA findFileData;
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
files.push_back(findFileData.cFileName);
}
} while (FindNextFile(hFind, &findFileData) != 0);
FindClose(hFind);
}
return files;
}
#elif defined(__linux__)
std::string FileDialogWrapper::ShowOpenFileDialog() {
return "Linux: Open file dialog";
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
return "Linux: Open folder dialog";
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder_path) {
return {"Linux: Subdirectories in folder"};
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string& folder_path) {
return {"Linux: Files in folder"};
}
#endif
} // namespace core
} // namespace yaze

View File

@@ -1,61 +1,24 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H #ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H #define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#include <string>
#ifdef _WIN32
// Include Windows-specific headers
#include <shobjidl.h>
#include <windows.h>
class FileDialogWrapper {
public:
static std::string ShowOpenFileDialog() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileDialog, reinterpret_cast<void **>(&pfd));
std::string file_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the file path
PWSTR pszFilePath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
char str[128];
wcstombs(str, pszFilePath, 128);
file_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFilePath);
}
}
pfd->Release();
}
CoUninitialize();
return file_path_windows;
}
};
#elif defined(__APPLE__)
#include "TargetConditionals.h"
#include <string> #include <string>
#include <vector> #include <vector>
#ifdef TARGET_OS_MAC namespace yaze {
// Other kinds of Mac OS namespace core {
class FileDialogWrapper { class FileDialogWrapper {
public: public:
/**
* @brief ShowOpenFileDialog opens a file dialog and returns the selected
* filepath.
*/
static std::string ShowOpenFileDialog(); static std::string ShowOpenFileDialog();
/**
* @brief ShowOpenFolderDialog opens a file dialog and returns the selected
* folder path.
*/
static std::string ShowOpenFolderDialog(); static std::string ShowOpenFolderDialog();
static std::vector<std::string> GetSubdirectoriesInFolder( static std::vector<std::string> GetSubdirectoriesInFolder(
const std::string& folder_path); const std::string& folder_path);
@@ -63,34 +26,7 @@ class FileDialogWrapper {
const std::string& folder_path); const std::string& folder_path);
}; };
#elif TARGET_OS_IPHONE } // namespace core
} // namespace yaze
// iOS
class FileDialogWrapper {
public:
static std::string ShowOpenFileDialog();
static std::string ShowOpenFolderDialog();
static std::vector<std::string> GetSubdirectoriesInFolder(
const std::string& folder_path);
static std::vector<std::string> GetFilesInFolder(
const std::string& folder_path);
};
#endif
#elif defined(__linux__)
class FileDialogWrapper {
public:
static std::string ShowOpenFileDialog() {
// Linux-specific file dialog implementation using GTK
// ...
return "file_path_linux";
}
};
#else
#error "Unsupported platform."
#endif
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H #endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H

View File

@@ -1,8 +1,8 @@
#include "app/core/platform/file_dialog.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include "imgui/imgui.h"
#include "app/core/platform/file_dialog.h"
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */ /* Apple OSX and iOS (Darwin). */
@@ -10,32 +10,46 @@
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1 #if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */ /* iOS in Xcode simulator */
std::string FileDialogWrapper::ShowOpenFileDialog() { return ""; } #import <UIKit/UIKit.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
std::string FileDialogWrapper::ShowOpenFolderDialog() { return ""; } #include "app/core/platform/app_delegate.h"
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) { namespace {
static std::string selectedFile;
void ShowOpenFileDialogImpl(void (^completionHandler)(std::string)) {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate PresentDocumentPickerWithCompletionHandler:^(NSString *filePath) {
selectedFile = std::string([filePath UTF8String]);
completionHandler(selectedFile);
}];
}
std::string ShowOpenFileDialogSync() {
__block std::string result;
ShowOpenFileDialogImpl(^(std::string filePath) {
result = filePath;
});
return result;
}
} // namespace
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() { return ShowOpenFileDialogSync(); }
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
std::vector<std::string> yaze::core::FileDialogWrapper::GetFilesInFolder(
const std::string &folder) {
return {}; return {};
} }
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) { std::vector<std::string> yaze::core::FileDialogWrapper::GetSubdirectoriesInFolder(
return {}; const std::string &folder) {
}
#elif TARGET_OS_IPHONE == 1
/* iOS */
std::string FileDialogWrapper::ShowOpenFileDialog() { return ""; }
std::string FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) {
return {};
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) {
return {}; return {};
} }
@@ -44,7 +58,7 @@ std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std:
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
std::string FileDialogWrapper::ShowOpenFileDialog() { std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel]; NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:YES]; [openPanel setCanChooseFiles:YES];
[openPanel setCanChooseDirectories:NO]; [openPanel setCanChooseDirectories:NO];
@@ -59,7 +73,7 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
return ""; return "";
} }
std::string FileDialogWrapper::ShowOpenFolderDialog() { std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialog() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel]; NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO]; [openPanel setCanChooseFiles:NO];
[openPanel setCanChooseDirectories:YES]; [openPanel setCanChooseDirectories:YES];
@@ -74,7 +88,8 @@ std::string FileDialogWrapper::ShowOpenFolderDialog() {
return ""; return "";
} }
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) { std::vector<std::string> yaze::core::FileDialogWrapper::GetFilesInFolder(
const std::string& folder) {
std::vector<std::string> filenames; std::vector<std::string> filenames;
NSFileManager* fileManager = [NSFileManager defaultManager]; NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator = NSDirectoryEnumerator* enumerator =
@@ -89,7 +104,8 @@ std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string&
return filenames; return filenames;
} }
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) { std::vector<std::string> yaze::core::FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder) {
std::vector<std::string> subdirectories; std::vector<std::string> subdirectories;
NSFileManager* fileManager = [NSFileManager defaultManager]; NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator = NSDirectoryEnumerator* enumerator =
@@ -113,4 +129,4 @@ std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std:
// Unsupported platform // Unsupported platform
#endif // TARGET_OS_MAC #endif // TARGET_OS_MAC
#endif // __APPLE__ && __MACH__ #endif // __APPLE__ && __MACH__

View File

@@ -1,6 +1,18 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H #ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H
#define YAZE_APP_CORE_PLATFORM_FILE_PATH_H #define YAZE_APP_CORE_PLATFORM_FILE_PATH_H
#include <string>
namespace yaze {
namespace core {
/**
* @brief GetBundleResourcePath returns the path to the bundle resource
* directory. Specific to MacOS.
*/
std::string GetBundleResourcePath(); std::string GetBundleResourcePath();
} // namespace core
} // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_FILE_PATH_H #endif // YAZE_APP_CORE_PLATFORM_FILE_PATH_H

View File

@@ -1,3 +1,5 @@
#include "file_path.h"
#include <iostream> #include <iostream>
#include <string> #include <string>
@@ -5,17 +7,15 @@
#include <Foundation/Foundation.h> #include <Foundation/Foundation.h>
#include <TargetConditionals.h> #include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1 #if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
std::string GetBundleResourcePath() {} std::string yaze::core::GetBundleResourcePath() {
#elif TARGET_OS_IPHONE == 1
std::string GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle]; NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath]; NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"]; NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String]; return [path UTF8String];
} }
#elif TARGET_OS_MAC == 1 #elif TARGET_OS_MAC == 1
std::string GetBundleResourcePath() { std::string yaze::core::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle]; NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath]; NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"]; NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];

View File

@@ -1,17 +1,128 @@
#include "app/core/platform/font_loader.h" #include "app/core/platform/font_loader.h"
#include <filesystem>
#include <string>
#include <unordered_set>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "app/core/platform/file_path.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include <string> namespace yaze {
#include <vector> namespace core {
absl::Status LoadPackageFonts() {
ImGuiIO &io = ImGui::GetIO();
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 16.0f;
static const float FONT_SIZE_DROID_SANS = 18.0f;
static const float ICON_FONT_SIZE = 18.0f;
// Icon configuration
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;
// Japanese font configuration
ImFontConfig japanese_font_config;
japanese_font_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
// List of fonts to be loaded
std::vector<const char *> font_paths = {
KARLA_REGULAR, ROBOTO_MEDIUM, COUSINE_REGULAR, IBM_PLEX_JP, DROID_SANS};
// Load fonts with associated icon and Japanese merges
for (const auto &font_path : font_paths) {
float font_size =
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
std::string actual_font_path;
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
actual_font_path = kBundlePath + font_path;
#else
actual_font_path = absl::StrCat(GetBundleResourcePath(),
"Contents/Resources/font/", font_path);
#endif
#else
actual_font_path = absl::StrCat("assets/font/", font_path);
actual_font_path = std::filesystem::absolute(actual_font_path).string();
#endif
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
// Merge icon set
std::string actual_icon_font_path = "";
const char *icon_font_path = FONT_ICON_FILE_NAME_MD;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kIconBundlePath = GetBundleResourcePath();
actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf";
#else
actual_icon_font_path =
absl::StrCat(GetBundleResourcePath(),
"Contents/Resources/font/MaterialIcons-Regular.ttf");
#endif
#else
actual_icon_font_path = std::filesystem::absolute(icon_font_path).string();
#endif
if (!io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(),
ICON_FONT_SIZE, &icons_config,
icons_ranges)) {
return absl::InternalError("Failed to load icon fonts");
}
// Merge Japanese font
std::string actual_japanese_font_path = "";
const char *japanese_font_path = NOTO_SANS_JP;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kJapaneseBundlePath = GetBundleResourcePath();
actual_japanese_font_path = kJapaneseBundlePath + japanese_font_path;
#else
actual_japanese_font_path =
absl::StrCat(GetBundleResourcePath(), "Contents/Resources/font/",
japanese_font_path);
#endif
#else
actual_japanese_font_path = absl::StrCat("assets/font/", japanese_font_path);
actual_japanese_font_path =
std::filesystem::absolute(actual_japanese_font_path).string();
#endif
io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f,
&japanese_font_config,
io.Fonts->GetGlyphRangesJapanese());
}
return absl::OkStatus();
}
#ifdef _WIN32 #ifdef _WIN32
#include <Windows.h> #include <Windows.h>
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme, int CALLBACK EnumFontFamExProc(const LOGFONT *lpelfe, const TEXTMETRIC *lpntme,
DWORD FontType, LPARAM lParam) { DWORD FontType, LPARAM lParam) {
// Step 3: Load the font into ImGui // Step 3: Load the font into ImGui
ImGuiIO& io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f); io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
return 1; return 1;
@@ -34,8 +145,8 @@ void LoadSystemFonts() {
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount, RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
&maxValueNameSize, &maxValueDataSize, NULL, NULL); &maxValueNameSize, &maxValueDataSize, NULL, NULL);
char* valueName = new char[maxValueNameSize + 1]; // +1 for null terminator char *valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
BYTE* valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator BYTE *valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
// Enumerate all font entries // Enumerate all font entries
for (DWORD i = 0; i < valueCount; i++) { for (DWORD i = 0; i < valueCount; i++) {
@@ -52,7 +163,9 @@ void LoadSystemFonts() {
valueData, &valueDataSize) == ERROR_SUCCESS) { valueData, &valueDataSize) == ERROR_SUCCESS) {
if (valueType == REG_SZ) { if (valueType == REG_SZ) {
// Add the font file path to the vector // Add the font file path to the vector
std::string fontPath((char*)valueData); std::string fontPath(reinterpret_cast<char *>(valueData),
valueDataSize);
fontPaths.push_back(fontPath); fontPaths.push_back(fontPath);
} }
} }
@@ -64,10 +177,57 @@ void LoadSystemFonts() {
RegCloseKey(hKey); RegCloseKey(hKey);
} }
ImGuiIO& io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
for (const auto& fontPath : fontPaths) { // List of common font face names
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f); static const std::unordered_set<std::string> commonFontFaceNames = {
"arial",
"times",
"cour",
"verdana",
"tahoma",
"comic",
"Impact",
"ariblk",
"Trebuchet MS",
"Georgia",
"Palatino Linotype",
"Lucida Sans Unicode",
"Tahoma",
"Lucida Console"};
for (auto &fontPath : fontPaths) {
// Check if the font path has a "C:\" prefix
if (fontPath.substr(0, 2) != "C:") {
// Add "C:\Windows\Fonts\" prefix to the font path
fontPath = absl::StrFormat("C:\\Windows\\Fonts\\%s", fontPath.c_str());
}
// Check if the font file has a .ttf or .TTF extension
std::string extension = fontPath.substr(fontPath.find_last_of(".") + 1);
if (extension == "ttf" || extension == "TTF") {
// Get the font face name from the font path
std::string fontFaceName =
fontPath.substr(fontPath.find_last_of("\\/") + 1);
fontFaceName = fontFaceName.substr(0, fontFaceName.find_last_of("."));
// Check if the font face name is in the common font face names list
if (commonFontFaceNames.find(fontFaceName) != commonFontFaceNames.end()) {
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
// Merge icon set
// Icon configuration
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config;
static const float ICON_FONT_SIZE = 18.0f;
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, ICON_FONT_SIZE,
&icons_config, icons_ranges);
}
}
} }
} }
@@ -78,4 +238,7 @@ void LoadSystemFonts() {
// ... // ...
} }
#endif #endif
} // namespace core
} // namespace yaze

View File

@@ -1,26 +1,15 @@
// FontLoader.h #ifndef YAZE_APP_CORE_PLATFORM_FONTLOADER_H
#ifndef FONTLOADER_H #define YAZE_APP_CORE_PLATFORM_FONTLOADER_H
#define FONTLOADER_H
#include "TargetConditionals.h" #include "absl/status/status.h"
#ifdef _WIN32 namespace yaze {
#include <Windows.h> namespace core {
// Windows specific function declaration for loading system fonts into ImGui
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
DWORD FontType, LPARAM lParam);
#elif __APPLE__
#ifdef TARGET_OS_MAC
void LoadSystemFonts(); void LoadSystemFonts();
absl::Status LoadPackageFonts();
#elif TARGET_OS_IPHONE } // namespace core
} // namespace yaze
void LoadSystemFonts(); #endif // YAZE_APP_CORE_PLATFORM_FONTLOADER_H
#endif
#endif
#endif // FONTLOADER_H

View File

@@ -1,32 +1,20 @@
// FontLoader.mm
#include "app/core/platform/font_loader.h" #include "app/core/platform/font_loader.h"
#include "imgui/imgui.h" #import <CoreText/CoreText.h>
#include "app/gui/icons.h"
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#include <TargetConditionals.h> #include <TargetConditionals.h>
#import <CoreText/CoreText.h> #include "app/gui/icons.h"
#include "imgui/imgui.h"
#if TARGET_IPHONE_SIMULATOR == 1 #if TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1
/* iOS in Xcode simulator */
void LoadSystemFonts() {}
#elif TARGET_OS_IPHONE == 1
/* iOS */ /* iOS */
void LoadSystemFonts() {} void yaze::core::LoadSystemFonts() {}
#elif TARGET_OS_MAC == 1 #elif TARGET_OS_MAC == 1
/* macOS */ /* macOS */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
// MacOS Implementation void yaze::core::LoadSystemFonts() {
void LoadSystemFonts() {
// List of common macOS system fonts
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ]; NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
for (NSString *fontName in fontNames) { for (NSString *fontName in fontNames) {
@@ -67,8 +55,5 @@ void LoadSystemFonts() {
} }
} }
} }
#else
// Unsupported platform
#endif
#endif #endif

View File

@@ -0,0 +1,81 @@
#ifndef YAZE_APP_CORE_PLATFORM_RENDERER_H
#define YAZE_APP_CORE_PLATFORM_RENDERER_H
#include <SDL.h>
#include <memory>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/core/utils/sdl_deleter.h"
#include "app/gfx/bitmap.h"
namespace yaze {
namespace core {
/**
* @class Renderer
* @brief The Renderer class represents the renderer for the Yaze application.
*
* This class is a singleton that provides functionality for creating and
* rendering bitmaps to the screen. It also includes methods for updating
* bitmaps on the screen.
*/
class Renderer {
public:
static Renderer &GetInstance() {
static Renderer instance;
return instance;
}
absl::Status CreateRenderer(SDL_Window *window) {
renderer_ = std::unique_ptr<SDL_Renderer, SDL_Deleter>(SDL_CreateRenderer(
window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED));
if (renderer_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
}
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
return absl::OkStatus();
}
auto renderer() -> SDL_Renderer * { return renderer_.get(); }
/**
* @brief Used to render a bitmap to the screen.
*/
void RenderBitmap(gfx::Bitmap *bitmap) {
bitmap->CreateTexture(renderer_.get());
}
/**
* @brief Used to update a bitmap on the screen.
*/
void UpdateBitmap(gfx::Bitmap *bitmap) {
bitmap->UpdateTexture(renderer_.get());
}
absl::Status CreateAndRenderBitmap(int width, int height, int depth,
const std::vector<uint8_t> &data,
gfx::Bitmap &bitmap,
gfx::SnesPalette &palette) {
bitmap.Create(width, height, depth, data);
RETURN_IF_ERROR(bitmap.ApplyPalette(palette));
RenderBitmap(&bitmap);
return absl::OkStatus();
}
private:
Renderer() = default;
std::unique_ptr<SDL_Renderer, SDL_Deleter> renderer_;
Renderer(const Renderer &) = delete;
Renderer &operator=(const Renderer &) = delete;
};
} // namespace core
} // namespace yaze
#endif

View File

@@ -0,0 +1,29 @@
#ifndef YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
#define YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
#ifdef __APPLE__
#include <TargetConditionals.h>
#if TARGET_OS_OSX
#include "imgui_impl_osx.h"
@interface AppViewController : NSViewController <NSWindowDelegate>
@end
#else
@interface AppViewController : UIViewController <MTKViewDelegate>
@property(nonatomic) yaze::core::Controller *controller;
@property(nonatomic) UIHoverGestureRecognizer *hoverGestureRecognizer;
@property(nonatomic) UIPinchGestureRecognizer *pinchRecognizer;
@property(nonatomic) UISwipeGestureRecognizer *swipeRecognizer;
@property(nonatomic) UILongPressGestureRecognizer *longPressRecognizer;
@end
#endif
@interface AppViewController () <MTKViewDelegate>
@property(nonatomic, readonly) MTKView *mtkView;
@property(nonatomic, strong) id<MTLDevice> device;
@property(nonatomic, strong) id<MTLCommandQueue> commandQueue;
@end
#endif // __APPLE__
#endif // YAZE_APP_CORE_PLATFORM_APP_VIEW_CONTROLLER_H

View File

@@ -1,22 +1,65 @@
#include "app/core/labeling.h" #include "project.h"
#include <fstream>
#include <string>
#include "app/core/constants.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h" #include "imgui/misc/cpp/imgui_stdlib.h"
#include <cstdint>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "app/core/common.h"
#include "app/core/constants.h"
#include "app/gui/icons.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { absl::Status Project::Open(const std::string& project_path) {
filepath = project_path;
name = project_path.substr(project_path.find_last_of("/") + 1);
std::ifstream in(project_path);
if (!in.good()) {
return absl::InternalError("Could not open project file.");
}
std::string line;
std::getline(in, name);
std::getline(in, filepath);
std::getline(in, rom_filename_);
std::getline(in, code_folder_);
std::getline(in, labels_filename_);
std::getline(in, keybindings_file);
while (std::getline(in, line)) {
if (line == kEndOfProjectFile) {
break;
}
}
in.close();
return absl::OkStatus();
}
absl::Status Project::Save() {
RETURN_IF_ERROR(CheckForEmptyFields());
std::ofstream out(filepath + "/" + name + ".yaze");
if (!out.good()) {
return absl::InternalError("Could not open project file.");
}
out << name << std::endl;
out << filepath << std::endl;
out << rom_filename_ << std::endl;
out << code_folder_ << std::endl;
out << labels_filename_ << std::endl;
out << keybindings_file << std::endl;
out << kEndOfProjectFile << std::endl;
out.close();
return absl::OkStatus();
}
bool ResourceLabelManager::LoadLabels(const std::string& filename) { bool ResourceLabelManager::LoadLabels(const std::string& filename) {
std::ifstream file(filename); std::ifstream file(filename);
@@ -124,6 +167,11 @@ void ResourceLabelManager::SelectableLabelWithNameEdit(
} }
} }
std::string ResourceLabelManager::GetLabel(const std::string& type,
const std::string& key) {
return labels_[type][key];
}
std::string ResourceLabelManager::CreateOrGetLabel( std::string ResourceLabelManager::CreateOrGetLabel(
const std::string& type, const std::string& key, const std::string& type, const std::string& key,
const std::string& defaultValue) { const std::string& defaultValue) {
@@ -136,6 +184,5 @@ std::string ResourceLabelManager::CreateOrGetLabel(
return labels_[type][key]; return labels_[type][key];
} }
} // namespace core
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,26 +1,19 @@
#ifndef YAZE_APP_CORE_PROJECT_H #ifndef YAZE_APP_CORE_PROJECT_H
#define YAZE_APP_CORE_PROJECT_H #define YAZE_APP_CORE_PROJECT_H
#include "absl/strings/match.h" #include <algorithm>
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/utils/file_util.h"
namespace yaze { namespace yaze {
namespace app {
constexpr absl::string_view kProjectFileExtension = ".yaze"; const std::string kRecentFilesFilename = "recent_files.txt";
constexpr absl::string_view kProjectFileFilter = constexpr char kEndOfProjectFile[] = "EndOfProjectFile";
"Yaze Project Files (*.yaze)\0*.yaze\0";
constexpr absl::string_view kPreviousRomFilenameDelimiter =
"PreviousRomFilename";
constexpr absl::string_view kEndOfProjectFile = "EndOfProjectFile";
/** /**
* @struct Project * @struct Project
@@ -31,81 +24,12 @@ constexpr absl::string_view kEndOfProjectFile = "EndOfProjectFile";
* user can have different rom file names for a single project and keep track of * user can have different rom file names for a single project and keep track of
* backups. * backups.
*/ */
struct Project {
struct Project : public core::ExperimentFlags { absl::Status Create(const std::string& project_name) {
/**
* @brief Creates a new project.
*
* @param project_name The name of the project.
* @param project_path The path to the project.
* @return An absl::Status indicating the success or failure of the project
* creation.
*/
absl::Status Create(const std::string &project_name) {
name = project_name; name = project_name;
project_opened_ = true; project_opened_ = true;
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Open(const std::string &project_path) {
filepath = project_path;
name = project_path.substr(project_path.find_last_of("/") + 1);
std::ifstream in(project_path);
if (!in.good()) {
return absl::InternalError("Could not open project file.");
}
std::string line;
std::getline(in, name);
std::getline(in, filepath);
std::getline(in, rom_filename_);
std::getline(in, code_folder_);
std::getline(in, labels_filename_);
while (std::getline(in, line)) {
if (line == kEndOfProjectFile) {
break;
}
if (absl::StrContains(line, kPreviousRomFilenameDelimiter)) {
previous_rom_filenames_.push_back(
line.substr(line.find(kPreviousRomFilenameDelimiter) +
kPreviousRomFilenameDelimiter.size() + 1));
}
}
in.close();
return absl::OkStatus();
}
absl::Status Save() {
RETURN_IF_ERROR(CheckForEmptyFields());
std::ofstream out(filepath + "/" + name + ".yaze");
if (!out.good()) {
return absl::InternalError("Could not open project file.");
}
out << name << std::endl;
out << filepath << std::endl;
out << rom_filename_ << std::endl;
out << code_folder_ << std::endl;
out << labels_filename_ << std::endl;
for (const auto &filename : previous_rom_filenames_) {
out << kPreviousRomFilenameDelimiter << " " << filename << std::endl;
}
out << kEndOfProjectFile << std::endl;
out.close();
return absl::OkStatus();
}
absl::Status CheckForEmptyFields() { absl::Status CheckForEmptyFields() {
if (name.empty() || filepath.empty() || rom_filename_.empty() || if (name.empty() || filepath.empty() || rom_filename_.empty() ||
code_folder_.empty() || labels_filename_.empty()) { code_folder_.empty() || labels_filename_.empty()) {
@@ -116,23 +40,96 @@ struct Project : public core::ExperimentFlags {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Open(const std::string &project_path);
absl::Status SerializeExperimentFlags() { absl::Status Save();
auto flags = mutable_flags();
// TODO: Serialize flags
return absl::OkStatus();
}
bool project_opened_ = false; bool project_opened_ = false;
std::string name; std::string name;
std::string flags = "";
std::string filepath; std::string filepath;
std::string rom_filename_ = ""; std::string rom_filename_ = "";
std::string code_folder_ = ""; std::string code_folder_ = "";
std::string labels_filename_ = ""; std::string labels_filename_ = "";
std::vector<std::string> previous_rom_filenames_; std::string keybindings_file = "";
}; };
} // namespace app // Default types
static constexpr absl::string_view kDefaultTypes[] = {
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
struct ResourceLabelManager {
bool LoadLabels(const std::string& filename);
bool SaveLabels();
void DisplayLabels(bool* p_open);
void EditLabel(const std::string& type, const std::string& key,
const std::string& newValue);
void SelectableLabelWithNameEdit(bool selected, const std::string& type,
const std::string& key,
const std::string& defaultValue);
std::string GetLabel(const std::string& type, const std::string& key);
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
const std::string& defaultValue);
bool labels_loaded_ = false;
std::string filename_;
struct ResourceType {
std::string key_name;
std::string display_description;
};
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
labels_;
};
class RecentFilesManager {
public:
RecentFilesManager() : RecentFilesManager(kRecentFilesFilename) {}
RecentFilesManager(const std::string& filename) : filename_(filename) {}
void AddFile(const std::string& file_path) {
// Add a file to the list, avoiding duplicates
auto it = std::find(recent_files_.begin(), recent_files_.end(), file_path);
if (it == recent_files_.end()) {
recent_files_.push_back(file_path);
}
}
void Save() {
std::ofstream file(filename_);
if (!file.is_open()) {
return; // Handle the error appropriately
}
for (const auto& file_path : recent_files_) {
file << file_path << std::endl;
}
}
void Load() {
std::ifstream file(filename_);
if (!file.is_open()) {
return;
}
recent_files_.clear();
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
recent_files_.push_back(line);
}
}
}
const std::vector<std::string>& GetRecentFiles() const {
return recent_files_;
}
private:
std::string filename_;
std::vector<std::string> recent_files_;
};
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_PROJECT_H #endif // YAZE_APP_CORE_PROJECT_H

View File

@@ -1,19 +0,0 @@
#ifndef YAZE_APP_CORE_TESTABLE_H
#define YAZE_APP_CORE_TESTABLE_H
#include <imgui_test_engine/imgui_te_context.h>
namespace yaze {
namespace app {
namespace core {
class GuiTestable {
public:
virtual void RegisterTests(ImGuiTestEngine* e) = 0;
ImGuiTestEngine* test_engine;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_TESTABLE_H

View File

@@ -0,0 +1,95 @@
#include "file_util.h"
#if defined(_WIN32)
#include <windows.h>
#else
#include <dirent.h>
#include <sys/stat.h>
#endif
#include <fstream>
#include <sstream>
namespace yaze {
namespace core {
std::string GetFileExtension(const std::string &filename) {
size_t dot = filename.find_last_of(".");
if (dot == std::string::npos) {
return "";
}
return filename.substr(dot + 1);
}
std::string GetFileName(const std::string &filename) {
size_t slash = filename.find_last_of("/");
if (slash == std::string::npos) {
return filename;
}
return filename.substr(slash + 1);
}
std::string LoadFile(const std::string &filename) {
std::string contents;
std::ifstream file(filename);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
} else {
// Throw an exception
throw std::runtime_error("Could not open file: " + filename);
}
return contents;
}
std::string LoadConfigFile(const std::string &filename) {
std::string contents;
Platform platform;
#if defined(_WIN32)
platform = Platform::kWindows;
#elif defined(__APPLE__)
platform = Platform::kMacOS;
#else
platform = Platform::kLinux;
#endif
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
std::ifstream file(filepath);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
}
return contents;
}
void SaveFile(const std::string &filename, const std::string &contents,
Platform platform) {
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
std::ofstream file(filepath);
if (file.is_open()) {
file << contents;
file.close();
}
}
std::string GetConfigDirectory(Platform platform) {
std::string config_directory = ".yaze";
switch (platform) {
case Platform::kWindows:
config_directory = "~/AppData/Roaming/yaze";
break;
case Platform::kMacOS:
case Platform::kLinux:
config_directory = "~/.config/yaze";
break;
default:
break;
}
return config_directory;
}
} // namespace core
} // namespace yaze

View File

@@ -0,0 +1,23 @@
#ifndef YAZE_APP_CORE_UTILS_FILE_UTIL_H
#define YAZE_APP_CORE_UTILS_FILE_UTIL_H
#include <string>
namespace yaze {
namespace core {
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
std::string GetFileExtension(const std::string &filename);
std::string GetFileName(const std::string &filename);
std::string LoadFile(const std::string &filename);
std::string LoadConfigFile(const std::string &filename);
std::string GetConfigDirectory(Platform platform);
void SaveFile(const std::string &filename, const std::string &data,
Platform platform);
} // namespace core
} // namespace yaze
#endif // YAZE_APP_CORE_UTILS_FILE_UTIL_H

View File

@@ -0,0 +1,34 @@
#ifndef YAZE_APP_CORE_UTILS_SDL_DELETER_H_
#define YAZE_APP_CORE_UTILS_SDL_DELETER_H_
#include <SDL.h>
namespace yaze {
namespace core {
/**
* @brief Deleter for SDL_Window and SDL_Renderer.
*/
struct SDL_Deleter {
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
};
/**
* @brief Deleter for SDL_Texture.
*/
struct SDL_Texture_Deleter {
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
};
/**
* @brief Deleter for SDL_Surface.
*/
struct SDL_Surface_Deleter {
void operator()(SDL_Surface *p) const { SDL_FreeSurface(p); }
};
} // namespace core
} // namespace yaze
#endif // YAZE_APP_CORE_UTILS_SDL_DELETER_H_

View File

@@ -1,16 +1,15 @@
#include "assembly_editor.h" #include "assembly_editor.h"
#include "ImGuiColorTextEdit/TextEditor.h" #include "absl/strings/str_cat.h"
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/modules/text_editor.h"
#include "core/constants.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using core::FileDialogWrapper;
namespace { namespace {
std::vector<std::string> RemoveIgnoredFiles( std::vector<std::string> RemoveIgnoredFiles(
@@ -57,11 +56,11 @@ core::FolderItem LoadFolder(const std::string& folder) {
auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name); auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
current_folder.files = RemoveIgnoredFiles(root_files, ignored_files); current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
for (const auto& folder : for (const auto& subfolder :
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) { FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
core::FolderItem folder_item; core::FolderItem folder_item;
folder_item.name = folder; folder_item.name = subfolder;
std::string full_folder = current_folder.name + "/" + folder; std::string full_folder = current_folder.name + "/" + subfolder;
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder); auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
for (const auto& files : folder_files) { for (const auto& files : folder_files) {
// Remove subdirectory files // Remove subdirectory files
@@ -277,21 +276,14 @@ void AssemblyEditor::DrawFileTabView() {
void AssemblyEditor::DrawFileMenu() { void AssemblyEditor::DrawFileMenu() {
if (ImGui::BeginMenu("File")) { if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) { if (ImGui::MenuItem("Open", "Ctrl+O")) {
ImGuiFileDialog::Instance()->OpenDialog( auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", "."); ChangeActiveFile(filename);
} }
if (ImGui::MenuItem("Save", "Ctrl+S")) { if (ImGui::MenuItem("Save", "Ctrl+S")) {
// TODO: Implement this // TODO: Implement this
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
if (ImGuiFileDialog::Instance()->IsOk()) {
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
}
ImGuiFileDialog::Instance()->Close();
}
} }
void AssemblyEditor::DrawEditMenu() { void AssemblyEditor::DrawEditMenu() {
@@ -363,5 +355,4 @@ absl::Status AssemblyEditor::Redo() {
absl::Status AssemblyEditor::Update() { return absl::OkStatus(); } absl::Status AssemblyEditor::Update() { return absl::OkStatus(); }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,19 +1,14 @@
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H #ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#define 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> #include <string>
#include "app/core/common.h" #include "app/core/common.h"
#include "app/editor/utils/editor.h" #include "app/editor/editor.h"
#include "app/gui/modules/text_editor.h"
#include "app/gui/style.h" #include "app/gui/style.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
/** /**
@@ -73,7 +68,6 @@ class AssemblyEditor : public Editor {
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

View File

@@ -1,27 +1,20 @@
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H #ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H #define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/common.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#include "app/core/project.h" #include "app/core/project.h"
#include "app/editor/code/assembly_editor.h" #include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h" #include "app/editor/code/memory_editor.h"
#include "app/editor/dungeon/dungeon_editor.h" #include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/graphics_editor.h" #include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h" #include "app/editor/graphics/screen_editor.h"
#include "app/editor/music/music_editor.h" #include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h" #include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h" #include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/editor/utils/recent_files.h"
#include "app/emu/emulator.h" #include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
@@ -30,9 +23,11 @@
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using ImGui::SameLine; using ImGui::SameLine;
@@ -46,7 +41,7 @@ struct MemoryEditorWithDiffChecker : public SharedRom {
static Rom comparison_rom; static Rom comparison_rom;
ImGui::Begin("Hex Editor", &show_memory_editor); ImGui::Begin("Hex Editor", &show_memory_editor);
if (ImGui::Button("Compare Rom")) { if (ImGui::Button("Compare Rom")) {
auto file_name = FileDialogWrapper::ShowOpenFileDialog(); auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name)); PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name));
show_compare_rom = true; show_compare_rom = true;
} }
@@ -82,7 +77,6 @@ struct MemoryEditorWithDiffChecker : public SharedRom {
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H #endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H

View File

@@ -1,9 +1,7 @@
#include "dungeon_editor.h" #include "dungeon_editor.h"
#include "imgui/imgui.h" #include "absl/container/flat_hash_map.h"
#include "app/core/platform/renderer.h"
#include "app/core/common.h"
#include "app/core/labeling.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h" #include "app/gui/color.h"
@@ -11,13 +9,15 @@
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/dungeon/object_names.h" #include "app/zelda3/dungeon/object_names.h"
#include "app/zelda3/dungeon/room_names.h" #include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include "zelda3/dungeon/room.h" #include "zelda3/dungeon/room.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using core::Renderer;
using ImGui::BeginChild; using ImGui::BeginChild;
using ImGui::BeginTabBar; using ImGui::BeginTabBar;
using ImGui::BeginTabItem; using ImGui::BeginTabItem;
@@ -50,32 +50,34 @@ absl::Status DungeonEditor::Update() {
refresh_graphics_ = false; refresh_graphics_ = false;
} }
TAB_BAR("##DungeonEditorTabBar") if (ImGui::BeginTabBar("##DungeonEditorTabBar")) {
TAB_ITEM("Room Editor") TAB_ITEM("Room Editor")
status_ = UpdateDungeonRoomView(); status_ = UpdateDungeonRoomView();
END_TAB_ITEM() END_TAB_ITEM()
TAB_ITEM("Usage Statistics") TAB_ITEM("Usage Statistics")
if (is_loaded_) { if (is_loaded_) {
static bool calc_stats = false; static bool calc_stats = false;
if (!calc_stats) { if (!calc_stats) {
CalculateUsageStats(); CalculateUsageStats();
calc_stats = true; calc_stats = true;
}
DrawUsageStats();
} }
DrawUsageStats(); END_TAB_ITEM()
ImGui::EndTabBar();
} }
END_TAB_ITEM()
END_TAB_BAR()
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status DungeonEditor::Initialize() { absl::Status DungeonEditor::Initialize() {
auto dungeon_man_pal_group = rom()->palette_group().dungeon_main; auto dungeon_man_pal_group = rom()->palette_group().dungeon_main;
for (int i = 0; i < 0x100 + 40; i++) { for (int i = 0; i < 0x100 + 40; i++) {
rooms_.emplace_back(zelda3::dungeon::Room(i)); rooms_.emplace_back(zelda3::Room(/*room_id=*/i));
rooms_[i].LoadHeader(); rooms_[i].LoadHeader();
rooms_[i].LoadRoomFromROM(); rooms_[i].LoadRoomFromROM();
if (flags()->kDrawDungeonRoomGraphics) { if (core::ExperimentFlags::get().kDrawDungeonRoomGraphics) {
rooms_[i].LoadRoomGraphics(); rooms_[i].LoadRoomGraphics();
} }
@@ -95,11 +97,11 @@ absl::Status DungeonEditor::Initialize() {
LoadDungeonRoomSize(); LoadDungeonRoomSize();
// LoadRoomEntrances // LoadRoomEntrances
for (int i = 0; i < 0x07; ++i) { for (int i = 0; i < 0x07; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, true)); entrances_.emplace_back(zelda3::RoomEntrance(*rom(), i, true));
} }
for (int i = 0; i < 0x85; ++i) { for (int i = 0; i < 0x85; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, false)); entrances_.emplace_back(zelda3::RoomEntrance(*rom(), i, false));
} }
// Load the palette group and palette for the dungeon // Load the palette group and palette for the dungeon
@@ -107,7 +109,7 @@ absl::Status DungeonEditor::Initialize() {
ASSIGN_OR_RETURN(current_palette_group_, ASSIGN_OR_RETURN(current_palette_group_,
gfx::CreatePaletteGroupFromLargePalette(full_palette_)); gfx::CreatePaletteGroupFromLargePalette(full_palette_));
graphics_bin_ = *rom()->mutable_bitmap_manager(); graphics_bin_ = rom()->gfx_sheets();
// Create a vector of pointers to the current block bitmaps // Create a vector of pointers to the current block bitmaps
for (int block : rooms_[current_room_id_].blocks()) { for (int block : rooms_[current_room_id_].blocks()) {
room_gfx_sheets_.emplace_back(&graphics_bin_[block]); room_gfx_sheets_.emplace_back(&graphics_bin_[block]);
@@ -116,31 +118,36 @@ absl::Status DungeonEditor::Initialize() {
} }
absl::Status DungeonEditor::RefreshGraphics() { absl::Status DungeonEditor::RefreshGraphics() {
for (int i = 0; i < 8; i++) { std::for_each_n(
int block = rooms_[current_room_id_].blocks()[i]; rooms_[current_room_id_].blocks().begin(), 8,
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent( [this](int block) -> absl::Status {
current_palette_group_[current_palette_id_], 0)); RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
rom()->UpdateBitmap(&graphics_bin_[block], true); current_palette_group_[current_palette_id_], 0));
} Renderer::GetInstance().UpdateBitmap(&graphics_bin_[block]);
return absl::OkStatus();
});
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1; auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
for (int i = 9; i < 16; i++) { std::for_each_n(
int block = rooms_[current_room_id_].blocks()[i]; rooms_[current_room_id_].blocks().begin() + 8, 8,
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent( [this, &sprites_aux1_pal_group](int block) -> absl::Status {
sprites_aux1_pal_group[current_palette_id_], 0)); RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
rom()->UpdateBitmap(&graphics_bin_[block], true); sprites_aux1_pal_group[current_palette_id_], 0));
} Renderer::GetInstance().UpdateBitmap(&graphics_bin_[block]);
return absl::OkStatus();
});
return absl::OkStatus(); return absl::OkStatus();
} }
void DungeonEditor::LoadDungeonRoomSize() { void DungeonEditor::LoadDungeonRoomSize() {
std::map<int, std::vector<int>> rooms_by_bank; std::map<int, std::vector<int>> rooms_by_bank;
for (const auto& room : room_size_addresses_) { for (const auto &room : room_size_addresses_) {
int bank = room.second >> 16; int bank = room.second >> 16;
rooms_by_bank[bank].push_back(room.second); rooms_by_bank[bank].push_back(room.second);
} }
// Process and calculate room sizes within each bank // Process and calculate room sizes within each bank
for (auto& bank_rooms : rooms_by_bank) { for (auto &bank_rooms : rooms_by_bank) {
// Sort the rooms within this bank // Sort the rooms within this bank
std::sort(bank_rooms.second.begin(), bank_rooms.second.end()); std::sort(bank_rooms.second.begin(), bank_rooms.second.end());
@@ -150,7 +157,7 @@ void DungeonEditor::LoadDungeonRoomSize() {
// Identify the room ID for the current room pointer // Identify the room ID for the current room pointer
int room_id = int room_id =
std::find_if(room_size_addresses_.begin(), room_size_addresses_.end(), std::find_if(room_size_addresses_.begin(), room_size_addresses_.end(),
[room_ptr](const auto& entry) { [room_ptr](const auto &entry) {
return entry.second == room_ptr; return entry.second == room_ptr;
}) })
->first; ->first;
@@ -195,14 +202,15 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() {
TableNextRow(); TableNextRow();
TableNextColumn(); TableNextColumn();
TAB_BAR("##DungeonRoomTabBar"); if (ImGui::BeginTabBar("##DungeonRoomTabBar")) {
TAB_ITEM("Rooms"); TAB_ITEM("Rooms");
DrawRoomSelector(); DrawRoomSelector();
END_TAB_ITEM(); END_TAB_ITEM();
TAB_ITEM("Entrances"); TAB_ITEM("Entrances");
DrawEntranceSelector(); DrawEntranceSelector();
END_TAB_ITEM(); END_TAB_ITEM();
END_TAB_BAR(); ImGui::EndTabBar();
}
TableNextColumn(); TableNextColumn();
DrawDungeonTabView(); DrawDungeonTabView();
@@ -313,14 +321,14 @@ void DungeonEditor::DrawRoomSelector() {
gui::InputHexWord("Room ID", &current_room_id_); gui::InputHexWord("Room ID", &current_room_id_);
gui::InputHex("Palette ID", &current_palette_id_); gui::InputHex("Palette ID", &current_palette_id_);
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9); if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9);
BeginChild(child_id, ImGui::GetContentRegionAvail(), true, BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) { ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
int i = 0; int i = 0;
for (const auto each_room_name : zelda3::dungeon::kRoomNames) { for (const auto each_room_name : zelda3::kRoomNames) {
rom()->resource_label()->SelectableLabelWithNameEdit( rom()->resource_label()->SelectableLabelWithNameEdit(
current_room_id_ == i, "Dungeon Room Names", current_room_id_ == i, "Dungeon Room Names",
core::UppercaseHexByte(i), zelda3::dungeon::kRoomNames[i].data()); core::HexByte(i), each_room_name.data());
if (ImGui::IsItemClicked()) { if (ImGui::IsItemClicked()) {
// TODO: Jump to tab if room is already open // TODO: Jump to tab if room is already open
current_room_id_ = i; current_room_id_ = i;
@@ -394,8 +402,8 @@ void DungeonEditor::DrawEntranceSelector() {
for (int i = 0; i < 0x85 + 7; i++) { for (int i = 0; i < 0x85 + 7; i++) {
rom()->resource_label()->SelectableLabelWithNameEdit( rom()->resource_label()->SelectableLabelWithNameEdit(
current_entrance_id_ == i, "Dungeon Entrance Names", current_entrance_id_ == i, "Dungeon Entrance Names",
core::UppercaseHexByte(i), core::HexByte(i),
zelda3::dungeon::kEntranceNames[i].data()); zelda3::kEntranceNames[i].data());
if (ImGui::IsItemClicked()) { if (ImGui::IsItemClicked()) {
current_entrance_id_ = i; current_entrance_id_ = i;
@@ -426,12 +434,12 @@ void DungeonEditor::DrawDungeonTabView() {
for (int n = 0; n < active_rooms_.Size;) { for (int n = 0; n < active_rooms_.Size;) {
bool open = true; bool open = true;
if (active_rooms_[n] > sizeof(zelda3::dungeon::kRoomNames) / 4) { if (active_rooms_[n] > sizeof(zelda3::kRoomNames) / 4) {
active_rooms_.erase(active_rooms_.Data + n); active_rooms_.erase(active_rooms_.Data + n);
continue; continue;
} }
if (BeginTabItem(zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), if (BeginTabItem(zelda3::kRoomNames[active_rooms_[n]].data(),
&open, ImGuiTabItemFlags_None)) { &open, ImGuiTabItemFlags_None)) {
DrawDungeonCanvas(active_rooms_[n]); DrawDungeonCanvas(active_rooms_[n]);
EndTabItem(); EndTabItem();
@@ -498,7 +506,7 @@ void DungeonEditor::DrawRoomGraphics() {
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block; top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
} }
room_gfx_canvas_.draw_list()->AddImage( room_gfx_canvas_.draw_list()->AddImage(
(void*)graphics_bin_[block].texture(), (ImTextureID)(intptr_t)graphics_bin_[block].texture(),
ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y), ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y),
ImVec2(room_gfx_canvas_.zero_point().x + 0x100, ImVec2(room_gfx_canvas_.zero_point().x + 0x100,
room_gfx_canvas_.zero_point().y + offset)); room_gfx_canvas_.zero_point().y + offset));
@@ -512,7 +520,7 @@ void DungeonEditor::DrawRoomGraphics() {
void DungeonEditor::DrawTileSelector() { void DungeonEditor::DrawTileSelector() {
if (BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) { if (BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
if (BeginTabItem("Room Graphics")) { if (BeginTabItem("Room Graphics")) {
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3); if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
BeginChild(child_id, ImGui::GetContentRegionAvail(), true, BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) { ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
DrawRoomGraphics(); DrawRoomGraphics();
@@ -541,13 +549,13 @@ void DungeonEditor::DrawObjectRenderer() {
int selected_object = 0; int selected_object = 0;
int i = 0; int i = 0;
for (const auto object_name : zelda3::dungeon::Type1RoomObjectNames) { for (const auto object_name : zelda3::Type1RoomObjectNames) {
if (ImGui::Selectable(object_name.data(), selected_object == i)) { if (ImGui::Selectable(object_name.data(), selected_object == i)) {
selected_object = i; selected_object = i;
current_object_ = i; current_object_ = i;
object_renderer_.LoadObject(i, object_renderer_.LoadObject(i,
rooms_[current_room_id_].mutable_blocks()); rooms_[current_room_id_].mutable_blocks());
rom()->RenderBitmap(object_renderer_.bitmap()); Renderer::GetInstance().RenderBitmap(object_renderer_.bitmap());
object_loaded_ = true; object_loaded_ = true;
} }
i += 1; i += 1;
@@ -575,7 +583,7 @@ void DungeonEditor::DrawObjectRenderer() {
if (object_loaded_) { if (object_loaded_) {
ImGui::Begin("Memory Viewer", &object_loaded_, 0); ImGui::Begin("Memory Viewer", &object_loaded_, 0);
static MemoryEditor mem_edit; static MemoryEditor mem_edit;
mem_edit.DrawContents((void*)object_renderer_.mutable_memory(), mem_edit.DrawContents((void *)object_renderer_.mutable_memory(),
object_renderer_.mutable_memory()->size()); object_renderer_.mutable_memory()->size());
ImGui::End(); ImGui::End();
} }
@@ -584,7 +592,7 @@ void DungeonEditor::DrawObjectRenderer() {
// ============================================================================ // ============================================================================
void DungeonEditor::CalculateUsageStats() { void DungeonEditor::CalculateUsageStats() {
for (const auto& room : rooms_) { for (const auto &room : rooms_) {
if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) { if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) {
blockset_usage_[room.blockset] = 1; blockset_usage_[room.blockset] = 1;
} else { } else {
@@ -606,15 +614,15 @@ void DungeonEditor::CalculateUsageStats() {
} }
void DungeonEditor::RenderSetUsage( void DungeonEditor::RenderSetUsage(
const absl::flat_hash_map<uint16_t, int>& usage_map, uint16_t& selected_set, const absl::flat_hash_map<uint16_t, int> &usage_map, uint16_t &selected_set,
int spriteset_offset) { int spriteset_offset) {
// Sort the usage map by set number // Sort the usage map by set number
std::vector<std::pair<uint16_t, int>> sorted_usage(usage_map.begin(), std::vector<std::pair<uint16_t, int>> sorted_usage(usage_map.begin(),
usage_map.end()); usage_map.end());
std::sort(sorted_usage.begin(), sorted_usage.end(), std::sort(sorted_usage.begin(), sorted_usage.end(),
[](const auto& a, const auto& b) { return a.first < b.first; }); [](const auto &a, const auto &b) { return a.first < b.first; });
for (const auto& [set, count] : sorted_usage) { for (const auto &[set, count] : sorted_usage) {
std::string display_str; std::string display_str;
if (spriteset_offset != 0x00) { if (spriteset_offset != 0x00) {
display_str = absl::StrFormat("%#02x, %#02x: %d", set, display_str = absl::StrFormat("%#02x, %#02x: %d", set,
@@ -635,7 +643,7 @@ namespace {
// Range for spritesets 0-0x8F // Range for spritesets 0-0x8F
// Range for palettes 0-0x47 // Range for palettes 0-0x47
template <typename T> template <typename T>
void RenderUnusedSets(const absl::flat_hash_map<T, int>& usage_map, int max_set, void RenderUnusedSets(const absl::flat_hash_map<T, int> &usage_map, int max_set,
int spriteset_offset = 0x00) { int spriteset_offset = 0x00) {
std::vector<int> unused_sets; std::vector<int> unused_sets;
for (int i = 0; i < max_set; i++) { for (int i = 0; i < max_set; i++) {
@@ -643,7 +651,7 @@ void RenderUnusedSets(const absl::flat_hash_map<T, int>& usage_map, int max_set,
unused_sets.push_back(i); unused_sets.push_back(i);
} }
} }
for (const auto& set : unused_sets) { for (const auto &set : unused_sets) {
if (spriteset_offset != 0x00) { if (spriteset_offset != 0x00) {
Text("%#02x, %#02x", set, (set + spriteset_offset)); Text("%#02x, %#02x", set, (set + spriteset_offset));
} else { } else {
@@ -753,7 +761,7 @@ void DungeonEditor::DrawUsageGrid() {
break; break;
} }
// Determine if this square should be highlighted // Determine if this square should be highlighted
const auto& room = rooms_[row * squaresWide + col]; const auto &room = rooms_[row * squaresWide + col];
// Create a button or selectable for each square // Create a button or selectable for each square
ImGui::BeginGroup(); ImGui::BeginGroup();
@@ -826,5 +834,4 @@ void DungeonEditor::DrawUsageGrid() {
} }
} // namespace editor } // namespace editor
} // namespace app } // namespace yaze
} // namespace yaze

View File

@@ -1,22 +1,19 @@
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H #ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H #define YAZE_APP_EDITOR_DUNGEONEDITOR_H
#include "imgui/imgui.h" #include "absl/container/flat_hash_map.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/labeling.h" #include "app/editor/editor.h"
#include "app/editor/graphics/gfx_group_editor.h" #include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
#include "imgui/imgui.h"
#include "zelda3/dungeon/room.h" #include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_entrance.h" #include "zelda3/dungeon/room_entrance.h"
#include "zelda3/dungeon/room_object.h" #include "zelda3/dungeon/room_object.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
constexpr ImGuiTabItemFlags kDungeonTabFlags = constexpr ImGuiTabItemFlags kDungeonTabFlags =
@@ -42,9 +39,7 @@ constexpr ImGuiTableFlags kDungeonTableFlags =
* tile selector, and object renderer. Additionally, it handles loading room * tile selector, and object renderer. Additionally, it handles loading room
* entrances, calculating usage statistics, and rendering set usage. * entrances, calculating usage statistics, and rendering set usage.
*/ */
class DungeonEditor : public Editor, class DungeonEditor : public Editor, public SharedRom {
public SharedRom,
public core::ExperimentFlags {
public: public:
DungeonEditor() { type_ = EditorType::kDungeon; } DungeonEditor() { type_ = EditorType::kDungeon; }
@@ -100,7 +95,6 @@ class DungeonEditor : public Editor,
bool object_loaded_ = false; bool object_loaded_ = false;
bool palette_showing_ = false; bool palette_showing_ = false;
bool refresh_graphics_ = false; bool refresh_graphics_ = false;
bool show_object_render_ = false;
uint16_t current_entrance_id_ = 0; uint16_t current_entrance_id_ = 0;
uint16_t current_room_id_ = 0; uint16_t current_room_id_ = 0;
@@ -120,13 +114,12 @@ class DungeonEditor : public Editor,
gui::Canvas object_canvas_; gui::Canvas object_canvas_;
gfx::Bitmap room_gfx_bmp_; gfx::Bitmap room_gfx_bmp_;
gfx::BitmapManager graphics_bin_; std::array<gfx::Bitmap, kNumGfxSheets> graphics_bin_;
std::vector<gfx::Bitmap*> room_gfx_sheets_; std::vector<gfx::Bitmap*> room_gfx_sheets_;
std::vector<zelda3::dungeon::Room> rooms_; std::vector<zelda3::Room> rooms_;
std::vector<zelda3::dungeon::RoomEntrance> entrances_; std::vector<zelda3::RoomEntrance> entrances_;
std::vector<gfx::BitmapManager> room_graphics_; zelda3::DungeonObjectRenderer object_renderer_;
zelda3::dungeon::DungeonObjectRenderer object_renderer_;
absl::flat_hash_map<uint16_t, int> spriteset_usage_; absl::flat_hash_map<uint16_t, int> spriteset_usage_;
absl::flat_hash_map<uint16_t, int> blockset_usage_; absl::flat_hash_map<uint16_t, int> blockset_usage_;
@@ -147,7 +140,6 @@ class DungeonEditor : public Editor,
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

7
src/app/editor/editor.cc Normal file
View File

@@ -0,0 +1,7 @@
#include "editor.h"
namespace yaze {
namespace editor {
} // namespace editor
} // namespace yaze

View File

@@ -1,21 +1,21 @@
set( set(
YAZE_APP_EDITOR_SRC YAZE_APP_EDITOR_SRC
app/editor/editor.cc
app/editor/editor_manager.cc
app/editor/dungeon/dungeon_editor.cc app/editor/dungeon/dungeon_editor.cc
app/editor/master_editor.cc app/editor/overworld/overworld_editor.cc
app/editor/master_editor_test.cc
app/editor/settings_editor.cc
app/editor/overworld_editor.cc
app/editor/sprite/sprite_editor.cc app/editor/sprite/sprite_editor.cc
app/editor/music/music_editor.cc app/editor/music/music_editor.cc
app/editor/message/message_editor.cc app/editor/message/message_editor.cc
app/editor/message/message_editor_test.cc app/editor/message/message_data.cc
app/editor/code/assembly_editor.cc app/editor/code/assembly_editor.cc
app/editor/graphics/screen_editor.cc app/editor/graphics/screen_editor.cc
app/editor/graphics/graphics_editor.cc app/editor/graphics/graphics_editor.cc
app/editor/graphics/palette_editor.cc app/editor/graphics/palette_editor.cc
app/editor/graphics/tile16_editor.cc app/editor/graphics/tile16_editor.cc
app/editor/graphics/gfx_group_editor.cc app/editor/graphics/gfx_group_editor.cc
app/editor/utils/gfx_context.cc
app/editor/overworld/refresh.cc
app/editor/overworld/entity.cc app/editor/overworld/entity.cc
) app/editor/system/settings_editor.cc
app/editor/system/command_manager.cc
app/editor/system/extension_manager.cc
)

View File

@@ -1,17 +1,31 @@
#ifndef YAZE_APP_CORE_EDITOR_H #ifndef YAZE_APP_CORE_EDITOR_H
#define YAZE_APP_CORE_EDITOR_H #define YAZE_APP_CORE_EDITOR_H
#include <array>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/editor/system/command_manager.h"
#include "app/editor/system/constant_manager.h"
#include "app/editor/system/extension_manager.h"
#include "app/editor/system/history_manager.h"
#include "app/editor/system/resource_manager.h"
namespace yaze { namespace yaze {
namespace app {
/** /**
* @namespace yaze::app::editor * @namespace yaze::editor
* @brief Editors are the view controllers for the application. * @brief Editors are the view controllers for the application.
*/ */
namespace editor { namespace editor {
struct EditorContext {
ConstantManager constant_manager;
CommandManager command_manager;
ExtensionManager extension_manager;
HistoryManager history_manager;
ResourceManager resource_manager;
};
enum class EditorType { enum class EditorType {
kAssembly, kAssembly,
kDungeon, kDungeon,
@@ -25,7 +39,7 @@ enum class EditorType {
kSettings, kSettings,
}; };
constexpr std::array<const char*, 10> kEditorNames = { constexpr std::array<const char *, 10> kEditorNames = {
"Assembly", "Dungeon", "Graphics", "Music", "Overworld", "Assembly", "Dungeon", "Graphics", "Music", "Overworld",
"Palette", "Screen", "Sprite", "Message", "Settings", "Palette", "Screen", "Sprite", "Message", "Settings",
}; };
@@ -56,10 +70,10 @@ class Editor {
protected: protected:
EditorType type_; EditorType type_;
EditorContext context_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_EDITOR_H #endif // YAZE_APP_CORE_EDITOR_H

View File

@@ -1,46 +1,36 @@
#include "master_editor.h" #include "editor_manager.h"
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "abseil-cpp/absl/strings/match.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_internal.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/common.h" #include "absl/strings/match.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h" #include "app/editor/code/assembly_editor.h"
#include "app/editor/dungeon/dungeon_editor.h" #include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/graphics/graphics_editor.h" #include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h" #include "app/editor/graphics/screen_editor.h"
#include "app/editor/music/music_editor.h" #include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h" #include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h" #include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/flags.h" #include "app/editor/system/flags.h"
#include "app/editor/utils/recent_files.h"
#include "app/emu/emulator.h" #include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
#include "editor/editor.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using namespace ImGui; using namespace ImGui;
using core::FileDialogWrapper;
namespace { namespace {
bool BeginCentered(const char *name) { bool BeginCentered(const char *name) {
ImGuiIO const &io = GetIO(); ImGuiIO const &io = GetIO();
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f); ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
@@ -51,36 +41,30 @@ bool BeginCentered(const char *name) {
return Begin(name, nullptr, flags); return Begin(name, nullptr, flags);
} }
bool IsEditorActive(Editor* editor, std::vector<Editor*>& active_editors) { bool IsEditorActive(Editor *editor, std::vector<Editor *> &active_editors) {
return std::find(active_editors.begin(), active_editors.end(), editor) != return std::find(active_editors.begin(), active_editors.end(), editor) !=
active_editors.end(); active_editors.end();
} }
} // namespace } // namespace
void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer, void EditorManager::Initialize(std::string filename) {
std::string filename) {
sdl_renderer_ = renderer;
rom()->SetupRenderer(renderer);
if (!filename.empty()) { if (!filename.empty()) {
PRINT_IF_ERROR(rom()->LoadFromFile(filename)); PRINT_IF_ERROR(rom()->LoadFromFile(filename));
} }
overworld_editor_.InitializeZeml(); overworld_editor_.Initialize();
} }
absl::Status MasterEditor::Update() { absl::Status EditorManager::Update() {
ManageKeyboardShortcuts(); ManageKeyboardShortcuts();
DrawYazeMenu(); DrawYazeMenu();
DrawFileDialog();
DrawStatusPopup(); DrawStatusPopup();
DrawAboutPopup(); DrawAboutPopup();
DrawInfoPopup(); DrawInfoPopup();
if (rom()->is_loaded() && !rom_assets_loaded_) { if (rom()->is_loaded() && !rom_assets_loaded_) {
// Load all of the graphics data from the game.
RETURN_IF_ERROR(rom()->LoadAllGraphicsData()) RETURN_IF_ERROR(rom()->LoadAllGraphicsData())
// Initialize overworld graphics, maps, and palettes
RETURN_IF_ERROR(overworld_editor_.LoadGraphics()); RETURN_IF_ERROR(overworld_editor_.LoadGraphics());
rom_assets_loaded_ = true; rom_assets_loaded_ = true;
} }
@@ -90,7 +74,7 @@ absl::Status MasterEditor::Update() {
return absl::OkStatus(); return absl::OkStatus();
} }
void MasterEditor::ManageActiveEditors() { void EditorManager::ManageActiveEditors() {
// Show popup pane to select an editor to add // Show popup pane to select an editor to add
static bool show_add_editor = false; static bool show_add_editor = false;
if (show_add_editor) OpenPopup("AddEditor"); if (show_add_editor) OpenPopup("AddEditor");
@@ -253,16 +237,17 @@ void MasterEditor::ManageActiveEditors() {
} }
} }
void MasterEditor::ManageKeyboardShortcuts() { void EditorManager::ManageKeyboardShortcuts() {
bool ctrl_or_super = (GetIO().KeyCtrl || GetIO().KeySuper); bool ctrl_or_super = (GetIO().KeyCtrl || GetIO().KeySuper);
editor_context_.command_manager.ShowWhichKey();
// If CMD + R is pressed, reload the top result of recent files // If CMD + R is pressed, reload the top result of recent files
if (IsKeyDown(ImGuiKey_R) && ctrl_or_super) { if (IsKeyDown(ImGuiKey_R) && ctrl_or_super) {
static RecentFilesManager manager("recent_files.txt"); static RecentFilesManager manager("recent_files.txt");
manager.Load(); manager.Load();
if (!manager.GetRecentFiles().empty()) { if (!manager.GetRecentFiles().empty()) {
auto front = manager.GetRecentFiles().front(); auto front = manager.GetRecentFiles().front();
std::cout << "Reloading: " << front << std::endl;
OpenRomOrProject(front); OpenRomOrProject(front);
} }
} }
@@ -311,55 +296,38 @@ void MasterEditor::ManageKeyboardShortcuts() {
} }
} }
void MasterEditor::DrawFileDialog() { void EditorManager::DrawStatusPopup() {
gui::FileDialogPipeline("ChooseFileDlgKey", ".sfc,.smc", std::nullopt, [&]() { static absl::Status prev_status;
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
status_ = rom()->LoadFromFile(filePathName);
static RecentFilesManager manager("recent_files.txt");
// Load existing recent files
manager.Load();
// Add a new file
manager.AddFile(filePathName);
// Save the updated list
manager.Save();
});
}
void MasterEditor::DrawStatusPopup() {
if (!status_.ok()) { if (!status_.ok()) {
show_status_ = true; show_status_ = true;
prev_status_ = status_; prev_status = status_;
} }
if (show_status_ && (BeginCentered("StatusWindow"))) { if (show_status_ && (BeginCentered("StatusWindow"))) {
Text("%s", ICON_MD_ERROR); Text("%s", ICON_MD_ERROR);
Text("%s", prev_status_.ToString().c_str()); Text("%s", prev_status.ToString().c_str());
Spacing(); Spacing();
NextColumn(); NextColumn();
Columns(1); Columns(1);
Separator(); Separator();
NewLine(); NewLine();
SameLine(128); SameLine(128);
if (Button("OK", gui::kDefaultModalSize) || if (Button("OK", gui::kDefaultModalSize) || IsKeyPressed(ImGuiKey_Space)) {
IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) {
show_status_ = false; show_status_ = false;
status_ = absl::OkStatus(); status_ = absl::OkStatus();
} }
SameLine(); SameLine();
if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) { if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
SetClipboardText(prev_status_.ToString().c_str()); SetClipboardText(prev_status.ToString().c_str());
} }
End(); End();
} }
} }
void MasterEditor::DrawAboutPopup() { void EditorManager::DrawAboutPopup() {
if (about_) OpenPopup("About"); if (about_) OpenPopup("About");
if (BeginPopupModal("About", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { if (BeginPopupModal("About", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
Text("Yet Another Zelda3 Editor - v%.2f", core::kYazeVersion); Text("Yet Another Zelda3 Editor - v%s", version_.c_str());
Text("Written by: scawful"); Text("Written by: scawful");
Spacing(); Spacing();
Text("Special Thanks: Zarby89, JaredBrian"); Text("Special Thanks: Zarby89, JaredBrian");
@@ -373,15 +341,15 @@ void MasterEditor::DrawAboutPopup() {
} }
} }
void MasterEditor::DrawInfoPopup() { void EditorManager::DrawInfoPopup() {
if (rom_info_) OpenPopup("ROM Information"); if (rom_info_) OpenPopup("ROM Information");
if (BeginPopupModal("ROM Information", nullptr, if (BeginPopupModal("ROM Information", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
Text("Title: %s", rom()->title()); Text("Title: %s", rom()->title().c_str());
Text("ROM Size: %ld", rom()->size()); Text("ROM Size: %s", core::HexLongLong(rom()->size()).c_str());
if (Button("Close", gui::kDefaultModalSize) || if (Button("Close", gui::kDefaultModalSize) ||
IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) { IsKeyPressed(ImGuiKey_Escape)) {
rom_info_ = false; rom_info_ = false;
CloseCurrentPopup(); CloseCurrentPopup();
} }
@@ -389,33 +357,19 @@ void MasterEditor::DrawInfoPopup() {
} }
} }
void MasterEditor::DrawYazeMenu() { void EditorManager::DrawYazeMenu() {
static bool show_display_settings = false; static bool show_display_settings = false;
static bool show_command_line_interface = false;
if (BeginMenuBar()) { if (BeginMenuBar()) {
DrawFileMenu(); DrawYazeMenuBar();
DrawEditMenu();
DrawViewMenu();
DrawTestMenu();
DrawProjectMenu();
DrawHelpMenu();
SameLine(GetWindowWidth() - GetStyle().ItemSpacing.x - SameLine(GetWindowWidth() - GetStyle().ItemSpacing.x -
CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150); CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 110);
// Modify the style of the button to have no background color
PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
if (Button(ICON_MD_DISPLAY_SETTINGS)) { if (Button(ICON_MD_DISPLAY_SETTINGS)) {
show_display_settings = !show_display_settings; show_display_settings = !show_display_settings;
} }
if (Button(ICON_MD_TERMINAL)) {
show_command_line_interface = !show_command_line_interface;
}
PopStyleColor(); PopStyleColor();
Text("yaze v%s", version_.c_str());
Text("%s", absl::StrCat("yaze v", core::kYazeVersion).c_str());
EndMenuBar(); EndMenuBar();
} }
@@ -424,27 +378,9 @@ void MasterEditor::DrawYazeMenu() {
gui::DrawDisplaySettings(); gui::DrawDisplaySettings();
End(); End();
} }
if (show_command_line_interface) {
Begin("Command Line Interface", &show_command_line_interface,
ImGuiWindowFlags_None);
Text("Enter a command:");
End();
}
} }
void MasterEditor::OpenRomOrProject(const std::string& filename) { void EditorManager::DrawYazeMenuBar() {
if (absl::StrContains(filename, ".yaze")) {
status_ = current_project_.Open(filename);
if (status_.ok()) {
status_ = OpenProject();
}
} else {
status_ = rom()->LoadFromFile(filename);
}
}
void MasterEditor::DrawFileMenu() {
static bool save_as_menu = false; static bool save_as_menu = false;
static bool new_project_menu = false; static bool new_project_menu = false;
@@ -459,7 +395,7 @@ void MasterEditor::DrawFileMenu() {
if (manager.GetRecentFiles().empty()) { if (manager.GetRecentFiles().empty()) {
MenuItem("No Recent Files", nullptr, false, false); MenuItem("No Recent Files", nullptr, false, false);
} else { } else {
for (const auto& filePath : manager.GetRecentFiles()) { for (const auto &filePath : manager.GetRecentFiles()) {
if (MenuItem(filePath.c_str())) { if (MenuItem(filePath.c_str())) {
OpenRomOrProject(filePath); OpenRomOrProject(filePath);
} }
@@ -476,7 +412,6 @@ void MasterEditor::DrawFileMenu() {
MENU_ITEM("Save As..") { save_as_menu = true; } MENU_ITEM("Save As..") { save_as_menu = true; }
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
MENU_ITEM("Reload") { status_ = rom()->Reload(); }
MENU_ITEM("Close") { MENU_ITEM("Close") {
status_ = rom()->Close(); status_ = rom()->Close();
rom_assets_loaded_ = false; rom_assets_loaded_ = false;
@@ -492,8 +427,8 @@ void MasterEditor::DrawFileMenu() {
} }
if (MenuItem("Open Project")) { if (MenuItem("Open Project")) {
// Open an existing project // Open an existing project
status_ = status_ = current_project_.Open(
current_project_.Open(FileDialogWrapper::ShowOpenFileDialog()); core::FileDialogWrapper::ShowOpenFileDialog());
if (status_.ok()) { if (status_.ok()) {
status_ = OpenProject(); status_ = OpenProject();
} }
@@ -582,9 +517,7 @@ void MasterEditor::DrawFileMenu() {
} }
End(); End();
} }
}
void MasterEditor::DrawEditMenu() {
if (BeginMenu("Edit")) { if (BeginMenu("Edit")) {
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); } MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); }
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); } MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); }
@@ -593,12 +526,10 @@ void MasterEditor::DrawEditMenu() {
MENU_ITEM2("Copy", "Ctrl+C") { status_ = current_editor_->Copy(); } MENU_ITEM2("Copy", "Ctrl+C") { status_ = current_editor_->Copy(); }
MENU_ITEM2("Paste", "Ctrl+V") { status_ = current_editor_->Paste(); } MENU_ITEM2("Paste", "Ctrl+V") { status_ = current_editor_->Paste(); }
Separator(); Separator();
MENU_ITEM2("Find", "Ctrl+F") {} MENU_ITEM2("Find", "Ctrl+F") { status_ = current_editor_->Find(); }
EndMenu(); EndMenu();
} }
}
void MasterEditor::DrawViewMenu() {
static bool show_imgui_metrics = false; static bool show_imgui_metrics = false;
static bool show_memory_editor = false; static bool show_memory_editor = false;
static bool show_asm_editor = false; static bool show_asm_editor = false;
@@ -606,28 +537,17 @@ void MasterEditor::DrawViewMenu() {
static bool show_palette_editor = false; static bool show_palette_editor = false;
static bool show_emulator = false; static bool show_emulator = false;
if (show_imgui_demo) ShowDemoWindow();
if (show_imgui_metrics) ShowMetricsWindow(&show_imgui_metrics);
if (show_memory_editor) memory_editor_.Update(show_memory_editor);
if (show_asm_editor) assembly_editor_.Update(show_asm_editor);
if (show_emulator) { if (show_emulator) {
Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar); Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar);
emulator_.Run(); emulator_.Run();
End(); End();
} }
if (show_imgui_metrics) {
ShowMetricsWindow(&show_imgui_metrics);
}
if (show_memory_editor) {
memory_editor_.Update(show_memory_editor);
}
if (show_imgui_demo) {
ShowDemoWindow();
}
if (show_asm_editor) {
assembly_editor_.Update(show_asm_editor);
}
if (show_palette_editor) { if (show_palette_editor) {
Begin("Palette Editor", &show_palette_editor); Begin("Palette Editor", &show_palette_editor);
status_ = palette_editor_.Update(); status_ = palette_editor_.Update();
@@ -647,23 +567,9 @@ void MasterEditor::DrawViewMenu() {
MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics); MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
EndMenu(); EndMenu();
} }
}
void MasterEditor::DrawTestMenu() {
static bool show_tests_ = false;
if (BeginMenu("Tests")) {
MenuItem("Run Tests", nullptr, &show_tests_);
EndMenu();
}
}
void MasterEditor::DrawProjectMenu() {
static bool show_resource_label_manager = false; static bool show_resource_label_manager = false;
if (current_project_.project_opened_) { if (current_project_.project_opened_) {
// Project Menu
if (BeginMenu("Project")) { if (BeginMenu("Project")) {
Text("Name: %s", current_project_.name.c_str()); Text("Name: %s", current_project_.name.c_str());
Text("ROM: %s", current_project_.rom_filename_.c_str()); Text("ROM: %s", current_project_.rom_filename_.c_str());
@@ -674,16 +580,7 @@ void MasterEditor::DrawProjectMenu() {
EndMenu(); EndMenu();
} }
} }
if (show_resource_label_manager) {
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
if (current_project_.project_opened_ &&
!current_project_.labels_filename_.empty()) {
current_project_.labels_filename_ = rom()->resource_label()->filename_;
}
}
}
void MasterEditor::DrawHelpMenu() {
static bool open_rom_help = false; static bool open_rom_help = false;
static bool open_supported_features = false; static bool open_supported_features = false;
static bool open_manage_project = false; static bool open_manage_project = false;
@@ -752,11 +649,10 @@ void MasterEditor::DrawHelpMenu() {
Text("Project Menu"); Text("Project Menu");
Text("Create a new project or open an existing one."); Text("Create a new project or open an existing one.");
Text("Save the project to save the current state of the project."); Text("Save the project to save the current state of the project.");
Text( TextWrapped(
"To save a project, you need to first open a ROM and initialize your " "To save a project, you need to first open a ROM and initialize your "
"code path and labels file. Label resource manager can be found in " "code path and labels file. Label resource manager can be found in "
"the " "the View menu. Code path is set in the Code editor after opening a "
"View menu. Code path is set in the Code editor after opening a "
"folder."); "folder.");
if (Button("Close", gui::kDefaultModalSize)) { if (Button("Close", gui::kDefaultModalSize)) {
@@ -765,58 +661,51 @@ void MasterEditor::DrawHelpMenu() {
} }
EndPopup(); EndPopup();
} }
if (show_resource_label_manager) {
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
if (current_project_.project_opened_ &&
!current_project_.labels_filename_.empty()) {
current_project_.labels_filename_ = rom()->resource_label()->filename_;
}
}
} }
void MasterEditor::LoadRom() { void EditorManager::LoadRom() {
if (flags()->kNewFileDialogWrapper) { auto file_name = FileDialogWrapper::ShowOpenFileDialog();
auto file_name = FileDialogWrapper::ShowOpenFileDialog(); auto load_rom = rom()->LoadFromFile(file_name);
PRINT_IF_ERROR(rom()->LoadFromFile(file_name)); if (load_rom.ok()) {
static RecentFilesManager manager("recent_files.txt"); static RecentFilesManager manager("recent_files.txt");
manager.Load(); manager.Load();
manager.AddFile(file_name); manager.AddFile(file_name);
manager.Save(); manager.Save();
} else {
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
".sfc,.smc", ".");
} }
} }
void MasterEditor::SaveRom() { void EditorManager::SaveRom() {
if (flags()->kSaveDungeonMaps) { if (core::ExperimentFlags::get().kSaveDungeonMaps) {
status_ = screen_editor_.SaveDungeonMaps(); status_ = screen_editor_.SaveDungeonMaps();
RETURN_VOID_IF_ERROR(status_); RETURN_VOID_IF_ERROR(status_);
} }
if (flags()->overworld.kSaveOverworldMaps) {
RETURN_VOID_IF_ERROR( status_ = overworld_editor_.Save();
status_ = overworld_editor_.overworld()->CreateTile32Tilemap()); RETURN_VOID_IF_ERROR(status_);
status_ = overworld_editor_.overworld()->SaveMap32Tiles();
RETURN_VOID_IF_ERROR(status_);
status_ = overworld_editor_.overworld()->SaveMap16Tiles();
RETURN_VOID_IF_ERROR(status_);
status_ = overworld_editor_.overworld()->SaveOverworldMaps();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldEntrances) {
status_ = overworld_editor_.overworld()->SaveEntrances();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldExits) {
status_ = overworld_editor_.overworld()->SaveExits();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldItems) {
status_ = overworld_editor_.overworld()->SaveItems();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldProperties) {
status_ = overworld_editor_.overworld()->SaveMapProperties();
RETURN_VOID_IF_ERROR(status_);
}
status_ = rom()->SaveToFile(backup_rom_, save_new_auto_); status_ = rom()->SaveToFile(backup_rom_, save_new_auto_);
} }
absl::Status MasterEditor::OpenProject() { void EditorManager::OpenRomOrProject(const std::string &filename) {
if (absl::StrContains(filename, ".yaze")) {
status_ = current_project_.Open(filename);
if (status_.ok()) {
status_ = OpenProject();
}
} else {
status_ = rom()->LoadFromFile(filename);
}
}
absl::Status EditorManager::OpenProject() {
RETURN_IF_ERROR(rom()->LoadFromFile(current_project_.rom_filename_)); RETURN_IF_ERROR(rom()->LoadFromFile(current_project_.rom_filename_));
if (!rom()->resource_label()->LoadLabels(current_project_.labels_filename_)) { if (!rom()->resource_label()->LoadLabels(current_project_.labels_filename_)) {
@@ -838,5 +727,4 @@ absl::Status MasterEditor::OpenProject() {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,17 +1,9 @@
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H #ifndef YAZE_APP_EDITOR_EDITOR_MANAGER_H
#define YAZE_APP_EDITOR_MASTER_EDITOR_H #define YAZE_APP_EDITOR_EDITOR_MANAGER_H
#define IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS
#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 "absl/status/status.h"
#include "app/core/common.h"
#include "app/core/constants.h"
#include "app/core/project.h" #include "app/core/project.h"
#include "app/editor/code/assembly_editor.h" #include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h" #include "app/editor/code/memory_editor.h"
@@ -21,45 +13,31 @@
#include "app/editor/graphics/screen_editor.h" #include "app/editor/graphics/screen_editor.h"
#include "app/editor/message/message_editor.h" #include "app/editor/message/message_editor.h"
#include "app/editor/music/music_editor.h" #include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h" #include "app/editor/overworld/overworld_editor.h"
#include "app/editor/settings_editor.h"
#include "app/editor/sprite/sprite_editor.h" #include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/gfx_context.h" #include "app/editor/system/settings_editor.h"
#include "app/emu/emulator.h" #include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/rom.h" #include "app/rom.h"
#include "yaze_config.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
/** /**
* @class MasterEditor * @class EditorManager
* @brief The MasterEditor class represents the main editor for a Rom in the * @brief The EditorManager controls the main editor window and manages the
* Yaze application. * various editor classes.
* *
* This class inherits from SharedRom, GfxContext, and ExperimentFlags, and * The EditorManager class contains instances of various editor classes such as
* provides functionality for setting up the screen, updating the editor, and
* shutting down the editor. It also includes methods for drawing various menus
* and popups, saving the Rom, and managing editor-specific flags.
*
* The MasterEditor class contains instances of various editor classes such as
* AssemblyEditor, DungeonEditor, GraphicsEditor, MusicEditor, OverworldEditor, * AssemblyEditor, DungeonEditor, GraphicsEditor, MusicEditor, OverworldEditor,
* PaletteEditor, ScreenEditor, and SpriteEditor. The current_editor_ member * PaletteEditor, ScreenEditor, and SpriteEditor. The current_editor_ member
* variable points to the currently active editor in the tab view. * variable points to the currently active editor in the tab view.
* *
* @note This class assumes the presence of an SDL_Renderer object for rendering
* graphics.
*/ */
class MasterEditor : public SharedRom, class EditorManager : public SharedRom {
public context::GfxContext,
public core::ExperimentFlags {
public: public:
MasterEditor() { EditorManager() {
current_editor_ = &overworld_editor_; current_editor_ = &overworld_editor_;
active_editors_.push_back(&overworld_editor_); active_editors_.push_back(&overworld_editor_);
active_editors_.push_back(&dungeon_editor_); active_editors_.push_back(&dungeon_editor_);
@@ -67,37 +45,34 @@ class MasterEditor : public SharedRom,
active_editors_.push_back(&palette_editor_); active_editors_.push_back(&palette_editor_);
active_editors_.push_back(&sprite_editor_); active_editors_.push_back(&sprite_editor_);
active_editors_.push_back(&message_editor_); active_editors_.push_back(&message_editor_);
active_editors_.push_back(&screen_editor_);
std::stringstream ss;
ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "."
<< YAZE_VERSION_PATCH;
ss >> version_;
} }
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer, void Initialize(std::string filename = "");
std::string filename = "");
absl::Status Update(); absl::Status Update();
auto emulator() -> emu::Emulator& { return emulator_; } auto emulator() -> emu::Emulator & { return emulator_; }
auto quit() { return quit_; } auto quit() { return quit_; }
auto overworld_editor() -> OverworldEditor& { return overworld_editor_; }
private: private:
void ManageActiveEditors(); void ManageActiveEditors();
void ManageKeyboardShortcuts(); void ManageKeyboardShortcuts();
void OpenRomOrProject(const std::string& filename);
void DrawFileDialog();
void DrawStatusPopup(); void DrawStatusPopup();
void DrawAboutPopup(); void DrawAboutPopup();
void DrawInfoPopup(); void DrawInfoPopup();
void DrawYazeMenu(); void DrawYazeMenu();
void DrawFileMenu(); void DrawYazeMenuBar();
void DrawEditMenu();
void DrawViewMenu();
void DrawTestMenu();
void DrawProjectMenu();
void DrawHelpMenu();
void LoadRom(); void LoadRom();
void SaveRom(); void SaveRom();
void OpenRomOrProject(const std::string &filename);
absl::Status OpenProject(); absl::Status OpenProject();
bool quit_ = false; bool quit_ = false;
@@ -108,34 +83,30 @@ class MasterEditor : public SharedRom,
bool show_status_ = false; bool show_status_ = false;
bool rom_assets_loaded_ = false; bool rom_assets_loaded_ = false;
std::string version_ = "";
absl::Status status_; absl::Status status_;
absl::Status prev_status_;
std::shared_ptr<SDL_Renderer> sdl_renderer_;
emu::Emulator emulator_; emu::Emulator emulator_;
std::vector<Editor *> active_editors_;
Project current_project_; Project current_project_;
EditorContext editor_context_;
Editor *current_editor_ = nullptr;
AssemblyEditor assembly_editor_; AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_; DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_; GraphicsEditor graphics_editor_;
MusicEditor music_editor_; MusicEditor music_editor_;
OverworldEditor overworld_editor_; OverworldEditor overworld_editor_{*rom()};
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
ScreenEditor screen_editor_; ScreenEditor screen_editor_;
SpriteEditor sprite_editor_; SpriteEditor sprite_editor_;
SettingsEditor settings_editor_; SettingsEditor settings_editor_;
MessageEditor message_editor_; MessageEditor message_editor_;
MemoryEditorWithDiffChecker memory_editor_; MemoryEditorWithDiffChecker memory_editor_;
ImVector<int> active_tabs_;
std::vector<Editor*> active_editors_;
Editor* current_editor_ = nullptr;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H #endif // YAZE_APP_EDITOR_EDITOR_MANAGER_H

View File

@@ -1,25 +1,16 @@
#include "gfx_group_editor.h" #include "gfx_group_editor.h"
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/strings/str_cat.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h" #include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using ImGui::BeginChild; using ImGui::BeginChild;
@@ -27,7 +18,6 @@ using ImGui::BeginGroup;
using ImGui::BeginTabBar; using ImGui::BeginTabBar;
using ImGui::BeginTabItem; using ImGui::BeginTabItem;
using ImGui::BeginTable; using ImGui::BeginTable;
using ImGui::ColorButton;
using ImGui::EndChild; using ImGui::EndChild;
using ImGui::EndGroup; using ImGui::EndGroup;
using ImGui::EndTabBar; using ImGui::EndTabBar;
@@ -39,7 +29,6 @@ using ImGui::IsItemClicked;
using ImGui::PopID; using ImGui::PopID;
using ImGui::PushID; using ImGui::PushID;
using ImGui::SameLine; using ImGui::SameLine;
using ImGui::Selectable;
using ImGui::Separator; using ImGui::Separator;
using ImGui::SetNextItemWidth; using ImGui::SetNextItemWidth;
using ImGui::TableHeadersRow; using ImGui::TableHeadersRow;
@@ -123,7 +112,7 @@ void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
BeginGroup(); BeginGroup();
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i]; int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
auto sheet = rom()->bitmap_manager()[sheet_id]; auto sheet = rom()->gfx_sheets().at(sheet_id);
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04, gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 22); 0x20, true, false, 22);
} }
@@ -176,7 +165,7 @@ void GfxGroupEditor::DrawRoomsetViewer() {
BeginGroup(); BeginGroup();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i]; int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
auto sheet = rom()->bitmap_manager()[sheet_id]; auto sheet = rom()->gfx_sheets().at(sheet_id);
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04, gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 23); 0x20, true, false, 23);
} }
@@ -214,7 +203,7 @@ void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
BeginGroup(); BeginGroup();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i]; int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
auto sheet = rom()->bitmap_manager()[115 + sheet_id]; auto sheet = rom()->gfx_sheets().at(115 + sheet_id);
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04, gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 24); 0x20, true, false, 24);
} }
@@ -229,7 +218,7 @@ void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
if (palette.empty()) { if (palette.empty()) {
return; return;
} }
for (int n = 0; n < palette.size(); n++) { for (size_t n = 0; n < palette.size(); n++) {
PushID(n); PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y); if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
@@ -307,5 +296,4 @@ void GfxGroupEditor::DrawPaletteViewer() {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,24 +1,12 @@
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H #ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H #define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
/** /**
@@ -40,13 +28,7 @@ class GfxGroupEditor : public SharedRom {
selected_spriteset_ = spriteset; selected_spriteset_ = spriteset;
} }
void InitBlockset(gfx::Bitmap* tile16_blockset) {
tile16_blockset_bmp_ = tile16_blockset;
}
private: private:
int preview_palette_id_ = 0;
int last_sheet_id_ = 0;
uint8_t selected_blockset_ = 0; uint8_t selected_blockset_ = 0;
uint8_t selected_roomset_ = 0; uint8_t selected_roomset_ = 0;
uint8_t selected_spriteset_ = 0; uint8_t selected_spriteset_ = 0;
@@ -57,17 +39,8 @@ class GfxGroupEditor : public SharedRom {
gui::Canvas spriteset_canvas_; gui::Canvas spriteset_canvas_;
gfx::SnesPalette palette_; gfx::SnesPalette palette_;
gfx::PaletteGroup palette_group_;
gfx::Bitmap* tile16_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
gui::BitmapViewer gfx_group_viewer_;
zelda3::overworld::Overworld overworld_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H #endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H

View File

@@ -1,30 +1,34 @@
#include "graphics_editor.h" #include "graphics_editor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h" #include <filesystem>
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "app/core/platform/clipboard.h" #include "app/core/platform/clipboard.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/compression.h" #include "app/gfx/compression.h"
#include "app/gfx/scad_format.h" #include "app/gfx/scad_format.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h" #include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/modules/asset_browser.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using core::Renderer;
using gfx::kPaletteGroupAddressesKeys; using gfx::kPaletteGroupAddressesKeys;
using ImGui::Button; using ImGui::Button;
using ImGui::InputInt; using ImGui::InputInt;
@@ -32,70 +36,56 @@ using ImGui::InputText;
using ImGui::SameLine; using ImGui::SameLine;
using ImGui::TableNextColumn; using ImGui::TableNextColumn;
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
constexpr ImGuiTableFlags kGfxEditTableFlags = constexpr ImGuiTableFlags kGfxEditTableFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
ImGuiTableFlags_SizingFixedFit; ImGuiTableFlags_SizingFixedFit;
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton;
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
absl::Status GraphicsEditor::Update() { absl::Status GraphicsEditor::Update() {
TAB_BAR("##TabBar") if (ImGui::BeginTabBar("##TabBar")) {
status_ = UpdateGfxEdit(); status_ = UpdateGfxEdit();
TAB_ITEM("Sheet Browser") TAB_ITEM("Sheet Browser")
if (asset_browser_.Initialized == false) { if (asset_browser_.Initialized == false) {
asset_browser_.Initialize(rom()->mutable_bitmap_manager()); asset_browser_.Initialize(rom()->gfx_sheets());
}
asset_browser_.Draw(rom()->gfx_sheets());
END_TAB_ITEM()
status_ = UpdateScadView();
status_ = UpdateLinkGfxView();
ImGui::EndTabBar();
} }
asset_browser_.Draw(rom()->mutable_bitmap_manager());
END_TAB_ITEM()
status_ = UpdateScadView();
status_ = UpdateLinkGfxView();
END_TAB_BAR()
CLEAR_AND_RETURN_STATUS(status_) CLEAR_AND_RETURN_STATUS(status_)
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status GraphicsEditor::UpdateGfxEdit() { absl::Status GraphicsEditor::UpdateGfxEdit() {
TAB_ITEM("Sheet Editor") if (ImGui::BeginTabItem("Sheet Editor")) {
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) {
for (const auto& name :
{"Tilesheets", "Current Graphics", "Palette Controls"})
ImGui::TableSetupColumn(name);
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags, ImGui::TableHeadersRow();
ImVec2(0, 0))) {
for (const auto& name :
{"Tilesheets", "Current Graphics", "Palette Controls"})
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow(); NEXT_COLUMN();
status_ = UpdateGfxSheetList();
NEXT_COLUMN(); NEXT_COLUMN();
status_ = UpdateGfxSheetList(); if (rom()->is_loaded()) {
DrawGfxEditToolset();
status_ = UpdateGfxTabView();
}
NEXT_COLUMN(); NEXT_COLUMN();
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
DrawGfxEditToolset(); status_ = UpdatePaletteColumn();
status_ = UpdateGfxTabView(); }
} }
ImGui::EndTable();
NEXT_COLUMN(); ImGui::EndTabItem();
if (rom()->is_loaded()) {
status_ = UpdatePaletteColumn();
}
} }
ImGui::EndTable();
END_TAB_ITEM()
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -127,8 +117,8 @@ void GraphicsEditor::DrawGfxEditToolset() {
TableNextColumn(); TableNextColumn();
if (Button(ICON_MD_CONTENT_COPY)) { if (Button(ICON_MD_CONTENT_COPY)) {
std::vector<uint8_t> png_data = std::vector<uint8_t> png_data =
rom()->bitmap_manager().shared_bitmap(current_sheet_).GetPngData(); rom()->gfx_sheets().at(current_sheet_).GetPngData();
CopyImageToClipboard(png_data); core::CopyImageToClipboard(png_data);
} }
HOVER_HINT("Copy to Clipboard"); HOVER_HINT("Copy to Clipboard");
@@ -136,14 +126,14 @@ void GraphicsEditor::DrawGfxEditToolset() {
if (Button(ICON_MD_CONTENT_PASTE)) { if (Button(ICON_MD_CONTENT_PASTE)) {
std::vector<uint8_t> png_data; std::vector<uint8_t> png_data;
int width, height; int width, height;
GetImageFromClipboard(png_data, width, height); core::GetImageFromClipboard(png_data, width, height);
if (png_data.size() > 0) { if (png_data.size() > 0) {
rom() rom()
->mutable_bitmap_manager() ->mutable_gfx_sheets()
->mutable_bitmap(current_sheet_) ->at(current_sheet_)
->Create(width, height, 8, png_data); .Create(width, height, 8, png_data);
rom()->UpdateBitmap( Renderer::GetInstance().UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_)); &rom()->mutable_gfx_sheets()->at(current_sheet_));
} }
} }
HOVER_HINT("Paste from Clipboard"); HOVER_HINT("Paste from Clipboard");
@@ -163,7 +153,7 @@ void GraphicsEditor::DrawGfxEditToolset() {
} }
TableNextColumn(); TableNextColumn();
auto bitmap = rom()->bitmap_manager()[current_sheet_]; auto bitmap = rom()->gfx_sheets()[current_sheet_];
auto palette = bitmap.palette(); auto palette = bitmap.palette();
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
ImGui::SameLine(); ImGui::SameLine();
@@ -192,28 +182,28 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
static ImGuiSelectionBasicStorage selection; static ImGuiSelectionBasicStorage selection;
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags flags =
ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect( ImGuiMultiSelectIO* ms_io =
flags, selection.Size, rom()->bitmap_manager().size()); ImGui::BeginMultiSelect(flags, selection.Size, kNumGfxSheets);
selection.ApplyRequests(ms_io); selection.ApplyRequests(ms_io);
ImGuiListClipper clipper; ImGuiListClipper clipper;
clipper.Begin(rom()->bitmap_manager().size()); clipper.Begin(kNumGfxSheets);
if (ms_io->RangeSrcItem != -1) if (ms_io->RangeSrcItem != -1)
clipper.IncludeItemByIndex( clipper.IncludeItemByIndex(
(int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. (int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
for (auto& [key, value] : rom()->bitmap_manager()) { int key = 0;
for (auto& value : rom()->gfx_sheets()) {
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(), ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
ImVec2(0x100 + 1, 0x40 + 1), true, ImVec2(0x100 + 1, 0x40 + 1), true,
ImGuiWindowFlags_NoDecoration); ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleVar(); ImGui::PopStyleVar();
gui::Canvas graphics_bin_canvas_;
graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1)); graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1));
graphics_bin_canvas_.DrawContextMenu(); graphics_bin_canvas_.DrawContextMenu();
if (value.is_active()) { if (value.is_active()) {
auto texture = value.texture(); auto texture = value.texture();
graphics_bin_canvas_.draw_list()->AddImage( graphics_bin_canvas_.draw_list()->AddImage(
(void*)texture, (ImTextureID)(intptr_t)texture,
ImVec2(graphics_bin_canvas_.zero_point().x + 2, ImVec2(graphics_bin_canvas_.zero_point().x + 2,
graphics_bin_canvas_.zero_point().y + 2), graphics_bin_canvas_.zero_point().y + 2),
ImVec2(graphics_bin_canvas_.zero_point().x + ImVec2(graphics_bin_canvas_.zero_point().x +
@@ -240,6 +230,8 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
graphics_bin_canvas_.draw_list()->AddText( graphics_bin_canvas_.draw_list()->AddText(
text_pos, IM_COL32(125, 255, 125, 255), text_pos, IM_COL32(125, 255, 125, 255),
absl::StrFormat("%02X", key).c_str()); absl::StrFormat("%02X", key).c_str());
key++;
} }
graphics_bin_canvas_.DrawGrid(16.0f); graphics_bin_canvas_.DrawGrid(16.0f);
graphics_bin_canvas_.DrawOverlay(); graphics_bin_canvas_.DrawOverlay();
@@ -256,6 +248,10 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
absl::Status GraphicsEditor::UpdateGfxTabView() { absl::Status GraphicsEditor::UpdateGfxTabView() {
static int next_tab_id = 0; static int next_tab_id = 0;
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton;
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) { if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing | if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
@@ -285,18 +281,17 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar); ImGuiWindowFlags_AlwaysHorizontalScrollbar);
gfx::Bitmap& current_bitmap = gfx::Bitmap& current_bitmap = rom()->mutable_gfx_sheets()->at(sheet_id);
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
auto draw_tile_event = [&]() { auto draw_tile_event = [&]() {
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, &current_bitmap, current_sheet_canvas_.DrawTileOnBitmap(tile_size_, &current_bitmap,
current_color_); current_color_);
rom()->UpdateBitmap(&current_bitmap, true); Renderer::GetInstance().UpdateBitmap(&current_bitmap);
}; };
current_sheet_canvas_.UpdateColorPainter( current_sheet_canvas_.UpdateColorPainter(
rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event, rom()->mutable_gfx_sheets()->at(sheet_id), current_color_,
tile_size_, current_scale_); draw_tile_event, tile_size_, current_scale_);
ImGui::EndChild(); ImGui::EndChild();
ImGui::EndTabItem(); ImGui::EndTabItem();
@@ -328,7 +323,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
current_sheet_ = id; current_sheet_ = id;
// ImVec2(0x100, 0x40), // ImVec2(0x100, 0x40),
current_sheet_canvas_.UpdateColorPainter( current_sheet_canvas_.UpdateColorPainter(
rom()->bitmap_manager()[id], current_color_, rom()->mutable_gfx_sheets()->at(id), current_color_,
[&]() { [&]() {
}, },
@@ -365,11 +360,12 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
if (refresh_graphics_ && !open_sheets_.empty()) { if (refresh_graphics_ && !open_sheets_.empty()) {
RETURN_IF_ERROR( RETURN_IF_ERROR(
rom()->bitmap_manager()[current_sheet_].ApplyPaletteWithTransparent( rom()
palette, edit_palette_sub_index_)); ->mutable_gfx_sheets()
rom()->UpdateBitmap( ->data()[current_sheet_]
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_), .ApplyPaletteWithTransparent(palette, edit_palette_sub_index_));
true); Renderer::GetInstance().UpdateBitmap(
&rom()->mutable_gfx_sheets()->data()[current_sheet_]);
refresh_graphics_ = false; refresh_graphics_ = false;
} }
} }
@@ -391,9 +387,9 @@ absl::Status GraphicsEditor::UpdateLinkGfxView() {
link_canvas_.DrawGrid(16.0f); link_canvas_.DrawGrid(16.0f);
int i = 0; int i = 0;
for (auto [key, link_sheet] : *rom()->mutable_link_graphics()) { for (auto link_sheet : *rom()->mutable_link_graphics()) {
int x_offset = 0; int x_offset = 0;
int y_offset = core::kTilesheetHeight * i * 4; int y_offset = gfx::kTilesheetHeight * i * 4;
link_canvas_.DrawContextMenu(&link_sheet); link_canvas_.DrawContextMenu(&link_sheet);
link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4); link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4);
i++; i++;
@@ -435,6 +431,10 @@ absl::Status GraphicsEditor::UpdateScadView() {
ImGui::End(); ImGui::End();
} }
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags) BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags)
SETUP_COLUMN("File Import (BIN, CGX, ROM)") SETUP_COLUMN("File Import (BIN, CGX, ROM)")
SETUP_COLUMN("Palette (COL)") SETUP_COLUMN("Palette (COL)")
@@ -458,17 +458,18 @@ absl::Status GraphicsEditor::UpdateScadView() {
NEXT_COLUMN() NEXT_COLUMN()
if (super_donkey_) { if (super_donkey_) {
if (refresh_graphics_) { // TODO: Implement the Super Donkey 1 graphics decompression
for (int i = 0; i < graphics_bin_.size(); i++) { // if (refresh_graphics_) {
status_ = graphics_bin_[i].ApplyPalette( // for (int i = 0; i < kNumGfxSheets; i++) {
col_file_palette_group_[current_palette_index_]); // status_ = graphics_bin_[i].ApplyPalette(
rom()->UpdateBitmap(&graphics_bin_[i]); // col_file_palette_group_[current_palette_index_]);
} // Renderer::GetInstance().UpdateBitmap(&graphics_bin_[i]);
refresh_graphics_ = false; // }
} // refresh_graphics_ = false;
// }
// Load the full graphics space from `super_donkey_1.bin` // Load the full graphics space from `super_donkey_1.bin`
gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3, // gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
super_donkey_, graphics_bin_); // super_donkey_, graphics_bin_);
} else if (cgx_loaded_ && col_file_) { } else if (cgx_loaded_ && col_file_) {
// Load the CGX graphics // Load the CGX graphics
gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20, gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20,
@@ -485,6 +486,11 @@ absl::Status GraphicsEditor::UpdateScadView() {
} }
absl::Status GraphicsEditor::DrawToolset() { absl::Status GraphicsEditor::DrawToolset() {
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit, if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit,
ImVec2(0, 0))) { ImVec2(0, 0))) {
for (const auto& name : kGfxToolsetColumnNames) for (const auto& name : kGfxToolsetColumnNames)
@@ -510,22 +516,19 @@ absl::Status GraphicsEditor::DrawCgxImport() {
gui::TextWithSeparators("Cgx Import"); gui::TextWithSeparators("Cgx Import");
InputInt("BPP", &current_bpp_); InputInt("BPP", &current_bpp_);
InputText("##CGXFile", cgx_file_name_, sizeof(cgx_file_name_)); InputText("##CGXFile", &cgx_file_name_);
SameLine(); SameLine();
gui::FileDialogPipeline("ImportCgxKey", ".CGX,.cgx\0", "Open CGX", [this]() { if (ImGui::Button("Open CGX")) {
strncpy(cgx_file_path_, auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
ImGuiFileDialog::Instance()->GetFilePathName().c_str(), cgx_file_name_ = filename;
sizeof(cgx_file_path_)); cgx_file_path_ = std::filesystem::absolute(filename).string();
strncpy(cgx_file_name_,
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
sizeof(cgx_file_name_));
is_open_ = true; is_open_ = true;
cgx_loaded_ = true; cgx_loaded_ = true;
}); }
if (ImGui::Button("Copy CGX Path")) { if (ImGui::Button("Copy CGX Path")) {
ImGui::SetClipboardText(cgx_file_path_); ImGui::SetClipboardText(cgx_file_path_.c_str());
} }
if (ImGui::Button("Load CGX Data")) { if (ImGui::Button("Load CGX Data")) {
@@ -535,7 +538,7 @@ absl::Status GraphicsEditor::DrawCgxImport() {
cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_); cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_);
if (col_file_) { if (col_file_) {
cgx_bitmap_.ApplyPalette(decoded_col_); cgx_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&cgx_bitmap_); Renderer::GetInstance().RenderBitmap(&cgx_bitmap_);
} }
} }
@@ -543,19 +546,15 @@ absl::Status GraphicsEditor::DrawCgxImport() {
} }
absl::Status GraphicsEditor::DrawScrImport() { absl::Status GraphicsEditor::DrawScrImport() {
InputText("##ScrFile", scr_file_name_, sizeof(scr_file_name_)); InputText("##ScrFile", &scr_file_name_);
gui::FileDialogPipeline( if (ImGui::Button("Open SCR")) {
"ImportScrKey", ".SCR,.scr,.BAK\0", "Open SCR", [this]() { auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
strncpy(scr_file_path_, scr_file_name_ = filename;
ImGuiFileDialog::Instance()->GetFilePathName().c_str(), scr_file_path_ = std::filesystem::absolute(filename).string();
sizeof(scr_file_path_)); is_open_ = true;
strncpy(scr_file_name_, scr_loaded_ = true;
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(), }
sizeof(scr_file_name_));
is_open_ = true;
scr_loaded_ = true;
});
InputInt("SCR Mod", &scr_mod_value_); InputInt("SCR Mod", &scr_mod_value_);
@@ -570,7 +569,7 @@ absl::Status GraphicsEditor::DrawScrImport() {
scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_); scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_);
if (scr_loaded_) { if (scr_loaded_) {
scr_bitmap_.ApplyPalette(decoded_col_); scr_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&scr_bitmap_); Renderer::GetInstance().RenderBitmap(&scr_bitmap_);
} }
} }
@@ -579,38 +578,35 @@ absl::Status GraphicsEditor::DrawScrImport() {
absl::Status GraphicsEditor::DrawPaletteControls() { absl::Status GraphicsEditor::DrawPaletteControls() {
gui::TextWithSeparators("COL Import"); gui::TextWithSeparators("COL Import");
InputText("##ColFile", col_file_name_, sizeof(col_file_name_)); InputText("##ColFile", &col_file_name_);
SameLine(); SameLine();
gui::FileDialogPipeline( if (ImGui::Button("Open COL")) {
"ImportColKey", ".COL,.col,.BAK,.bak\0", "Open COL", [this]() { auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
strncpy(col_file_path_, col_file_name_ = filename;
ImGuiFileDialog::Instance()->GetFilePathName().c_str(), col_file_path_ = std::filesystem::absolute(filename).string();
sizeof(col_file_path_)); status_ = temp_rom_.LoadFromFile(col_file_path_,
strncpy(col_file_name_, /*z3_load=*/false);
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(), auto col_data_ = gfx::GetColFileData(temp_rom_.mutable_data());
sizeof(col_file_name_)); if (col_file_palette_group_.size() != 0) {
status_ = temp_rom_.LoadFromFile(col_file_path_, col_file_palette_group_.clear();
/*z3_load=*/false); }
auto col_data_ = gfx::GetColFileData(temp_rom_.data()); auto col_file_palette_group_status =
if (col_file_palette_group_.size() != 0) { gfx::CreatePaletteGroupFromColFile(col_data_);
col_file_palette_group_.Clear(); if (col_file_palette_group_status.ok()) {
} col_file_palette_group_ = col_file_palette_group_status.value();
auto col_file_palette_group_status = }
gfx::CreatePaletteGroupFromColFile(col_data_); col_file_palette_ = gfx::SnesPalette(col_data_);
if (col_file_palette_group_status.ok()) {
col_file_palette_group_ = col_file_palette_group_status.value();
}
col_file_palette_ = gfx::SnesPalette(col_data_);
// gigaleak dev format based code // gigaleak dev format based code
decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_); decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_);
col_file_ = true; col_file_ = true;
is_open_ = true; is_open_ = true;
}); }
HOVER_HINT(".COL, .BAK");
if (ImGui::Button("Copy Col Path")) { if (ImGui::Button("Copy Col Path")) {
ImGui::SetClipboardText(col_file_path_); ImGui::SetClipboardText(col_file_path_.c_str());
} }
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
@@ -631,17 +627,17 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
absl::Status GraphicsEditor::DrawObjImport() { absl::Status GraphicsEditor::DrawObjImport() {
gui::TextWithSeparators("OBJ Import"); gui::TextWithSeparators("OBJ Import");
InputText("##ObjFile", obj_file_path_, sizeof(obj_file_path_)); InputText("##ObjFile", &obj_file_path_);
SameLine(); SameLine();
gui::FileDialogPipeline( if (ImGui::Button("Open OBJ")) {
"ImportObjKey", ".obj,.OBJ,.bak,.BAK\0", "Open OBJ", [this]() { auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
strncpy(file_path_, obj_file_path_ = std::filesystem::absolute(filename).string();
ImGuiFileDialog::Instance()->GetFilePathName().c_str(), status_ = temp_rom_.LoadFromFile(obj_file_path_);
sizeof(file_path_)); is_open_ = true;
status_ = temp_rom_.LoadFromFile(file_path_); obj_loaded_ = true;
is_open_ = true; }
}); HOVER_HINT(".OBJ, .BAK");
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -649,23 +645,22 @@ absl::Status GraphicsEditor::DrawObjImport() {
absl::Status GraphicsEditor::DrawTilemapImport() { absl::Status GraphicsEditor::DrawTilemapImport() {
gui::TextWithSeparators("Tilemap Import"); gui::TextWithSeparators("Tilemap Import");
InputText("##TMapFile", tilemap_file_path_, sizeof(tilemap_file_path_)); InputText("##TMapFile", &tilemap_file_path_);
SameLine(); SameLine();
gui::FileDialogPipeline( if (ImGui::Button("Open Tilemap")) {
"ImportTilemapKey", ".DAT,.dat,.BIN,.bin,.hex,.HEX\0", "Open Tilemap", auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
[this]() { tilemap_file_path_ = std::filesystem::absolute(filename).string();
strncpy(tilemap_file_path_, status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
ImGuiFileDialog::Instance()->GetFilePathName().c_str(), status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
sizeof(tilemap_file_path_));
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
// Extract the high and low bytes from the file. // Extract the high and low bytes from the file.
auto decomp_sheet = gfx::lc_lz2::DecompressV2( auto decomp_sheet = gfx::lc_lz2::DecompressV2(tilemap_rom_.data(),
tilemap_rom_.data(), gfx::lc_lz2::kNintendoMode1); gfx::lc_lz2::kNintendoMode1);
tilemap_loaded_ = true; tilemap_loaded_ = true;
is_open_ = true; is_open_ = true;
}); }
HOVER_HINT(".DAT, .BIN, .HEX");
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -673,30 +668,30 @@ absl::Status GraphicsEditor::DrawTilemapImport() {
absl::Status GraphicsEditor::DrawFileImport() { absl::Status GraphicsEditor::DrawFileImport() {
gui::TextWithSeparators("BIN Import"); gui::TextWithSeparators("BIN Import");
InputText("##ROMFile", file_path_, sizeof(file_path_)); InputText("##ROMFile", &file_path_);
SameLine(); SameLine();
gui::FileDialogPipeline("ImportDlgKey", ".bin,.hex\0", "Open BIN", [this]() { if (ImGui::Button("Open BIN")) {
strncpy(file_path_, ImGuiFileDialog::Instance()->GetFilePathName().c_str(), auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
sizeof(file_path_)); file_path_ = filename;
status_ = temp_rom_.LoadFromFile(file_path_); status_ = temp_rom_.LoadFromFile(file_path_);
is_open_ = true; is_open_ = true;
}); }
HOVER_HINT(".BIN, .HEX");
if (Button("Copy File Path")) { if (Button("Copy File Path")) {
ImGui::SetClipboardText(file_path_); ImGui::SetClipboardText(file_path_.c_str());
} }
gui::InputHex("BIN Offset", &current_offset_); gui::InputHex("BIN Offset", &current_offset_);
gui::InputHex("BIN Size", &bin_size_); gui::InputHex("BIN Size", &bin_size_);
if (Button("Decompress BIN")) { if (Button("Decompress BIN")) {
if (strlen(file_path_) > 0) { if (file_path_.empty()) {
RETURN_IF_ERROR(DecompressImportData(bin_size_))
} else {
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"Please select a file before importing."); "Please select a file before decompressing.");
} }
RETURN_IF_ERROR(DecompressImportData(bin_size_))
} }
return absl::OkStatus(); return absl::OkStatus();
@@ -707,7 +702,8 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
if (Button("Paste From Clipboard")) { if (Button("Paste From Clipboard")) {
const char* text = ImGui::GetClipboardText(); const char* text = ImGui::GetClipboardText();
if (text) { if (text) {
const auto clipboard_data = Bytes(text, text + strlen(text)); const auto clipboard_data =
std::vector<uint8_t>(text, text + strlen(text));
ImGui::MemFree((void*)text); ImGui::MemFree((void*)text);
status_ = temp_rom_.LoadFromBytes(clipboard_data); status_ = temp_rom_.LoadFromBytes(clipboard_data);
is_open_ = true; is_open_ = true;
@@ -734,13 +730,12 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
absl::Status GraphicsEditor::DrawExperimentalFeatures() { absl::Status GraphicsEditor::DrawExperimentalFeatures() {
gui::TextWithSeparators("Experimental"); gui::TextWithSeparators("Experimental");
if (Button("Decompress Super Donkey Full")) { if (Button("Decompress Super Donkey Full")) {
if (strlen(file_path_) > 0) { if (file_path_.empty()) {
RETURN_IF_ERROR(DecompressSuperDonkey())
} else {
return absl::InvalidArgumentError( return absl::InvalidArgumentError(
"Please select `super_donkey_1.bin` before " "Please select `super_donkey_1.bin` before "
"importing."); "importing.");
} }
RETURN_IF_ERROR(DecompressSuperDonkey())
} }
ImGui::SetItemTooltip( ImGui::SetItemTooltip(
"Requires `super_donkey_1.bin` to be imported under the " "Requires `super_donkey_1.bin` to be imported under the "
@@ -752,7 +747,8 @@ absl::Status GraphicsEditor::DrawMemoryEditor() {
std::string title = "Memory Editor"; std::string title = "Memory Editor";
if (is_open_) { if (is_open_) {
static MemoryEditor mem_edit; static MemoryEditor mem_edit;
mem_edit.DrawWindow(title.c_str(), temp_rom_.data(), temp_rom_.size()); mem_edit.DrawWindow(title.c_str(), temp_rom_.mutable_data(),
temp_rom_.size());
} }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -762,7 +758,7 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
temp_rom_.data(), current_offset_, size)) temp_rom_.data(), current_offset_, size))
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3); auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth, bin_bitmap_.Create(gfx::kTilesheetWidth, 0x2000, gfx::kTilesheetDepth,
converted_sheet); converted_sheet);
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
@@ -775,7 +771,7 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
} }
} }
rom()->RenderBitmap(&bin_bitmap_); Renderer::GetInstance().RenderBitmap(&bin_bitmap_);
gfx_loaded_ = true; gfx_loaded_ = true;
return absl::OkStatus(); return absl::OkStatus();
@@ -790,11 +786,10 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto decompressed_data, auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000)) gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3); auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
graphics_bin_[i] = gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight, gfx::kTilesheetDepth, converted_sheet);
core::kTilesheetDepth, converted_sheet);
if (col_file_) { if (col_file_) {
status_ = graphics_bin_[i].ApplyPalette( status_ = gfx_sheets_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]); col_file_palette_group_[current_palette_index_]);
} else { } else {
// ROM palette // ROM palette
@@ -802,10 +797,10 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto palette_group = rom()->palette_group().get_group( auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]); kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_); z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_); status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
} }
rom()->RenderBitmap(&graphics_bin_[i]); Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
i++; i++;
} }
@@ -816,21 +811,20 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto decompressed_data, auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000)) gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3); auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
graphics_bin_[i] = gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight, gfx::kTilesheetDepth, converted_sheet);
core::kTilesheetDepth, converted_sheet);
if (col_file_) { if (col_file_) {
status_ = graphics_bin_[i].ApplyPalette( status_ = gfx_sheets_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]); col_file_palette_group_[current_palette_index_]);
} else { } else {
// ROM palette // ROM palette
auto palette_group = rom()->palette_group().get_group( auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]); kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_); z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_); status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
} }
rom()->RenderBitmap(&graphics_bin_[i]); Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
i++; i++;
} }
super_donkey_ = true; super_donkey_ = true;
@@ -840,5 +834,4 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,25 +1,21 @@
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H #ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H #define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#include "ImGuiFileDialog/ImGuiFileDialog.h" #include <stack>
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "app/editor/editor.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/input.h" #include "app/gui/modules/asset_browser.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
// "99973","A3D80", // "99973","A3D80",
@@ -146,16 +142,16 @@ class GraphicsEditor : public SharedRom, public Editor {
bool obj_loaded_ = false; bool obj_loaded_ = false;
bool tilemap_loaded_ = false; bool tilemap_loaded_ = false;
char file_path_[256] = ""; std::string file_path_ = "";
char col_file_path_[256] = ""; std::string col_file_path_ = "";
char col_file_name_[256] = ""; std::string col_file_name_ = "";
char cgx_file_path_[256] = ""; std::string cgx_file_path_ = "";
char cgx_file_name_[256] = ""; std::string cgx_file_name_ = "";
char scr_file_path_[256] = ""; std::string scr_file_path_ = "";
char scr_file_name_[256] = ""; std::string scr_file_name_ = "";
char obj_file_path_[256] = ""; std::string obj_file_path_ = "";
char tilemap_file_path_[256] = ""; std::string tilemap_file_path_ = "";
char tilemap_file_name_[256] = ""; std::string tilemap_file_name_ = "";
gui::GfxSheetAssetBrowser asset_browser_; gui::GfxSheetAssetBrowser asset_browser_;
@@ -163,12 +159,12 @@ class GraphicsEditor : public SharedRom, public Editor {
Rom temp_rom_; Rom temp_rom_;
Rom tilemap_rom_; Rom tilemap_rom_;
zelda3::overworld::Overworld overworld_; zelda3::Overworld overworld_;
MemoryEditor cgx_memory_editor_; MemoryEditor cgx_memory_editor_;
MemoryEditor col_memory_editor_; MemoryEditor col_memory_editor_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
Bytes import_data_; std::vector<uint8_t> import_data_;
Bytes graphics_buffer_; std::vector<uint8_t> graphics_buffer_;
std::vector<uint8_t> decoded_cgx_; std::vector<uint8_t> decoded_cgx_;
std::vector<uint8_t> cgx_data_; std::vector<uint8_t> cgx_data_;
std::vector<uint8_t> extra_cgx_data_; std::vector<uint8_t> extra_cgx_data_;
@@ -179,9 +175,8 @@ class GraphicsEditor : public SharedRom, public Editor {
gfx::Bitmap scr_bitmap_; gfx::Bitmap scr_bitmap_;
gfx::Bitmap bin_bitmap_; gfx::Bitmap bin_bitmap_;
gfx::Bitmap link_full_sheet_; gfx::Bitmap link_full_sheet_;
gfx::BitmapTable graphics_bin_; std::array<gfx::Bitmap, kNumGfxSheets> gfx_sheets_;
gfx::BitmapTable clipboard_graphics_bin_;
gfx::BitmapTable link_graphics_;
gfx::PaletteGroup col_file_palette_group_; gfx::PaletteGroup col_file_palette_group_;
gfx::SnesPalette z3_rom_palette_; gfx::SnesPalette z3_rom_palette_;
gfx::SnesPalette col_file_palette_; gfx::SnesPalette col_file_palette_;
@@ -189,17 +184,17 @@ class GraphicsEditor : public SharedRom, public Editor {
gui::Canvas import_canvas_; gui::Canvas import_canvas_;
gui::Canvas scr_canvas_; gui::Canvas scr_canvas_;
gui::Canvas super_donkey_canvas_; gui::Canvas super_donkey_canvas_;
gui::Canvas graphics_bin_canvas_;
gui::Canvas current_sheet_canvas_{"CurrentSheetCanvas", ImVec2(0x80, 0x20), gui::Canvas current_sheet_canvas_{"CurrentSheetCanvas", ImVec2(0x80, 0x20),
gui::CanvasGridSize::k8x8}; gui::CanvasGridSize::k8x8};
gui::Canvas link_canvas_{ gui::Canvas link_canvas_{
"LinkCanvas", "LinkCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4), ImVec2(gfx::kTilesheetWidth * 4, gfx::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k16x16}; gui::CanvasGridSize::k16x16};
absl::Status status_; absl::Status status_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H #endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H

View File

@@ -1,16 +1,13 @@
#include "palette_editor.h" #include "palette_editor.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/color.h"
#include "app/gui/style.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "absl/status/status.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using ImGui::AcceptDragDropPayload; using ImGui::AcceptDragDropPayload;
@@ -86,6 +83,93 @@ static inline float color_saturate(float f) {
0.5f)) // Saturated, always output 0..255 0.5f)) // Saturated, always output 0..255
} // namespace } // namespace
absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
static ImVec4 current_palette[256] = {};
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;
if (loaded && !init) {
for (int n = 0; n < palette.size(); n++) {
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
current_palette[n].x = color.rgb().x / 255;
current_palette[n].y = color.rgb().y / 255;
current_palette[n].z = color.rgb().z / 255;
current_palette[n].w = 255; // Alpha
}
init = true;
}
static ImVec4 backup_color;
bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
SameLine(0, GetStyle().ItemInnerSpacing.x);
open_popup |= Button("Palette");
if (open_popup) {
OpenPopup("mypicker");
backup_color = color;
}
if (BeginPopup("mypicker")) {
TEXT_WITH_SEPARATOR("Current Overworld Palette");
ColorPicker4("##picker", (float*)&color,
misc_flags | ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview);
SameLine();
BeginGroup(); // Lock X position
Text("Current ==>");
SameLine();
Text("Previous");
if (Button("Update Map Palette")) {
}
ColorButton(
"##current", color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40));
SameLine();
if (ColorButton(
"##previous", backup_color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40)))
color = backup_color;
// List of Colors in Overworld Palette
Separator();
Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
if (ColorButton("##palette", current_palette[n], kPalButtonFlags2,
ImVec2(20, 20)))
color = ImVec4(current_palette[n].x, current_palette[n].y,
current_palette[n].z, color.w); // Preserve alpha!
if (BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
EndDragDropTarget();
}
PopID();
}
EndGroup();
EndPopup();
}
return absl::OkStatus();
}
absl::Status PaletteEditor::Update() { absl::Status PaletteEditor::Update() {
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
// Initialize the labels // Initialize the labels
@@ -348,7 +432,7 @@ absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
// SNES Format // SNES Format
CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X", CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
ConvertRGBtoSNES(ImVec4(col[0], col[1], col[2], 1.0f))); ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
if (Selectable(buf)) SetClipboardText(buf); if (Selectable(buf)) SetClipboardText(buf);
EndPopup(); EndPopup();
@@ -358,84 +442,6 @@ absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
return absl::OkStatus(); 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;
if (loaded && !init) {
status_ = InitializeSavedPalette(palette);
init = true;
}
static ImVec4 backup_color;
bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
SameLine(0, GetStyle().ItemInnerSpacing.x);
open_popup |= Button("Palette");
if (open_popup) {
OpenPopup("mypicker");
backup_color = color;
}
if (BeginPopup("mypicker")) {
TEXT_WITH_SEPARATOR("Current Overworld Palette");
ColorPicker4("##picker", (float*)&color,
misc_flags | ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview);
SameLine();
BeginGroup(); // Lock X position
Text("Current ==>");
SameLine();
Text("Previous");
if (Button("Update Map Palette")) {
}
ColorButton(
"##current", color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40));
SameLine();
if (ColorButton(
"##previous", backup_color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40)))
color = backup_color;
// List of Colors in Overworld Palette
Separator();
Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
if (ColorButton("##palette", saved_palette_[n], kPalButtonFlags2,
ImVec2(20, 20)))
color = ImVec4(saved_palette_[n].x, saved_palette_[n].y,
saved_palette_[n].z, color.w); // Preserve alpha!
if (BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
EndDragDropTarget();
}
PopID();
}
EndGroup();
EndPopup();
}
}
absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette, absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette,
int index) { int index) {
if (index >= palette.size()) { if (index >= palette.size()) {
@@ -465,5 +471,4 @@ absl::Status PaletteEditor::ResetColorToOriginal(
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,18 +1,19 @@
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H #ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H #define YAZE_APP_EDITOR_PALETTE_EDITOR_H
#include "imgui/imgui.h" #include <deque>
#include <string>
#include <vector>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/editor/graphics/gfx_group_editor.h" #include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/utils/editor.h" #include "app/editor/editor.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h" #include "app/gfx/snes_color.h"
#include "app/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
namespace palette_internal { namespace palette_internal {
@@ -26,56 +27,51 @@ struct PaletteChange {
class PaletteEditorHistory { class PaletteEditorHistory {
public: public:
// Record a change in the palette editor void RecordChange(const std::string& group_name, size_t palette_index,
void RecordChange(const std::string& groupName, size_t paletteIndex, size_t color_index, const gfx::SnesColor& original_color,
size_t colorIndex, const gfx::SnesColor& originalColor, const gfx::SnesColor& new_color) {
const gfx::SnesColor& newColor) { if (recent_changes_.size() >= kMaxHistorySize) {
// Check size and remove the oldest if necessary recent_changes_.pop_front();
if (recentChanges.size() >= maxHistorySize) {
recentChanges.pop_front();
} }
// Push the new change recent_changes_.push_back(
recentChanges.push_back( {group_name, palette_index, color_index, original_color, new_color});
{groupName, paletteIndex, colorIndex, originalColor, newColor});
} }
// Get recent changes for display in the palette editor gfx::SnesColor RestoreOriginalColor(const std::string& group_name,
const std::deque<PaletteChange>& GetRecentChanges() const { size_t palette_index,
return recentChanges; size_t color_index) const {
} for (const auto& change : recent_changes_) {
if (change.group_name == group_name &&
// Restore the original color change.palette_index == palette_index &&
gfx::SnesColor RestoreOriginalColor(const std::string& groupName, change.color_index == color_index) {
size_t paletteIndex,
size_t colorIndex) const {
for (const auto& change : recentChanges) {
if (change.group_name == groupName &&
change.palette_index == paletteIndex &&
change.color_index == colorIndex) {
return change.original_color; return change.original_color;
} }
} }
// Handle error or return default (this is just an example,
// handle as appropriate for your application)
return gfx::SnesColor(); return gfx::SnesColor();
} }
auto size() const { return recentChanges.size(); } auto size() const { return recent_changes_.size(); }
gfx::SnesColor& GetModifiedColor(size_t index) { gfx::SnesColor& GetModifiedColor(size_t index) {
return recentChanges[index].new_color; return recent_changes_[index].new_color;
} }
gfx::SnesColor& GetOriginalColor(size_t index) { gfx::SnesColor& GetOriginalColor(size_t index) {
return recentChanges[index].original_color; return recent_changes_[index].original_color;
}
const std::deque<PaletteChange>& GetRecentChanges() const {
return recent_changes_;
} }
private: private:
std::deque<PaletteChange> recentChanges; std::deque<PaletteChange> recent_changes_;
static const size_t maxHistorySize = 50; // or any other number you deem fit static const size_t kMaxHistorySize = 50;
}; };
} // namespace palette_internal } // namespace palette_internal
absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded);
/** /**
* @class PaletteEditor * @class PaletteEditor
* @brief Allows the user to view and edit in game palettes. * @brief Allows the user to view and edit in game palettes.
@@ -101,7 +97,6 @@ class PaletteEditor : public SharedRom, public Editor {
absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index); absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index);
absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index, absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index,
const gfx::SnesPalette& originalPalette); const gfx::SnesPalette& originalPalette);
void DisplayPalette(gfx::SnesPalette& palette, bool loaded);
absl::Status DrawPaletteGroup(int category, bool right_side = false); absl::Status DrawPaletteGroup(int category, bool right_side = false);
void DrawCustomPalette(); void DrawCustomPalette();
@@ -110,16 +105,6 @@ class PaletteEditor : public SharedRom, public Editor {
private: private:
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n); absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
absl::Status InitializeSavedPalette(const gfx::SnesPalette& palette) {
for (int n = 0; n < palette.size(); n++) {
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
saved_palette_[n].x = color.rgb().x / 255;
saved_palette_[n].y = color.rgb().y / 255;
saved_palette_[n].z = color.rgb().z / 255;
saved_palette_[n].w = 255; // Alpha
}
return absl::OkStatus();
}
absl::Status status_; absl::Status status_;
gfx::SnesColor current_color_; gfx::SnesColor current_color_;
@@ -134,7 +119,6 @@ class PaletteEditor : public SharedRom, public Editor {
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

View File

@@ -1,44 +1,45 @@
#include "screen_editor.h" #include "screen_editor.h"
#include "imgui/imgui.h"
#include <algorithm>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <sstream>
#include <string> #include <string>
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "app/core/common.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h" #include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/zelda3/dungeon/room.h" #include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
absl::Status ScreenEditor::Update() { using core::Renderer;
TAB_BAR("##TabBar")
TAB_ITEM("Dungeon Maps")
if (rom()->is_loaded()) {
DrawDungeonMapsEditor();
}
END_TAB_ITEM()
DrawInventoryMenuEditor();
DrawOverworldMapEditor();
DrawTitleScreenEditor();
DrawNamingScreenEditor();
END_TAB_BAR()
return absl::OkStatus(); constexpr uint32_t kRedPen = 0xFF0000FF;
absl::Status ScreenEditor::Update() {
if (ImGui::BeginTabBar("##ScreenEditorTabBar")) {
if (ImGui::BeginTabItem("Dungeon Maps")) {
if (rom()->is_loaded()) {
DrawDungeonMapsEditor();
}
ImGui::EndTabItem();
}
DrawInventoryMenuEditor();
DrawOverworldMapEditor();
DrawTitleScreenEditor();
DrawNamingScreenEditor();
ImGui::EndTabBar();
}
return status_;
} }
void ScreenEditor::DrawInventoryMenuEditor() { void ScreenEditor::DrawInventoryMenuEditor() {
@@ -121,16 +122,16 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
int ptr, int ptr,
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2))); rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
ASSIGN_OR_RETURN( ASSIGN_OR_RETURN(
int ptrGFX, int ptr_gfx,
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2))); rom()->ReadWord(zelda3::screen::kDungeonMapGfxPtr + (d * 2)));
ptr |= 0x0A0000; // Add bank to the short ptr ptr |= 0x0A0000; // Add bank to the short ptr
ptrGFX |= 0x0A0000; // Add bank to the short ptr ptr_gfx |= 0x0A0000; // Add bank to the short ptr
int pcPtr = core::SnesToPc(ptr); // Contains data for the next 25 rooms int pc_ptr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
int pcPtrGFX = int pc_ptr_gfx =
core::SnesToPc(ptrGFX); // Contains data for the next 25 rooms core::SnesToPc(ptr_gfx); // Contains data for the next 25 rooms
ASSIGN_OR_RETURN( ASSIGN_OR_RETURN(
ushort bossRoomD, ushort boss_room_d,
rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2))); rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2)));
ASSIGN_OR_RETURN( ASSIGN_OR_RETURN(
@@ -157,17 +158,16 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
// for each room on the floor // for each room on the floor
for (int j = 0; j < 25; j++) { for (int j = 0; j < 25; j++) {
// rdata[j] = 0x0F;
gdata[j] = 0xFF; gdata[j] = 0xFF;
rdata[j] = rom()->data()[pcPtr + j + (i * 25)]; // Set the rooms rdata[j] = rom()->data()[pc_ptr + j + (i * 25)]; // Set the rooms
if (rdata[j] == 0x0F) { if (rdata[j] == 0x0F) {
gdata[j] = 0xFF; gdata[j] = 0xFF;
} else { } else {
gdata[j] = rom()->data()[pcPtrGFX++]; gdata[j] = rom()->data()[pc_ptr_gfx++];
} }
std::string label = core::UppercaseHexByte(rdata[j]); std::string label = core::HexByte(rdata[j]);
dungeon_map_labels_[d][i][j] = label; dungeon_map_labels_[d][i][j] = label;
} }
@@ -175,7 +175,7 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
current_floor_rooms_d.push_back(rdata); // Add new floor data current_floor_rooms_d.push_back(rdata); // Add new floor data
} }
dungeon_maps_.emplace_back(bossRoomD, nbr_floor_d, nbr_basement_d, dungeon_maps_.emplace_back(boss_room_d, nbr_floor_d, nbr_basement_d,
current_floor_rooms_d, current_floor_gfx_d); current_floor_rooms_d, current_floor_gfx_d);
} }
@@ -185,23 +185,19 @@ absl::Status ScreenEditor::LoadDungeonMaps() {
absl::Status ScreenEditor::SaveDungeonMaps() { absl::Status ScreenEditor::SaveDungeonMaps() {
for (int d = 0; d < 14; d++) { for (int d = 0; d < 14; d++) {
int ptr = zelda3::screen::kDungeonMapRoomsPtr + (d * 2); int ptr = zelda3::screen::kDungeonMapRoomsPtr + (d * 2);
int ptrGFX = zelda3::screen::kDungeonMapGfxPtr + (d * 2); int ptr_gfx = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
int pcPtr = core::SnesToPc(ptr); int pc_ptr = core::SnesToPc(ptr);
int pcPtrGFX = core::SnesToPc(ptrGFX); int pc_ptr_gfx = core::SnesToPc(ptr_gfx);
const int nbr_floors = dungeon_maps_[d].nbr_of_floor; const int nbr_floors = dungeon_maps_[d].nbr_of_floor;
const int nbr_basements = dungeon_maps_[d].nbr_of_basement; const int nbr_basements = dungeon_maps_[d].nbr_of_basement;
for (int i = 0; i < nbr_floors + nbr_basements; i++) { for (int i = 0; i < nbr_floors + nbr_basements; i++) {
for (int j = 0; j < 25; j++) { for (int j = 0; j < 25; j++) {
// rom()->data()[pcPtr + j + (i * 25)] = RETURN_IF_ERROR(rom()->WriteByte(pc_ptr + j + (i * 25),
// dungeon_maps_[d].floor_rooms[i][j];
// rom()->data()[pcPtrGFX++] = dungeon_maps_[d].floor_gfx[i][j];
RETURN_IF_ERROR(rom()->WriteByte(ptr + j + (i * 25),
dungeon_maps_[d].floor_rooms[i][j])); dungeon_maps_[d].floor_rooms[i][j]));
RETURN_IF_ERROR(rom()->WriteByte(ptrGFX + j + (i * 25), RETURN_IF_ERROR(rom()->WriteByte(pc_ptr_gfx + j + (i * 25),
dungeon_maps_[d].floor_gfx[i][j])); dungeon_maps_[d].floor_gfx[i][j]));
pcPtrGFX++; pc_ptr_gfx++;
} }
} }
} }
@@ -209,7 +205,8 @@ absl::Status ScreenEditor::SaveDungeonMaps() {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ScreenEditor::LoadDungeonMapTile16() { absl::Status ScreenEditor::LoadDungeonMapTile16(
const std::vector<uint8_t>& gfx_data, bool bin_mode) {
tile16_sheet_.Init(256, 192, gfx::TileType::Tile16); tile16_sheet_.Init(256, 192, gfx::TileType::Tile16);
for (int i = 0; i < 186; i++) { for (int i = 0; i < 186; i++) {
@@ -230,69 +227,99 @@ absl::Status ScreenEditor::LoadDungeonMapTile16() {
ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8))); ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8)));
gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4); int sheet_offset = 212;
if (bin_mode) {
sheet_offset = 0;
}
tile16_sheet_.ComposeTile16(gfx_data, t1, t2, t3, t4, sheet_offset);
} }
RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette( RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette(
*rom()->mutable_dungeon_palette(3))); *rom()->mutable_dungeon_palette(3)));
rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get()); Renderer::GetInstance().RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) { for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
if (tile16_individual_.count(i) == 0) { auto tile = tile16_sheet_.GetTile16(i);
auto tile = tile16_sheet_.GetTile16(i); tile16_individual_[i] = tile;
tile16_individual_[i] = tile; RETURN_IF_ERROR(
rom()->RenderBitmap(&tile16_individual_[i]); tile16_individual_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3)));
} Renderer::GetInstance().RenderBitmap(&tile16_individual_[i]);
} }
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ScreenEditor::SaveDungeonMapTile16() {
for (int i = 0; i < 186; i++) {
int addr = zelda3::screen::kDungeonMapTile16;
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
addr = zelda3::screen::kDungeonMapTile16Expanded;
}
gfx::TileInfo t1 = tile16_sheet_.tile_info()[i].tiles[0];
gfx::TileInfo t2 = tile16_sheet_.tile_info()[i].tiles[1];
gfx::TileInfo t3 = tile16_sheet_.tile_info()[i].tiles[2];
gfx::TileInfo t4 = tile16_sheet_.tile_info()[i].tiles[3];
auto tl = gfx::TileInfoToWord(t1);
RETURN_IF_ERROR(rom()->WriteWord(addr + (i * 8), tl));
auto tr = gfx::TileInfoToWord(t2);
RETURN_IF_ERROR(rom()->WriteWord(addr + 2 + (i * 8), tr));
auto bl = gfx::TileInfoToWord(t3);
RETURN_IF_ERROR(rom()->WriteWord(addr + 4 + (i * 8), bl));
auto br = gfx::TileInfoToWord(t4);
RETURN_IF_ERROR(rom()->WriteWord(addr + 6 + (i * 8), br));
}
return absl::OkStatus();
}
void ScreenEditor::DrawDungeonMapsTabs() { void ScreenEditor::DrawDungeonMapsTabs() {
auto current_dungeon = dungeon_maps_[selected_dungeon]; auto& current_dungeon = dungeon_maps_[selected_dungeon];
if (ImGui::BeginTabBar("##DungeonMapTabs")) { if (ImGui::BeginTabBar("##DungeonMapTabs")) {
auto nbr_floors = auto nbr_floors =
current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement; current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
for (int i = 0; i < nbr_floors; i++) { for (int i = 0; i < nbr_floors; i++) {
std::string tab_name = absl::StrFormat("Floor %d", i + 1); int basement_num = current_dungeon.nbr_of_basement - i;
if (i >= current_dungeon.nbr_of_floor) { std::string tab_name = absl::StrFormat("Basement %d", basement_num);
tab_name = absl::StrFormat("Basement %d", if (i >= current_dungeon.nbr_of_basement) {
i - current_dungeon.nbr_of_floor + 1); tab_name = absl::StrFormat("Floor %d",
i - current_dungeon.nbr_of_basement + 1);
} }
if (ImGui::BeginTabItem(tab_name.c_str())) { if (ImGui::BeginTabItem(tab_name.c_str())) {
floor_number = i; floor_number = i;
// screen_canvas_.LoadCustomLabels(dungeon_map_labels_[selected_dungeon]);
// screen_canvas_.set_current_labels(floor_number);
screen_canvas_.DrawBackground(ImVec2(325, 325)); screen_canvas_.DrawBackground(ImVec2(325, 325));
screen_canvas_.DrawTileSelector(64.f); screen_canvas_.DrawTileSelector(64.f);
auto boss_room = current_dungeon.boss_room; auto boss_room = current_dungeon.boss_room;
for (int j = 0; j < 25; j++) { for (int j = 0; j < 25; j++) {
if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) { if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
int tile16_id = current_dungeon.floor_rooms[floor_number][j]; int tile16_id = current_dungeon.floor_gfx[floor_number][j];
int tile_x = (tile16_id % 16) * 16;
int tile_y = (tile16_id / 16) * 16;
int posX = ((j % 5) * 32); int posX = ((j % 5) * 32);
int posY = ((j / 5) * 32); int posY = ((j / 5) * 32);
if (tile16_individual_.count(tile16_id) == 0) { if (tile16_individual_.count(tile16_id) == 0) {
auto tile = tile16_sheet_.GetTile16(tile16_id); tile16_individual_[tile16_id] =
std::cout << "Tile16: " << tile16_id << std::endl; tile16_sheet_.GetTile16(tile16_id);
rom()->RenderBitmap(&tile); Renderer::GetInstance().RenderBitmap(
tile16_individual_[tile16_id] = tile; &tile16_individual_[tile16_id]);
} }
screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2), screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2),
(posY * 2), 4.0f); (posY * 2), 4.0f);
if (current_dungeon.floor_rooms[floor_number][j] == boss_room) { if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64,
64, core::kRedPen); 64, kRedPen);
} }
std::string label = std::string label =
dungeon_map_labels_[selected_dungeon][floor_number][j]; dungeon_map_labels_[selected_dungeon][floor_number][j];
screen_canvas_.DrawText(label, (posX * 2), (posY * 2)); screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
std::string gfx_id = core::HexByte(tile16_id);
screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
} }
} }
@@ -316,31 +343,93 @@ void ScreenEditor::DrawDungeonMapsTabs() {
gui::InputHexWord("Boss Room", &current_dungeon.boss_room); gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
if (ImGui::Button("Copy Floor", ImVec2(100, 0))) { const ImVec2 button_size = ImVec2(130, 0);
// Add Floor Button
if (ImGui::Button("Add Floor", button_size) &&
current_dungeon.nbr_of_floor < 8) {
current_dungeon.nbr_of_floor++;
dungeon_map_labels_[selected_dungeon].emplace_back();
}
ImGui::SameLine();
if (ImGui::Button("Remove Floor", button_size) &&
current_dungeon.nbr_of_floor > 0) {
current_dungeon.nbr_of_floor--;
dungeon_map_labels_[selected_dungeon].pop_back();
}
// Add Basement Button
if (ImGui::Button("Add Basement", button_size) &&
current_dungeon.nbr_of_basement < 8) {
current_dungeon.nbr_of_basement++;
dungeon_map_labels_[selected_dungeon].emplace_back();
}
ImGui::SameLine();
if (ImGui::Button("Remove Basement", button_size) &&
current_dungeon.nbr_of_basement > 0) {
current_dungeon.nbr_of_basement--;
dungeon_map_labels_[selected_dungeon].pop_back();
}
if (ImGui::Button("Copy Floor", button_size)) {
copy_button_pressed = true; copy_button_pressed = true;
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Paste Floor", ImVec2(100, 0))) { if (ImGui::Button("Paste Floor", button_size)) {
paste_button_pressed = true; paste_button_pressed = true;
} }
} }
void ScreenEditor::DrawDungeonMapsEditor() { void ScreenEditor::DrawDungeonMapsEditor() {
if (!dungeon_maps_loaded_) { if (!dungeon_maps_loaded_) {
if (LoadDungeonMaps().ok()) { if (!LoadDungeonMaps().ok()) {
if (LoadDungeonMapTile16().ok()) {
auto bitmap_manager = rom()->mutable_bitmap_manager();
sheets_.emplace(0, *bitmap_manager->mutable_bitmap(212));
sheets_.emplace(1, *bitmap_manager->mutable_bitmap(213));
sheets_.emplace(2, *bitmap_manager->mutable_bitmap(214));
sheets_.emplace(3, *bitmap_manager->mutable_bitmap(215));
dungeon_maps_loaded_ = true;
} else {
ImGui::Text("Failed to load dungeon map tile16");
}
} else {
ImGui::Text("Failed to load dungeon maps"); ImGui::Text("Failed to load dungeon maps");
} }
if (LoadDungeonMapTile16(rom()->graphics_buffer()).ok()) {
// TODO: Load roomset gfx based on dungeon ID
sheets_.emplace(0, rom()->gfx_sheets()[212]);
sheets_.emplace(1, rom()->gfx_sheets()[213]);
sheets_.emplace(2, rom()->gfx_sheets()[214]);
sheets_.emplace(3, rom()->gfx_sheets()[215]);
int current_tile8 = 0;
int tile_data_offset = 0;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 32; j++) {
std::vector<uint8_t> tile_data(64, 0); // 8x8 tile (64 bytes
int tile_index = current_tile8 + j;
int x = (j % 8) * 8;
int y = (j / 8) * 8;
sheets_[i].Get8x8Tile(tile_index, 0, 0, tile_data, tile_data_offset);
tile8_individual_.emplace_back(gfx::Bitmap(8, 8, 4, tile_data));
RETURN_VOID_IF_ERROR(tile8_individual_.back().ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(&tile8_individual_.back());
}
tile_data_offset = 0;
}
dungeon_maps_loaded_ = true;
} else {
ImGui::Text("Failed to load dungeon map tile16");
}
}
if (ImGui::BeginTable("##DungeonMapToolset", 2,
ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Draw Mode");
ImGui::TableSetupColumn("Edit Mode");
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_DRAW)) {
current_mode_ = EditingMode::DRAW;
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_EDIT)) {
current_mode_ = EditingMode::EDIT;
}
ImGui::EndTable();
} }
static std::vector<std::string> dungeon_names = { static std::vector<std::string> dungeon_names = {
@@ -350,7 +439,10 @@ void ScreenEditor::DrawDungeonMapsEditor() {
"Thieves' Town", "Ice Palace", "Misery Mire", "Thieves' Town", "Ice Palace", "Misery Mire",
"Turtle Rock", "Ganon's Tower"}; "Turtle Rock", "Ganon's Tower"};
if (ImGui::BeginTable("DungeonMapsTable", 4, ImGuiTableFlags_Resizable)) { if (ImGui::BeginTable("DungeonMapsTable", 4,
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Hideable)) {
ImGui::TableSetupColumn("Dungeon"); ImGui::TableSetupColumn("Dungeon");
ImGui::TableSetupColumn("Map"); ImGui::TableSetupColumn("Map");
ImGui::TableSetupColumn("Rooms Gfx"); ImGui::TableSetupColumn("Rooms Gfx");
@@ -384,32 +476,120 @@ void ScreenEditor::DrawDungeonMapsEditor() {
if (!tilesheet_canvas_.points().empty()) { if (!tilesheet_canvas_.points().empty()) {
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 + selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
(tilesheet_canvas_.points().front().y / 32) * 16; (tilesheet_canvas_.points().front().y / 32) * 16;
current_tile16_info = tile16_sheet_.tile_info().at(selected_tile16_);
// Draw the selected tile
if (!screen_canvas_.points().empty()) {
dungeon_maps_[selected_dungeon]
.floor_gfx[floor_number][selected_room] = selected_tile16_;
tilesheet_canvas_.mutable_points()->clear();
}
}
ImGui::Separator();
current_tile_canvas_
.DrawBackground(); // ImVec2(64 * 2 + 2, 64 * 2 + 4));
current_tile_canvas_.DrawContextMenu();
if (current_tile_canvas_.DrawTilePainter(
tile8_individual_[selected_tile8_], 16)) {
// Modify the tile16 based on the selected tile and current_tile16_info
}
current_tile_canvas_.DrawBitmap(tile16_individual_[selected_tile16_], 2,
4.0f);
current_tile_canvas_.DrawGrid(16.f);
current_tile_canvas_.DrawOverlay();
gui::InputTileInfo("TL", &current_tile16_info.tiles[0]);
ImGui::SameLine();
gui::InputTileInfo("TR", &current_tile16_info.tiles[1]);
gui::InputTileInfo("BL", &current_tile16_info.tiles[2]);
ImGui::SameLine();
gui::InputTileInfo("BR", &current_tile16_info.tiles[3]);
if (ImGui::Button("Modify Tile16")) {
tile16_sheet_.ModifyTile16(
rom()->graphics_buffer(), current_tile16_info.tiles[0],
current_tile16_info.tiles[1], current_tile16_info.tiles[2],
current_tile16_info.tiles[3], selected_tile16_, 212);
tile16_individual_[selected_tile16_] =
tile16_sheet_.GetTile16(selected_tile16_);
RETURN_VOID_IF_ERROR(tile16_individual_[selected_tile16_].ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(
&tile16_individual_[selected_tile16_]);
} }
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
tilemap_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4)); tilemap_canvas_.DrawBackground();
tilemap_canvas_.DrawContextMenu(); tilemap_canvas_.DrawContextMenu();
if (tilemap_canvas_.DrawTileSelector(16.f)) {
// Get the tile8 ID to use for the tile16 drawing above
selected_tile8_ = tilemap_canvas_.GetTileIdFromMousePos();
}
tilemap_canvas_.DrawBitmapTable(sheets_); tilemap_canvas_.DrawBitmapTable(sheets_);
tilemap_canvas_.DrawGrid(); tilemap_canvas_.DrawGrid();
tilemap_canvas_.DrawOverlay(); tilemap_canvas_.DrawOverlay();
ImGui::Text("Selected tile8: %d", selected_tile8_);
ImGui::Separator();
ImGui::Text("For use with custom inserted graphics assembly patches.");
if (ImGui::Button("Load GFX from BIN file")) LoadBinaryGfx();
ImGui::EndTable(); ImGui::EndTable();
} }
} }
void ScreenEditor::LoadBinaryGfx() {
std::string bin_file = core::FileDialogWrapper::ShowOpenFileDialog();
if (!bin_file.empty()) {
std::ifstream file(bin_file, std::ios::binary);
if (file.is_open()) {
// Read the gfx data into a buffer
std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
gfx_bin_data_ = converted_bin;
tile16_sheet_.clear();
if (LoadDungeonMapTile16(converted_bin, true).ok()) {
sheets_.clear();
std::vector<std::vector<uint8_t>> gfx_sheets;
for (int i = 0; i < 4; i++) {
gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
converted_bin.begin() + ((i + 1) * 0x1000));
sheets_.emplace(i, gfx::Bitmap(128, 32, 8, gfx_sheets[i]));
status_ = sheets_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3));
if (status_.ok()) {
Renderer::GetInstance().RenderBitmap(&sheets_[i]);
}
}
binary_gfx_loaded_ = true;
} else {
status_ = absl::InternalError("Failed to load dungeon map tile16");
}
file.close();
}
}
}
void ScreenEditor::DrawTitleScreenEditor() { void ScreenEditor::DrawTitleScreenEditor() {
TAB_ITEM("Title Screen") if (ImGui::BeginTabItem("Title Screen")) {
END_TAB_ITEM() ImGui::EndTabItem();
}
} }
void ScreenEditor::DrawNamingScreenEditor() { void ScreenEditor::DrawNamingScreenEditor() {
TAB_ITEM("Naming Screen") if (ImGui::BeginTabItem("Naming Screen")) {
END_TAB_ITEM() ImGui::EndTabItem();
}
} }
void ScreenEditor::DrawOverworldMapEditor() { void ScreenEditor::DrawOverworldMapEditor() {
TAB_ITEM("Overworld Map") if (ImGui::BeginTabItem("Overworld Map")) {
END_TAB_ITEM() ImGui::EndTabItem();
}
} }
void ScreenEditor::DrawToolset() { void ScreenEditor::DrawToolset() {
@@ -433,5 +613,4 @@ void ScreenEditor::DrawToolset() {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,26 +1,20 @@
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H #ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H #define YAZE_APP_EDITOR_SCREEN_EDITOR_H
#include "imgui/imgui.h"
#include <array> #include <array>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/constants.h" #include "app/editor/editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h" #include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/screen/dungeon_map.h" #include "app/zelda3/screen/dungeon_map.h"
#include "app/zelda3/screen/inventory.h" #include "app/zelda3/screen/inventory.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
/** /**
@@ -65,42 +59,58 @@ class ScreenEditor : public SharedRom, public Editor {
void DrawInventoryToolset(); void DrawInventoryToolset();
absl::Status LoadDungeonMaps(); absl::Status LoadDungeonMaps();
absl::Status LoadDungeonMapTile16(); absl::Status LoadDungeonMapTile16(const std::vector<uint8_t> &gfx_data,
bool bin_mode = false);
absl::Status SaveDungeonMapTile16();
void DrawDungeonMapsTabs(); void DrawDungeonMapsTabs();
void DrawDungeonMapsEditor(); void DrawDungeonMapsEditor();
std::vector<zelda3::screen::DungeonMap> dungeon_maps_; void LoadBinaryGfx();
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
std::unordered_map<int, gfx::Bitmap> tile16_individual_; enum class EditingMode { DRAW, EDIT };
EditingMode current_mode_ = EditingMode::DRAW;
bool dungeon_maps_loaded_ = false; bool dungeon_maps_loaded_ = false;
bool binary_gfx_loaded_ = false;
int selected_tile16_ = 0;
int selected_dungeon = 0;
uint8_t selected_room = 0; uint8_t selected_room = 0;
uint8_t boss_room = 0; uint8_t boss_room = 0;
int selected_tile16_ = 0;
int selected_tile8_ = 0;
int selected_dungeon = 0;
int floor_number = 1; int floor_number = 1;
bool copy_button_pressed = false; bool copy_button_pressed = false;
bool paste_button_pressed = false; bool paste_button_pressed = false;
Bytes all_gfx_; std::array<uint16_t, 4> current_tile16_data_;
zelda3::screen::Inventory inventory_; std::unordered_map<int, gfx::Bitmap> tile16_individual_;
gfx::SnesPalette palette_; std::vector<gfx::Bitmap> tile8_individual_;
gui::Canvas screen_canvas_; std::vector<uint8_t> all_gfx_;
gui::Canvas tilesheet_canvas_; std::vector<uint8_t> gfx_bin_data_;
gui::Canvas tilemap_canvas_; std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_;
absl::Status status_; absl::Status status_;
gfx::SnesPalette palette_;
gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_;
gfx::InternalTile16 current_tile16_info;
gui::Canvas current_tile_canvas_{"##CurrentTileCanvas", ImVec2(32, 32),
gui::CanvasGridSize::k16x16, 2.0f};
gui::Canvas screen_canvas_;
gui::Canvas tilesheet_canvas_;
gui::Canvas tilemap_canvas_{"##TilemapCanvas", ImVec2(128 + 2, (192) + 4),
gui::CanvasGridSize::k8x8, 2.f};
zelda3::screen::Inventory inventory_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

View File

@@ -1,14 +1,13 @@
#include "tile16_editor.h" #include "tile16_editor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include <cmath> #include <cmath>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
@@ -19,11 +18,13 @@
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using core::Renderer;
using ImGui::BeginChild; using ImGui::BeginChild;
using ImGui::BeginMenu; using ImGui::BeginMenu;
using ImGui::BeginMenuBar; using ImGui::BeginMenuBar;
@@ -48,18 +49,17 @@ using ImGui::TableSetupColumn;
using ImGui::Text; using ImGui::Text;
absl::Status Tile16Editor::InitBlockset( absl::Status Tile16Editor::InitBlockset(
gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp, const gfx::Bitmap& tile16_blockset_bmp, const gfx::Bitmap& current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual, const std::vector<gfx::Bitmap>& tile16_individual,
uint8_t all_tiles_types[0x200]) { std::array<uint8_t, 0x200>& all_tiles_types) {
all_tiles_types_ = all_tiles_types; all_tiles_types_ = all_tiles_types;
tile16_blockset_bmp_ = tile16_blockset_bmp; tile16_blockset_bmp_ = tile16_blockset_bmp;
tile16_individual_ = tile16_individual; tile16_individual_ = tile16_individual;
current_gfx_bmp_ = current_gfx_bmp; current_gfx_bmp_ = current_gfx_bmp;
tile8_gfx_data_ = current_gfx_bmp_.vector();
RETURN_IF_ERROR(LoadTile8()); RETURN_IF_ERROR(LoadTile8());
ImVector<std::string> tile16_names; ImVector<std::string> tile16_names;
for (int i = 0; i < 0x200; ++i) { for (int i = 0; i < 0x200; ++i) {
std::string str = core::UppercaseHexByte(all_tiles_types_[i]); std::string str = core::HexByte(all_tiles_types_[i]);
tile16_names.push_back(str); tile16_names.push_back(str);
} }
@@ -75,7 +75,7 @@ absl::Status Tile16Editor::Update() {
RETURN_IF_ERROR(DrawMenu()); RETURN_IF_ERROR(DrawMenu());
if (BeginTabBar("Tile16 Editor Tabs")) { if (BeginTabBar("Tile16 Editor Tabs")) {
RETURN_IF_ERROR(DrawTile16Editor()); DrawTile16Editor();
RETURN_IF_ERROR(UpdateTile16Transfer()); RETURN_IF_ERROR(UpdateTile16Transfer());
EndTabBar(); EndTabBar();
} }
@@ -97,7 +97,7 @@ absl::Status Tile16Editor::DrawMenu() {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Tile16Editor::DrawTile16Editor() { void Tile16Editor::DrawTile16Editor() {
if (BeginTabItem("Tile16 Editing")) { if (BeginTabItem("Tile16 Editing")) {
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE, if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) { ImVec2(0, 0))) {
@@ -108,18 +108,23 @@ absl::Status Tile16Editor::DrawTile16Editor() {
TableHeadersRow(); TableHeadersRow();
TableNextRow(); TableNextRow();
TableNextColumn(); TableNextColumn();
RETURN_IF_ERROR(UpdateBlockset()); status_ = UpdateBlockset();
if (!status_.ok()) {
EndTable();
}
TableNextColumn(); TableNextColumn();
RETURN_IF_ERROR(UpdateTile16Edit()); status_ = UpdateTile16Edit();
RETURN_IF_ERROR(DrawTileEditControls()); if (status_ != absl::OkStatus()) {
EndTable();
}
status_ = DrawTileEditControls();
EndTable(); EndTable();
} }
EndTabItem(); EndTabItem();
} }
return absl::OkStatus();
} }
absl::Status Tile16Editor::UpdateBlockset() { absl::Status Tile16Editor::UpdateBlockset() {
@@ -127,14 +132,12 @@ absl::Status Tile16Editor::UpdateBlockset() {
gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion"); gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
blockset_canvas_.DrawBackground(); blockset_canvas_.DrawBackground();
gui::EndPadding(); gui::EndPadding();
{ blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawContextMenu(); blockset_canvas_.DrawTileSelector(32);
blockset_canvas_.DrawTileSelector(32); blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_);
blockset_canvas_.DrawBitmap(*tile16_blockset_bmp_, 0, map_blockset_loaded_); blockset_canvas_.DrawGrid();
blockset_canvas_.DrawGrid(); blockset_canvas_.DrawOverlay();
blockset_canvas_.DrawOverlay(); EndChild();
EndChild();
}
if (!blockset_canvas_.points().empty()) { if (!blockset_canvas_.points().empty()) {
notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos(); notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos();
@@ -142,11 +145,11 @@ absl::Status Tile16Editor::UpdateBlockset() {
if (notify_tile16.modified()) { if (notify_tile16.modified()) {
current_tile16_ = notify_tile16.get(); current_tile16_ = notify_tile16.get();
current_tile16_bmp_ = &tile16_individual_[notify_tile16]; current_tile16_bmp_ = tile16_individual_[notify_tile16];
auto ow_main_pal_group = rom()->palette_group().overworld_main; auto ow_main_pal_group = rom()->palette_group().overworld_main;
RETURN_IF_ERROR(current_tile16_bmp_->ApplyPalette( RETURN_IF_ERROR(current_tile16_bmp_.ApplyPalette(
ow_main_pal_group[current_palette_])); ow_main_pal_group[current_palette_]));
rom()->RenderBitmap(current_tile16_bmp_); Renderer::GetInstance().RenderBitmap(&current_tile16_bmp_);
} }
} }
@@ -166,8 +169,8 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
// Calculate the pixel start position within the Tile16 // Calculate the pixel start position within the Tile16
ImVec2 start_position; ImVec2 start_position;
start_position.x = ((tile_index_x) / 4) * 0x40; start_position.x = tile_index_x * 0x40;
start_position.y = ((tile_index_y) / 4) * 0x40; start_position.y = tile_index_y * 0x40;
std::cout << "Start Position X: " << start_position.x << std::endl; std::cout << "Start Position X: " << start_position.x << std::endl;
std::cout << "Start Position Y: " << start_position.y << std::endl; std::cout << "Start Position Y: " << start_position.y << std::endl;
@@ -177,7 +180,7 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
int pixel_index = int pixel_index =
(start_position.y + y) * tile16_size + ((start_position.x) + x); (start_position.y + y) * tile16_size + ((start_position.x) + x);
int gfx_pixel_index = y * tile8_size + x; int gfx_pixel_index = y * tile8_size + x;
current_tile16_bmp_->WriteToPixel( current_tile16_bmp_.WriteToPixel(
pixel_index, pixel_index,
current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]); current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
} }
@@ -197,7 +200,8 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
RETURN_IF_ERROR( RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent( current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_)); ow_main_pal_group[0], current_palette_));
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]); Renderer::GetInstance().UpdateBitmap(
&current_gfx_individual_[current_tile8_]);
} }
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f); tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
tile8_source_canvas_.DrawGrid(); tile8_source_canvas_.DrawGrid();
@@ -214,20 +218,21 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
RETURN_IF_ERROR( RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent( current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_)); ow_main_pal_group[0], current_palette_));
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]); Renderer::GetInstance().UpdateBitmap(
&current_gfx_individual_[current_tile8_]);
} }
if (BeginChild("Tile16 Editor Options", if (BeginChild("Tile16 Editor Options",
ImVec2(GetContentRegionAvail().x, 0x50), true)) { ImVec2(GetContentRegionAvail().x, 0x50), true)) {
tile16_edit_canvas_.DrawBackground(); tile16_edit_canvas_.DrawBackground();
tile16_edit_canvas_.DrawContextMenu(current_tile16_bmp_); tile16_edit_canvas_.DrawContextMenu(&current_tile16_bmp_);
tile16_edit_canvas_.DrawBitmap(*current_tile16_bmp_, 0, 0, 4.0f); tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
if (!tile8_source_canvas_.points().empty()) { if (!tile8_source_canvas_.points().empty()) {
if (tile16_edit_canvas_.DrawTilePainter( if (tile16_edit_canvas_.DrawTilePainter(
current_gfx_individual_[current_tile8_], 16, 2.0f)) { current_gfx_individual_[current_tile8_], 16, 2.0f)) {
RETURN_IF_ERROR( RETURN_IF_ERROR(
DrawToCurrentTile16(tile16_edit_canvas_.drawn_tile_position())); DrawToCurrentTile16(tile16_edit_canvas_.drawn_tile_position()));
rom()->UpdateBitmap(current_tile16_bmp_); Renderer::GetInstance().UpdateBitmap(&current_tile16_bmp_);
} }
} }
tile16_edit_canvas_.DrawGrid(); tile16_edit_canvas_.DrawGrid();
@@ -245,7 +250,7 @@ absl::Status Tile16Editor::DrawTileEditControls() {
gui::InputHexByte("Palette", &notify_palette.mutable_get()); gui::InputHexByte("Palette", &notify_palette.mutable_get());
notify_palette.apply_changes(); notify_palette.apply_changes();
if (notify_palette.modified()) { if (notify_palette.modified()) {
auto palette = palettesets_[current_palette_].main; auto palette = palettesets_[current_palette_].main_;
auto value = notify_palette.get(); auto value = notify_palette.get();
if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) { if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
palette = palettesets_[current_palette_].aux1; palette = palettesets_[current_palette_].aux1;
@@ -258,10 +263,11 @@ absl::Status Tile16Editor::DrawTileEditControls() {
if (value > 0x00) { if (value > 0x00) {
RETURN_IF_ERROR( RETURN_IF_ERROR(
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value)); current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
Renderer::GetInstance().UpdateBitmap(&current_gfx_bmp_);
RETURN_IF_ERROR( RETURN_IF_ERROR(
current_tile16_bmp_->ApplyPaletteWithTransparent(palette, value)); current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value));
rom()->UpdateBitmap(&current_gfx_bmp_); Renderer::GetInstance().UpdateBitmap(&current_tile16_bmp_);
rom()->UpdateBitmap(current_tile16_bmp_);
} }
} }
@@ -290,7 +296,6 @@ absl::Status Tile16Editor::LoadTile8() {
// Calculate the position in the current gfx data // Calculate the position in the current gfx data
int num_columns = current_gfx_bmp_.width() / 8; int num_columns = current_gfx_bmp_.width() / 8;
int num_rows = current_gfx_bmp_.height() / 8;
int x = (index % num_columns) * 8 + tx; int x = (index % num_columns) * 8 + tx;
int y = (index / num_columns) * 8 + ty; int y = (index / num_columns) * 8 + ty;
int gfx_position = x + (y * 0x100); int gfx_position = x + (y * 0x100);
@@ -310,7 +315,7 @@ absl::Status Tile16Editor::LoadTile8() {
current_gfx_individual_[index].Create(0x08, 0x08, 0x08, tile_data); current_gfx_individual_[index].Create(0x08, 0x08, 0x08, tile_data);
RETURN_IF_ERROR(current_gfx_individual_[index].ApplyPaletteWithTransparent( RETURN_IF_ERROR(current_gfx_individual_[index].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_)); ow_main_pal_group[0], current_palette_));
rom()->RenderBitmap(&current_gfx_individual_[index]); Renderer::GetInstance().RenderBitmap(&current_gfx_individual_[index]);
} }
map_blockset_loaded_ = true; map_blockset_loaded_ = true;
@@ -318,8 +323,17 @@ absl::Status Tile16Editor::LoadTile8() {
return absl::OkStatus(); return absl::OkStatus();
} }
// ============================================================================ absl::Status Tile16Editor::SetCurrentTile(int id) {
// Tile16 Transfer current_tile16_ = id;
current_tile16_bmp_ = tile16_individual_[id];
auto ow_main_pal_group = rom()->palette_group().overworld_main;
RETURN_IF_ERROR(
current_tile16_bmp_.ApplyPalette(ow_main_pal_group[current_palette_]));
Renderer::GetInstance().RenderBitmap(&current_tile16_bmp_);
return absl::OkStatus();
}
#pragma mark - Tile16Transfer
absl::Status Tile16Editor::UpdateTile16Transfer() { absl::Status Tile16Editor::UpdateTile16Transfer() {
if (BeginTabItem("Tile16 Transfer")) { if (BeginTabItem("Tile16 Transfer")) {
@@ -349,30 +363,23 @@ absl::Status Tile16Editor::UpdateTile16Transfer() {
absl::Status Tile16Editor::UpdateTransferTileCanvas() { absl::Status Tile16Editor::UpdateTransferTileCanvas() {
// Create a button for loading another ROM // Create a button for loading another ROM
if (Button("Load ROM")) { if (Button("Load ROM")) {
ImGuiFileDialog::Instance()->OpenDialog( auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", "."); transfer_status_ = transfer_rom_.LoadFromFile(file_name);
transfer_started_ = true;
} }
gui::FileDialogPipeline(
"ChooseTransferFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
std::string filePathName =
ImGuiFileDialog::Instance()->GetFilePathName();
transfer_status_ = transfer_rom_.LoadFromFile(filePathName);
transfer_started_ = true;
});
// TODO: Implement tile16 transfer // TODO: Implement tile16 transfer
if (transfer_started_ && !transfer_blockset_loaded_) { if (transfer_started_ && !transfer_blockset_loaded_) {
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData()) PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
graphics_bin_ = transfer_rom_.graphics_bin();
// Load the Link to the Past overworld. // Load the Link to the Past overworld.
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_)) PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
transfer_overworld_.set_current_map(0); transfer_overworld_.set_current_map(0);
palette_ = transfer_overworld_.AreaPalette(); palette_ = transfer_overworld_.current_area_palette();
// Create the tile16 blockset image // Create the tile16 blockset image
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap( RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
0x80, 0x2000, 0x80, transfer_overworld_.Tile16Blockset(), 0x80, 0x2000, 0x80, transfer_overworld_.tile16_blockset_data(),
transfer_blockset_bmp_, palette_)); transfer_blockset_bmp_, palette_));
transfer_blockset_loaded_ = true; transfer_blockset_loaded_ = true;
} }
@@ -385,16 +392,5 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Tile16Editor::SetCurrentTile(int id) {
current_tile16_ = id;
current_tile16_bmp_ = &tile16_individual_[id];
auto ow_main_pal_group = rom()->palette_group().overworld_main;
RETURN_IF_ERROR(
current_tile16_bmp_->ApplyPalette(ow_main_pal_group[current_palette_]));
rom()->RenderBitmap(current_tile16_bmp_);
return absl::OkStatus();
}
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,42 +1,34 @@
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H #ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
#define YAZE_APP_EDITOR_TILE16EDITOR_H #define YAZE_APP_EDITOR_TILE16EDITOR_H
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "app/core/common.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
/** /**
* @brief Popup window to edit Tile16 data * @brief Popup window to edit Tile16 data
*/ */
class Tile16Editor : public context::GfxContext, public SharedRom { class Tile16Editor : public gfx::GfxContext, public SharedRom {
public: public:
absl::Status InitBlockset(gfx::Bitmap* tile16_blockset_bmp, absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
gfx::Bitmap current_gfx_bmp, const gfx::Bitmap& current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual, const std::vector<gfx::Bitmap>& tile16_individual,
uint8_t all_tiles_types[0x200]); std::array<uint8_t, 0x200>& all_tiles_types);
absl::Status Update(); absl::Status Update();
absl::Status DrawMenu(); absl::Status DrawMenu();
absl::Status DrawTile16Editor(); void DrawTile16Editor();
absl::Status UpdateTile16Transfer(); absl::Status UpdateTile16Transfer();
absl::Status UpdateBlockset(); absl::Status UpdateBlockset();
@@ -70,49 +62,42 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
bool priority_tile; bool priority_tile;
int tile_size; int tile_size;
uint8_t* all_tiles_types_; std::array<uint8_t, 0x200> all_tiles_types_;
// Tile16 blockset for selecting the tile to edit // Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000), gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32}; gui::CanvasGridSize::k32x32};
gfx::Bitmap* tile16_blockset_bmp_; gfx::Bitmap tile16_blockset_bmp_;
// Canvas for editing the selected tile // Canvas for editing the selected tile
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40), gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
gui::CanvasGridSize::k64x64}; gui::CanvasGridSize::k64x64};
gfx::Bitmap* current_tile16_bmp_; gfx::Bitmap current_tile16_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_ // Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
gui::Canvas tile8_source_canvas_{ gui::Canvas tile8_source_canvas_{
"Tile8SourceCanvas", "Tile8SourceCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4), ImVec2(gfx::kTilesheetWidth * 4, gfx::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k32x32}; gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_; gfx::Bitmap current_gfx_bmp_;
gui::Canvas transfer_canvas_; gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_; gfx::Bitmap transfer_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_; std::vector<gfx::Bitmap> tile16_individual_;
std::vector<gfx::Bitmap> current_gfx_individual_; std::vector<gfx::Bitmap> current_gfx_individual_;
std::vector<uint8_t> current_tile16_data_;
std::vector<uint8_t> tile8_gfx_data_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
gfx::SnesPalette palette_; gfx::SnesPalette palette_;
zelda3::overworld::Overworld transfer_overworld_; zelda3::Overworld transfer_overworld_;
gfx::BitmapTable graphics_bin_; absl::Status status_;
Rom transfer_rom_; Rom transfer_rom_;
absl::Status transfer_status_; absl::Status transfer_status_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H #endif // YAZE_APP_EDITOR_TILE16EDITOR_H

View File

@@ -1,10 +0,0 @@
#include "master_editor.h"
namespace yaze {
namespace app {
namespace editor {
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,180 @@
#include "message_data.h"
#include "app/core/common.h"
namespace yaze {
namespace editor {
uint8_t FindMatchingCharacter(char value) {
for (const auto [key, char_value] : CharEncoder) {
if (value == char_value) {
return key;
}
}
return 0xFF;
}
uint8_t FindDictionaryEntry(uint8_t value) {
if (value < DICTOFF || value == 0xFF) {
return -1;
}
return value - DICTOFF;
}
TextElement FindMatchingCommand(uint8_t b) {
TextElement empty_element;
for (const auto& text_element : TextCommands) {
if (text_element.ID == b) {
return text_element;
}
}
return empty_element;
}
TextElement FindMatchingSpecial(uint8_t value) {
auto it = std::find_if(SpecialChars.begin(), SpecialChars.end(),
[value](const TextElement& text_element) {
return text_element.ID == value;
});
if (it != SpecialChars.end()) {
return *it;
}
return TextElement();
}
ParsedElement FindMatchingElement(const std::string& str) {
std::smatch match;
for (auto& textElement : TextCommands) {
match = textElement.MatchMe(str);
if (match.size() > 0) {
if (textElement.HasArgument) {
return ParsedElement(textElement,
std::stoi(match[1].str(), nullptr, 16));
} else {
return ParsedElement(textElement, 0);
}
}
}
const auto dictionary_element =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
match = dictionary_element.MatchMe(str);
if (match.size() > 0) {
return ParsedElement(dictionary_element,
DICTOFF + std::stoi(match[1].str(), nullptr, 16));
}
return ParsedElement();
}
std::string ParseTextDataByte(uint8_t value) {
if (CharEncoder.contains(value)) {
char c = CharEncoder.at(value);
std::string str = "";
str.push_back(c);
return str;
}
// Check for command.
TextElement textElement = FindMatchingCommand(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for special characters.
textElement = FindMatchingSpecial(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(value);
if (dictionary >= 0) {
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
}
return "";
}
std::vector<uint8_t> ParseMessageToData(std::string str) {
std::vector<uint8_t> bytes;
std::string temp_string = str;
int pos = 0;
while (pos < temp_string.size()) {
// Get next text fragment.
if (temp_string[pos] == '[') {
int next = temp_string.find(']', pos);
if (next == -1) {
break;
}
ParsedElement parsedElement =
FindMatchingElement(temp_string.substr(pos, next - pos + 1));
const auto dictionary_element =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
if (!parsedElement.Active) {
core::logf("Error parsing message: %s", temp_string);
break;
} else if (parsedElement.Parent == dictionary_element) {
bytes.push_back(parsedElement.Value);
} else {
bytes.push_back(parsedElement.Parent.ID);
if (parsedElement.Parent.HasArgument) {
bytes.push_back(parsedElement.Value);
}
}
pos = next + 1;
continue;
} else {
uint8_t bb = FindMatchingCharacter(temp_string[pos++]);
if (bb != 0xFF) {
core::logf("Error parsing message: %s", temp_string);
bytes.push_back(bb);
}
}
}
return bytes;
}
std::vector<DictionaryEntry> BuildDictionaryEntries(Rom* rom) {
std::vector<DictionaryEntry> AllDictionaries;
for (int i = 0; i < kNumDictionaryEntries; i++) {
std::vector<uint8_t> bytes;
std::stringstream stringBuilder;
int address = core::SnesToPc(
kTextData + (rom->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
rom->data()[kPointersDictionaries + (i * 2)]);
int temppush_backress = core::SnesToPc(
kTextData +
(rom->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
rom->data()[kPointersDictionaries + ((i + 1) * 2)]);
while (address < temppush_backress) {
uint8_t uint8_tDictionary = rom->data()[address++];
bytes.push_back(uint8_tDictionary);
stringBuilder << ParseTextDataByte(uint8_tDictionary);
}
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
}
std::sort(AllDictionaries.begin(), AllDictionaries.end(),
[](const DictionaryEntry& a, const DictionaryEntry& b) {
return a.Contents.size() > b.Contents.size();
});
return AllDictionaries;
}
} // namespace editor
} // namespace yaze

View File

@@ -1,23 +1,89 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H #ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H #define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#include <regex>
#include <string> #include <string>
#include <vector> #include <vector>
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "app/rom.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
const uint8_t MESSAGETERMINATOR = 0x7F; const uint8_t kMessageTerminator = 0x7F;
const std::string BANKToken = "BANK";
const std::string DICTIONARYTOKEN = "D";
constexpr uint8_t DICTOFF = 0x88;
static std::string AddNewLinesToCommands(std::string str); static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
static std::string ReplaceAllDictionaryWords(std::string str); {0x00, 'A'}, {0x01, 'B'}, {0x02, 'C'}, {0x03, 'D'}, {0x04, 'E'},
static std::vector<uint8_t> ParseMessageToData(std::string str); {0x05, 'F'}, {0x06, 'G'}, {0x07, 'H'}, {0x08, 'I'}, {0x09, 'J'},
{0x0A, 'K'}, {0x0B, 'L'}, {0x0C, 'M'}, {0x0D, 'N'}, {0x0E, 'O'},
{0x0F, 'P'}, {0x10, 'Q'}, {0x11, 'R'}, {0x12, 'S'}, {0x13, 'T'},
{0x14, 'U'}, {0x15, 'V'}, {0x16, 'W'}, {0x17, 'X'}, {0x18, 'Y'},
{0x19, 'Z'}, {0x1A, 'a'}, {0x1B, 'b'}, {0x1C, 'c'}, {0x1D, 'd'},
{0x1E, 'e'}, {0x1F, 'f'}, {0x20, 'g'}, {0x21, 'h'}, {0x22, 'i'},
{0x23, 'j'}, {0x24, 'k'}, {0x25, 'l'}, {0x26, 'm'}, {0x27, 'n'},
{0x28, 'o'}, {0x29, 'p'}, {0x2A, 'q'}, {0x2B, 'r'}, {0x2C, 's'},
{0x2D, 't'}, {0x2E, 'u'}, {0x2F, 'v'}, {0x30, 'w'}, {0x31, 'x'},
{0x32, 'y'}, {0x33, 'z'}, {0x34, '0'}, {0x35, '1'}, {0x36, '2'},
{0x37, '3'}, {0x38, '4'}, {0x39, '5'}, {0x3A, '6'}, {0x3B, '7'},
{0x3C, '8'}, {0x3D, '9'}, {0x3E, '!'}, {0x3F, '?'}, {0x40, '-'},
{0x41, '.'}, {0x42, ','}, {0x44, '>'}, {0x45, '('}, {0x46, ')'},
{0x4C, '"'}, {0x51, '\''}, {0x59, ' '}, {0x5A, '<'}, {0x5F, L'¡'},
{0x60, L'¡'}, {0x61, L'¡'}, {0x62, L' '}, {0x63, L' '}, {0x64, L' '},
{0x65, ' '}, {0x66, '_'},
};
const std::string CHEESE = "\uBEBE"; // Inserted into commands to protect uint8_t FindMatchingCharacter(char value);
// them from dictionary replacements. uint8_t FindDictionaryEntry(uint8_t value);
std::vector<uint8_t> ParseMessageToData(std::string str);
struct DictionaryEntry {
uint8_t ID;
std::string Contents;
std::vector<uint8_t> Data;
int Length;
std::string Token;
DictionaryEntry() = default;
DictionaryEntry(uint8_t i, std::string s)
: Contents(s), ID(i), Length(s.length()) {
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
Data = ParseMessageToData(Contents);
}
bool ContainedInString(std::string s) {
return s.find(Contents) != std::string::npos;
}
std::string ReplaceInstancesOfIn(std::string s) {
std::string replacedString = s;
size_t pos = replacedString.find(Contents);
while (pos != std::string::npos) {
replacedString.replace(pos, Contents.length(), Token);
pos = replacedString.find(Contents, pos + Token.length());
}
return replacedString;
}
};
constexpr int kTextData = 0xE0000;
constexpr int kTextDataEnd = 0xE7FFF;
constexpr int kNumDictionaryEntries = 97;
constexpr int kPointersDictionaries = 0x74703;
std::vector<DictionaryEntry> BuildDictionaryEntries(Rom* rom);
std::string ReplaceAllDictionaryWords(std::string str,
std::vector<DictionaryEntry> dictionary);
// Inserted into commands to protect them from dictionary replacements.
const std::string CHEESE = "\uBEBE";
struct MessageData { struct MessageData {
int ID; int ID;
@@ -49,35 +115,13 @@ struct MessageData {
ContentsParsed = other.ContentsParsed; ContentsParsed = other.ContentsParsed;
} }
void SetMessage(std::string messageString) {
ContentsParsed = messageString;
RawString = OptimizeMessageForDictionary(messageString);
RecalculateData();
}
std::string ToString() { std::string ToString() {
return absl::StrFormat("%0X - %s", ID, ContentsParsed); return absl::StrFormat("%0X - %s", ID, ContentsParsed);
} }
std::string GetReadableDumpedContents() { std::string OptimizeMessageForDictionary(
std::stringstream stringBuilder; std::string messageString,
for (const auto& b : Data) { const std::vector<DictionaryEntry>& dictionary) {
stringBuilder << absl::StrFormat("%0X ", b);
}
stringBuilder << absl::StrFormat("%00X", MESSAGETERMINATOR);
return absl::StrFormat(
"[[[[\r\nMessage "
"%000X]]]]\r\n[Contents]\r\n%s\r\n\r\n[Data]\r\n%s"
"\r\n\r\n\r\n\r\n",
ID, AddNewLinesToCommands(ContentsParsed), stringBuilder.str());
}
std::string GetDumpedContents() {
return absl::StrFormat("%000X : %s\r\n\r\n", ID, ContentsParsed);
}
std::string OptimizeMessageForDictionary(std::string messageString) {
std::stringstream protons; std::stringstream protons;
bool command = false; bool command = false;
for (const auto& c : messageString) { for (const auto& c : messageString) {
@@ -94,16 +138,18 @@ struct MessageData {
} }
std::string protonsString = protons.str(); std::string protonsString = protons.str();
std::string replacedString = ReplaceAllDictionaryWords(protonsString); std::string replacedString =
ReplaceAllDictionaryWords(protonsString, dictionary);
std::string finalString = std::string finalString =
absl::StrReplaceAll(replacedString, {{CHEESE, ""}}); absl::StrReplaceAll(replacedString, {{CHEESE, ""}});
return finalString; return finalString;
} }
void RecalculateData() { void SetMessage(const std::string& message,
Data = ParseMessageToData(RawString); const std::vector<DictionaryEntry>& dictionary) {
DataParsed = ParseMessageToData(ContentsParsed); RawString = message;
ContentsParsed = OptimizeMessageForDictionary(message, dictionary);
} }
}; };
@@ -155,8 +201,64 @@ struct TextElement {
} }
bool Empty() { return ID == 0; } bool Empty() { return ID == 0; }
// Comparison operator
bool operator==(const TextElement& other) const { return ID == other.ID; }
}; };
static const std::vector<TextElement> TextCommands = {
TextElement(0x6B, "W", true, "Window border"),
TextElement(0x6D, "P", true, "Window position"),
TextElement(0x6E, "SPD", true, "Scroll speed"),
TextElement(0x7A, "S", true, "Text draw speed"),
TextElement(0x77, "C", true, "Text color"),
TextElement(0x6A, "L", false, "Player name"),
TextElement(0x74, "1", false, "Line 1"),
TextElement(0x75, "2", false, "Line 2"),
TextElement(0x76, "3", false, "Line 3"),
TextElement(0x7E, "K", false, "Wait for key"),
TextElement(0x73, "V", false, "Scroll text"),
TextElement(0x78, "WT", true, "Delay X"),
TextElement(0x6C, "N", true, "BCD number"),
TextElement(0x79, "SFX", true, "Sound effect"),
TextElement(0x71, "CH3", false, "Choose 3"),
TextElement(0x72, "CH2", false, "Choose 2 high"),
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
TextElement(0x69, "CHI", false, "Choose item"),
TextElement(0x67, "IMG", false, "Next attract image"),
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
TextElement(0x70, "NONO", false, "Crash"),
};
TextElement FindMatchingCommand(uint8_t b);
static const std::vector<TextElement> SpecialChars = {
TextElement(0x43, "...", false, "Ellipsis …"),
TextElement(0x4D, "UP", false, "Arrow ↑"),
TextElement(0x4E, "DOWN", false, "Arrow ↓"),
TextElement(0x4F, "LEFT", false, "Arrow ←"),
TextElement(0x50, "RIGHT", false, "Arrow →"),
TextElement(0x5B, "A", false, "Button Ⓐ"),
TextElement(0x5C, "B", false, "Button Ⓑ"),
TextElement(0x5D, "X", false, "Button ⓧ"),
TextElement(0x5E, "Y", false, "Button ⓨ"),
TextElement(0x52, "HP1L", false, "1 HP left"),
TextElement(0x53, "HP1R", false, "1 HP right"),
TextElement(0x54, "HP2L", false, "2 HP left"),
TextElement(0x55, "HP3L", false, "3 HP left"),
TextElement(0x56, "HP3R", false, "3 HP right"),
TextElement(0x57, "HP4L", false, "4 HP left"),
TextElement(0x58, "HP4R", false, "4 HP right"),
TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
TextElement(0x4A, "LFL", false, "Link face left"),
TextElement(0x4B, "LFR", false, "Link face right"),
};
TextElement FindMatchingSpecial(uint8_t b);
struct ParsedElement { struct ParsedElement {
TextElement Parent; TextElement Parent;
uint8_t Value; uint8_t Value;
@@ -170,8 +272,11 @@ struct ParsedElement {
} }
}; };
ParsedElement FindMatchingElement(const std::string& str);
std::string ParseTextDataByte(uint8_t value);
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H #endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H

View File

@@ -1,7 +1,5 @@
#include "message_editor.h" #include "message_editor.h"
#include <regex>
#include <sstream>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -9,26 +7,24 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h" #include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h" #include "app/core/platform/renderer.h"
#include "app/core/common.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
#include "imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using ImGui::Begin; using core::Renderer;
using ImGui::BeginChild; using ImGui::BeginChild;
using ImGui::BeginTable; using ImGui::BeginTable;
using ImGui::Button; using ImGui::Button;
using ImGui::End;
using ImGui::EndChild; using ImGui::EndChild;
using ImGui::EndTable; using ImGui::EndTable;
using ImGui::InputText; using ImGui::InputText;
@@ -37,216 +33,24 @@ using ImGui::SameLine;
using ImGui::Separator; using ImGui::Separator;
using ImGui::TableHeadersRow; using ImGui::TableHeadersRow;
using ImGui::TableNextColumn; using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn; using ImGui::TableSetupColumn;
using ImGui::Text; using ImGui::Text;
using ImGui::TextWrapped; using ImGui::TextWrapped;
using ImGui::TreeNode;
static ParsedElement FindMatchingElement(string str) { constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
std::smatch match; ImGuiTableFlags_Borders |
for (auto& textElement : TextCommands) { ImGuiTableFlags_Resizable;
match = textElement.MatchMe(str);
if (match.size() > 0) {
if (textElement.HasArgument) {
return ParsedElement(textElement,
std::stoi(match[1].str(), nullptr, 16));
} else {
return ParsedElement(textElement, 0);
}
}
}
match = DictionaryElement.MatchMe(str); constexpr ImGuiTableFlags kDictTableFlags =
if (match.size() > 0) { ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
return ParsedElement(DictionaryElement,
DICTOFF + std::stoi(match[1].str(), nullptr, 16));
}
return ParsedElement();
}
static string ReplaceAllDictionaryWords(string str) {
string temp = str;
for (const auto& entry : AllDictionaries) {
if (absl::StrContains(temp, entry.Contents)) {
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
}
}
return temp;
}
static std::vector<uint8_t> ParseMessageToData(string str) {
std::vector<uint8_t> bytes;
string tempString = str;
int pos = 0;
while (pos < tempString.size()) {
// Get next text fragment.
if (tempString[pos] == '[') {
int next = tempString.find(']', pos);
if (next == -1) {
break;
}
ParsedElement parsedElement =
FindMatchingElement(tempString.substr(pos, next - pos + 1));
if (!parsedElement.Active) {
break; // TODO: handle badness.
// } else if (parsedElement.Parent == DictionaryElement) {
// bytes.push_back(parsedElement.Value);
} else {
bytes.push_back(parsedElement.Parent.ID);
if (parsedElement.Parent.HasArgument) {
bytes.push_back(parsedElement.Value);
}
}
pos = next + 1;
continue;
} else {
uint8_t bb = MessageEditor::FindMatchingCharacter(tempString[pos++]);
if (bb != 0xFF) {
// TODO: handle badness.
bytes.push_back(bb);
}
}
}
return bytes;
}
absl::Status MessageEditor::Update() {
if (rom()->is_loaded() && !data_loaded_) {
RETURN_IF_ERROR(Initialize());
CurrentMessage = ListOfTexts[1];
data_loaded_ = true;
}
if (BeginTable("##MessageEditor", 3,
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
TableSetupColumn("List");
TableSetupColumn("Contents");
TableSetupColumn("Commands");
TableHeadersRow();
TableNextColumn();
DrawMessageList();
TableNextColumn();
DrawCurrentMessage();
TableNextColumn();
DrawTextCommands();
EndTable();
}
return absl::OkStatus();
}
void MessageEditor::DrawMessageList() {
if (InputText("Search", &search_text_)) {
DisplayedMessages.clear();
for (const auto& message : ListOfTexts) {
if (absl::StrContains(message.ContentsParsed, search_text_)) {
DisplayedMessages.push_back(message);
}
}
}
if (BeginChild("##MessagesList", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (BeginTable("##MessagesTable", 3,
ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders |
ImGuiTableFlags_Resizable)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
TableSetupColumn("Data");
TableHeadersRow();
for (const auto& message : ListOfTexts) {
TableNextColumn();
if (Button(core::UppercaseHexWord(message.ID).c_str())) {
CurrentMessage = message;
DrawMessagePreview();
}
TableNextColumn();
TextWrapped("%s", ParsedMessages[message.ID].c_str());
TableNextColumn();
TextWrapped(
"%s",
core::UppercaseHexLong(ListOfTexts[message.ID].Address).c_str());
}
EndTable();
}
EndChild();
}
}
void MessageEditor::DrawCurrentMessage() {
Button(absl::StrCat("Message ", CurrentMessage.ID).c_str());
if (InputTextMultiline("##MessageEditor", &ParsedMessages[CurrentMessage.ID],
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
CurrentMessage.Data = ParseMessageToData(message_text_box_.text);
DrawMessagePreview();
}
Separator();
Text("Font Graphics");
gui::BeginPadding(1);
BeginChild("MessageEditorCanvas", ImVec2(0, 130));
font_gfx_canvas_.DrawBackground();
font_gfx_canvas_.DrawContextMenu();
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
font_gfx_canvas_.DrawGrid();
font_gfx_canvas_.DrawOverlay();
EndChild();
gui::EndPadding();
Separator();
Text("Message Preview");
if (Button("Refresh Bitmap")) {
rom()->UpdateBitmap(&current_font_gfx16_bitmap_);
}
gui::BeginPadding(1);
BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
current_font_gfx16_canvas_.DrawBackground();
gui::EndPadding();
current_font_gfx16_canvas_.DrawContextMenu();
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
current_font_gfx16_canvas_.DrawGrid();
current_font_gfx16_canvas_.DrawOverlay();
EndChild();
}
void MessageEditor::DrawTextCommands() {
if (BeginChild("##TextCommands", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (const auto& text_element : TextCommands) {
if (Button(text_element.GenericToken.c_str())) {
}
SameLine();
TextWrapped("%s", text_element.Description.c_str());
Separator();
}
EndChild();
}
}
absl::Status MessageEditor::Initialize() { absl::Status MessageEditor::Initialize() {
for (int i = 0; i < 100; i++) { for (int i = 0; i < kWidthArraySize; i++) {
width_array[i] = rom()->data()[kCharactersWidth + i]; width_array[i] = rom()->data()[kCharactersWidth + i];
} }
BuildDictionaryEntries(); all_dictionaries_ = BuildDictionaryEntries(rom());
ReadAllTextData(); ReadAllTextDataV2();
font_preview_colors_.AddColor(0x7FFF); // White font_preview_colors_.AddColor(0x7FFF); // White
font_preview_colors_.AddColor(0x7C00); // Red font_preview_colors_.AddColor(0x7C00); // Red
@@ -257,21 +61,23 @@ absl::Status MessageEditor::Initialize() {
for (int i = 0; i < 0x4000; i++) { for (int i = 0; i < 0x4000; i++) {
data[i] = rom()->data()[kGfxFont + i]; data[i] = rom()->data()[kGfxFont + i];
} }
font_gfx16_data = gfx::SnesTo8bppSheet(data, /*bpp=*/2); font_gfx16_data_ = gfx::SnesTo8bppSheet(data, /*bpp=*/2, /*num_sheets=*/2);
// 4bpp // 4bpp
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap( RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
128, 128, 8, font_gfx16_data, font_gfx_bitmap_, font_preview_colors_)) kFontGfxMessageSize, kFontGfxMessageSize, kFontGfxMessageDepth,
font_gfx16_data_, font_gfx_bitmap_, font_preview_colors_))
current_font_gfx16_data_.reserve(172 * 4096); current_font_gfx16_data_.reserve(kCurrentMessageWidth *
for (int i = 0; i < 172 * 4096; i++) { kCurrentMessageHeight);
for (int i = 0; i < kCurrentMessageWidth * kCurrentMessageHeight; i++) {
current_font_gfx16_data_.push_back(0); current_font_gfx16_data_.push_back(0);
} }
// 8bpp // 8bpp
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap( RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
172, 4096, 64, current_font_gfx16_data_, current_font_gfx16_bitmap_, kCurrentMessageWidth, kCurrentMessageHeight, 64, current_font_gfx16_data_,
font_preview_colors_)) current_font_gfx16_bitmap_, font_preview_colors_))
gfx::SnesPalette color_palette = font_gfx_bitmap_.palette(); gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
for (int i = 0; i < font_preview_colors_.size(); i++) { for (int i = 0; i < font_preview_colors_.size(); i++) {
@@ -280,11 +86,11 @@ absl::Status MessageEditor::Initialize() {
*font_gfx_bitmap_.mutable_palette() = color_palette; *font_gfx_bitmap_.mutable_palette() = color_palette;
for (const auto& message : ListOfTexts) { for (const auto& each_message : list_of_texts_) {
DisplayedMessages.push_back(message); std::cout << "Message #" << each_message.ID << " at address "
} << core::HexLong(each_message.Address) << std::endl;
std::cout << " " << each_message.RawString << std::endl;
for (const auto& each_message : ListOfTexts) {
// Each string has a [:XX] char encoded // Each string has a [:XX] char encoded
// The corresponding character is found in CharEncoder unordered_map // The corresponding character is found in CharEncoder unordered_map
std::string parsed_message = ""; std::string parsed_message = "";
@@ -313,7 +119,8 @@ absl::Status MessageEditor::Initialize() {
} }
} }
} }
ParsedMessages.push_back(parsed_message); std::cout << " > " << parsed_message << std::endl;
parsed_messages_.push_back(parsed_message);
} }
DrawMessagePreview(); DrawMessagePreview();
@@ -321,38 +128,242 @@ absl::Status MessageEditor::Initialize() {
return absl::OkStatus(); return absl::OkStatus();
} }
void MessageEditor::BuildDictionaryEntries() { absl::Status MessageEditor::Update() {
for (int i = 0; i < 97; i++) { if (rom()->is_loaded() && !data_loaded_) {
std::vector<uint8_t> bytes; RETURN_IF_ERROR(Initialize());
std::stringstream stringBuilder; current_message_ = list_of_texts_[1];
data_loaded_ = true;
int address = core::SnesToPc(
0x0E0000 + (rom()->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
rom()->data()[kPointersDictionaries + (i * 2)]);
int temppush_backress = core::SnesToPc(
0x0E0000 +
(rom()->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
rom()->data()[kPointersDictionaries + ((i + 1) * 2)]);
while (address < temppush_backress) {
uint8_t uint8_tDictionary = rom()->data()[address++];
bytes.push_back(uint8_tDictionary);
stringBuilder << ParseTextDataByte(uint8_tDictionary);
}
// AllDictionaries[i] = DictionaryEntry{(uint8_t)i, stringBuilder.str()};
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
} }
// AllDictionaries.OrderByDescending(dictionary = > dictionary.Length); if (BeginTable("##MessageEditor", 4, kDictTableFlags)) {
AllDictionaries[0].Length = 0; TableSetupColumn("List");
TableSetupColumn("Contents");
TableSetupColumn("Commands");
TableSetupColumn("Dictionary");
TableHeadersRow();
TableNextColumn();
DrawMessageList();
TableNextColumn();
DrawCurrentMessage();
TableNextColumn();
DrawTextCommands();
TableNextColumn();
DrawDictionary();
EndTable();
}
return absl::OkStatus();
}
void MessageEditor::DrawMessageList() {
if (BeginChild("##MessagesList", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
TableSetupColumn("Data");
TableHeadersRow();
for (const auto& message : list_of_texts_) {
TableNextColumn();
if (Button(core::HexWord(message.ID).c_str())) {
current_message_ = message;
DrawMessagePreview();
}
TableNextColumn();
TextWrapped("%s", parsed_messages_[message.ID].c_str());
TableNextColumn();
TextWrapped(
"%s",
core::HexLong(list_of_texts_[message.ID].Address).c_str());
}
EndTable();
}
EndChild();
}
}
void MessageEditor::DrawCurrentMessage() {
Button(absl::StrCat("Message ", current_message_.ID).c_str());
if (InputTextMultiline("##MessageEditor",
&parsed_messages_[current_message_.ID],
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
current_message_.Data = ParseMessageToData(message_text_box_.text);
DrawMessagePreview();
}
Separator();
Text("Font Graphics");
gui::BeginPadding(1);
BeginChild("MessageEditorCanvas", ImVec2(0, 130));
font_gfx_canvas_.DrawBackground();
font_gfx_canvas_.DrawContextMenu();
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
font_gfx_canvas_.DrawGrid();
font_gfx_canvas_.DrawOverlay();
EndChild();
gui::EndPadding();
Separator();
Text("Message Preview");
if (Button("Refresh Bitmap")) {
Renderer::GetInstance().UpdateBitmap(&current_font_gfx16_bitmap_);
}
gui::BeginPadding(1);
BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
current_font_gfx16_canvas_.DrawBackground();
gui::EndPadding();
current_font_gfx16_canvas_.DrawContextMenu();
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
current_font_gfx16_canvas_.DrawGrid();
current_font_gfx16_canvas_.DrawOverlay();
EndChild();
}
void MessageEditor::DrawTextCommands() {
if (BeginChild("##TextCommands", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (const auto& text_element : TextCommands) {
if (Button(text_element.GenericToken.c_str())) {
}
SameLine();
TextWrapped("%s", text_element.Description.c_str());
Separator();
}
EndChild();
}
}
void MessageEditor::DrawDictionary() {
if (ImGui::BeginChild("##DictionaryChild", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (BeginTable("##Dictionary", 2, kDictTableFlags)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
for (const auto& dictionary : all_dictionaries_) {
TableNextColumn();
Text("%s", core::HexWord(dictionary.ID).c_str());
TableNextColumn();
Text("%s", dictionary.Contents.c_str());
}
EndTable();
}
EndChild();
}
}
// TODO: Fix the command parsing.
void MessageEditor::ReadAllTextDataV2() {
// Read all text data from the ROM.
int pos = kTextData;
int message_id = 0;
std::vector<uint8_t> raw_message;
std::vector<uint8_t> parsed_message;
std::string current_raw_message;
std::string current_parsed_message;
uint8_t current_byte = 0;
while (current_byte != 0xFF) {
current_byte = rom()->data()[pos++];
if (current_byte == kMessageTerminator) {
auto message =
MessageData(message_id++, pos, current_raw_message, raw_message,
current_parsed_message, parsed_message);
list_of_texts_.push_back(message);
raw_message.clear();
parsed_message.clear();
current_raw_message.clear();
current_parsed_message.clear();
continue;
}
raw_message.push_back(current_byte);
// Check for command.
TextElement text_element = FindMatchingCommand(current_byte);
if (!text_element.Empty()) {
parsed_message.push_back(current_byte);
if (text_element.HasArgument) {
current_byte = rom()->data()[pos++];
raw_message.push_back(current_byte);
parsed_message.push_back(current_byte);
}
current_raw_message.append(
text_element.GetParameterizedToken(current_byte));
current_parsed_message.append(
text_element.GetParameterizedToken(current_byte));
if (text_element.Token == BANKToken) {
pos = kTextData2;
}
continue;
}
// Check for special characters.
text_element = FindMatchingSpecial(current_byte);
if (!text_element.Empty()) {
current_raw_message.append(text_element.GetParameterizedToken());
current_parsed_message.append(text_element.GetParameterizedToken());
parsed_message.push_back(current_byte);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(current_byte);
if (dictionary >= 0) {
current_raw_message.append("[");
current_raw_message.append(DICTIONARYTOKEN);
current_raw_message.append(":");
current_raw_message.append(core::HexWord(dictionary));
current_raw_message.append("]");
uint32_t address = core::Get24LocalFromPC(
rom()->mutable_data(), kPointersDictionaries + (dictionary * 2));
uint32_t address_end = core::Get24LocalFromPC(
rom()->mutable_data(),
kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) {
parsed_message.push_back(rom()->data()[i]);
current_parsed_message.append(ParseTextDataByte(rom()->data()[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(current_byte)) {
std::string str = "";
str.push_back(CharEncoder.at(current_byte));
current_raw_message.append(str);
current_parsed_message.append(str);
parsed_message.push_back(current_byte);
}
}
} }
void MessageEditor::ReadAllTextData() { void MessageEditor::ReadAllTextData() {
int messageID = 0;
uint8_t current_byte;
int pos = kTextData; int pos = kTextData;
int message_id = 0;
uint8_t current_byte;
std::vector<uint8_t> temp_bytes_raw; std::vector<uint8_t> temp_bytes_raw;
std::vector<uint8_t> temp_bytes_parsed; std::vector<uint8_t> temp_bytes_parsed;
@@ -363,12 +374,12 @@ void MessageEditor::ReadAllTextData() {
while (true) { while (true) {
current_byte = rom()->data()[pos++]; current_byte = rom()->data()[pos++];
if (current_byte == MESSAGETERMINATOR) { if (current_byte == kMessageTerminator) {
auto message = auto message =
MessageData(messageID++, pos, current_message_raw, temp_bytes_raw, MessageData(message_id++, pos, current_message_raw, temp_bytes_raw,
current_message_parsed, temp_bytes_parsed); current_message_parsed, temp_bytes_parsed);
ListOfTexts.push_back(message); list_of_texts_.push_back(message);
temp_bytes_raw.clear(); temp_bytes_raw.clear();
temp_bytes_parsed.clear(); temp_bytes_parsed.clear();
@@ -407,7 +418,6 @@ void MessageEditor::ReadAllTextData() {
// Check for special characters. // Check for special characters.
text_element = FindMatchingSpecial(current_byte); text_element = FindMatchingSpecial(current_byte);
if (!text_element.Empty()) { if (!text_element.Empty()) {
current_message_raw.append(text_element.GetParameterizedToken()); current_message_raw.append(text_element.GetParameterizedToken());
current_message_parsed.append(text_element.GetParameterizedToken()); current_message_parsed.append(text_element.GetParameterizedToken());
@@ -422,13 +432,13 @@ void MessageEditor::ReadAllTextData() {
current_message_raw.append("["); current_message_raw.append("[");
current_message_raw.append(DICTIONARYTOKEN); current_message_raw.append(DICTIONARYTOKEN);
current_message_raw.append(":"); current_message_raw.append(":");
current_message_raw.append(core::UppercaseHexWord(dictionary)); current_message_raw.append(core::HexWord(dictionary));
current_message_raw.append("]"); current_message_raw.append("]");
uint32_t address = core::Get24LocalFromPC( uint32_t address = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + (dictionary * 2)); rom()->mutable_data(), kPointersDictionaries + (dictionary * 2));
uint32_t address_end = core::Get24LocalFromPC( uint32_t address_end = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2)); rom()->mutable_data(), kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) { for (uint32_t i = address; i < address_end; i++) {
temp_bytes_parsed.push_back(rom()->data()[i]); temp_bytes_parsed.push_back(rom()->data()[i]);
@@ -449,83 +459,29 @@ void MessageEditor::ReadAllTextData() {
} }
} }
TextElement MessageEditor::FindMatchingCommand(uint8_t b) { std::string ReplaceAllDictionaryWords(std::string str,
TextElement empty_element; std::vector<DictionaryEntry> dictionary) {
for (const auto text_element : TextCommands) { std::string temp = str;
if (text_element.ID == b) { for (const auto& entry : dictionary) {
return text_element; if (absl::StrContains(temp, entry.Contents)) {
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
} }
} }
return empty_element; return temp;
} }
TextElement MessageEditor::FindMatchingSpecial(uint8_t value) { DictionaryEntry MessageEditor::GetDictionaryFromID(uint8_t value) {
TextElement empty_element; if (value < 0 || value >= all_dictionaries_.size()) {
for (const auto text_element : SpecialChars) {
if (text_element.ID == value) {
return text_element;
}
}
return empty_element;
}
MessageEditor::DictionaryEntry MessageEditor::GetDictionaryFromID(
uint8_t value) {
if (value < 0 || value >= AllDictionaries.size()) {
return DictionaryEntry(); return DictionaryEntry();
} }
return AllDictionaries[value]; return all_dictionaries_[value];
}
uint8_t MessageEditor::FindDictionaryEntry(uint8_t value) {
if (value < DICTOFF || value == 0xFF) {
return -1;
}
return value - DICTOFF;
}
uint8_t MessageEditor::FindMatchingCharacter(char value) {
for (const auto [key, char_value] : CharEncoder) {
if (value == char_value) {
return key;
}
}
return 0xFF;
}
string MessageEditor::ParseTextDataByte(uint8_t value) {
if (CharEncoder.contains(value)) {
char c = CharEncoder.at(value);
string str = "";
str.push_back(c);
return str;
}
// Check for command.
TextElement textElement = FindMatchingCommand(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for special characters.
textElement = FindMatchingSpecial(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(value);
if (dictionary >= 0) {
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
}
return "";
} }
void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal, void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
int sizex, int sizey) { int sizex, int sizey) {
int drawid = srcx + (srcy * 32); const int num_x_tiles = 16;
const int img_width = 512; // (imgwidth/2)
int draw_id = srcx + (srcy * 32);
for (int yl = 0; yl < sizey * 8; yl++) { for (int yl = 0; yl < sizey * 8; yl++) {
for (int xl = 0; xl < 4; xl++) { for (int xl = 0; xl < 4; xl++) {
int mx = xl; int mx = xl;
@@ -533,8 +489,9 @@ void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
// Formula information to get tile index position in the array. // Formula information to get tile index position in the array.
// ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) )) // ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
int tx = ((drawid / 16) * 512) + ((drawid - ((drawid / 16) * 16)) * 4); int tx = ((draw_id / num_x_tiles) * img_width) +
uint8_t pixel = font_gfx16_data[tx + (yl * 64) + xl]; ((draw_id - ((draw_id / 16) * 16)) * 4);
uint8_t pixel = font_gfx16_data_[tx + (yl * 64) + xl];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel // nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position // position
@@ -552,7 +509,7 @@ void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
} }
} }
void MessageEditor::DrawStringToPreview(string str) { void MessageEditor::DrawStringToPreview(std::string str) {
for (const auto c : str) { for (const auto c : str) {
DrawCharacterToPreview(c); DrawCharacterToPreview(c);
} }
@@ -573,25 +530,25 @@ void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
int srcy = value / 16; int srcy = value / 16;
int srcx = value - (value & (~0xF)); int srcx = value - (value & (~0xF));
if (text_pos >= 170) { if (text_position_ >= 170) {
text_pos = 0; text_position_ = 0;
text_line++; text_line_++;
} }
DrawTileToPreview(text_pos, text_line * 16, srcx, srcy, 0, 1, 2); DrawTileToPreview(text_position_, text_line_ * 16, srcx, srcy, 0, 1, 2);
text_pos += width_array[value]; text_position_ += width_array[value];
} else if (value == kLine1) { } else if (value == kLine1) {
text_pos = 0; text_position_ = 0;
text_line = 0; text_line_ = 0;
} else if (value == kScrollVertical) { } else if (value == kScrollVertical) {
text_pos = 0; text_position_ = 0;
text_line += 1; text_line_ += 1;
} else if (value == kLine2) { } else if (value == kLine2) {
text_pos = 0; text_position_ = 0;
text_line = 1; text_line_ = 1;
} else if (value == kLine3) { } else if (value == kLine3) {
text_pos = 0; text_position_ = 0;
text_line = 2; text_line_ = 2;
} else if (value == 0x6B || value == 0x6D || value == 0x6E || } else if (value == 0x6B || value == 0x6D || value == 0x6E ||
value == 0x77 || value == 0x78 || value == 0x79 || value == 0x77 || value == 0x78 || value == 0x79 ||
value == 0x7A) { value == 0x7A) {
@@ -615,15 +572,15 @@ void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
} }
} }
void MessageEditor::DrawMessagePreview() // From Parsing. void MessageEditor::DrawMessagePreview() {
{ // From Parsing.
text_line = 0; text_line_ = 0;
for (int i = 0; i < (172 * 4096); i++) { for (int i = 0; i < (172 * 4096); i++) {
current_font_gfx16_data_[i] = 0; current_font_gfx16_data_[i] = 0;
} }
text_pos = 0; text_position_ = 0;
DrawCharacterToPreview(CurrentMessage.Data); DrawCharacterToPreview(current_message_.Data);
shown_lines = 0; shown_lines_ = 0;
} }
absl::Status MessageEditor::Cut() { absl::Status MessageEditor::Cut() {
@@ -675,7 +632,7 @@ absl::Status MessageEditor::Save() {
int pos = kTextData; int pos = kTextData;
bool in_second_bank = false; bool in_second_bank = false;
for (const auto& message : ListOfTexts) { for (const auto& message : list_of_texts_) {
for (const auto value : message.Data) { for (const auto value : message.Data) {
RETURN_IF_ERROR(rom()->Write(pos, value)); RETURN_IF_ERROR(rom()->Write(pos, value));
@@ -695,7 +652,7 @@ absl::Status MessageEditor::Save() {
} }
RETURN_IF_ERROR( RETURN_IF_ERROR(
rom()->Write(pos++, MESSAGETERMINATOR)); // , true, "Terminator text" rom()->Write(pos++, kMessageTerminator)); // , true, "Terminator text"
} }
// Verify that we didn't go over the space available for the second block. // Verify that we didn't go over the space available for the second block.
@@ -712,9 +669,10 @@ absl::Status MessageEditor::Save() {
std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) { std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2; int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
string bankSTR = bank ? "1st" : "2nd"; std::string bankSTR = bank ? "1st" : "2nd";
string posSTR = bank ? absl::StrFormat("%X4", pos & 0xFFFF) std::string posSTR =
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF); bank ? absl::StrFormat("%X4", pos & 0xFFFF)
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
std::string message = absl::StrFormat( std::string message = absl::StrFormat(
"There is too much text data in the %s block to save.\n" "There is too much text data in the %s block to save.\n"
"Available: %X4 | Used: %s", "Available: %X4 | Used: %s",
@@ -722,30 +680,6 @@ std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
return message; return message;
} }
// push_backs a command to the text field when the push_back command button is
// pressed or the command is double clicked in the list.
void MessageEditor::InsertCommandButton_Click_1() {
// InsertSelectedText(
// TextCommands[TextCommandList.SelectedIndex].GetParameterizedToken(
// (uint8_t)ParamsBox.HexValue));
}
// push_backs a special character to the text field when the push_back command
// button is pressed or the character is double clicked in the list.
void MessageEditor::InsertSpecialButton_Click() {
// InsertSelectedText(
// SpecialChars[SpecialsList.SelectedIndex].GetParameterizedToken());
}
void MessageEditor::InsertSelectedText(string str) {
int textboxPos = message_text_box_.selection_start;
from_form = true;
// message_text_box_.Text = message_text_box_.Text.Insert(textboxPos, str);
from_form = false;
message_text_box_.selection_start = textboxPos + str.size();
message_text_box_.Focus();
}
void MessageEditor::Delete() { void MessageEditor::Delete() {
// Determine if any text is selected in the TextBox control. // Determine if any text is selected in the TextBox control.
if (message_text_box_.selection_length == 0) { if (message_text_box_.selection_length == 0) {
@@ -766,5 +700,4 @@ void MessageEditor::SelectAll() {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,229 +1,53 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H #ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H #define YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#include <iostream>
#include <regex>
#include <sstream>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "app/editor/message/message_data.h" #include "app/editor/message/message_data.h"
#include "app/editor/utils/editor.h" #include "app/editor/editor.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using std::string; constexpr int kGfxFont = 0x70000; // 2bpp format
constexpr int kTextData2 = 0x75F40;
// TEXT EDITOR RELATED CONSTANTS constexpr int kTextData2End = 0x773FF;
const int kGfxFont = 0x70000; // 2bpp format constexpr int kCharactersWidth = 0x74ADF;
const int kTextData = 0xE0000; constexpr int kNumMessages = 396;
const int kTextDataEnd = 0xE7FFF; constexpr int kCurrentMessageWidth = 172;
const int kTextData2 = 0x75F40; constexpr int kCurrentMessageHeight = 4096;
const int kTextData2End = 0x773FF; constexpr int kFontGfxMessageSize = 128;
const int kPointersDictionaries = 0x74703; constexpr int kFontGfxMessageDepth = 8;
const int kCharactersWidth = 0x74ADF;
const string DICTIONARYTOKEN = "D";
const uint8_t DICTOFF = 0x88;
const string BANKToken = "BANK";
const uint8_t BANKID = 0x80;
constexpr uint8_t kWidthArraySize = 100;
constexpr uint8_t kBlockTerminator = 0x80; constexpr uint8_t kBlockTerminator = 0x80;
constexpr uint8_t kMessageBankChangeId = 0x80;
static std::vector<uint8_t> ParseMessageToData(string str);
static ParsedElement FindMatchingElement(string str);
constexpr uint8_t kScrollVertical = 0x73; constexpr uint8_t kScrollVertical = 0x73;
constexpr uint8_t kLine1 = 0x74; constexpr uint8_t kLine1 = 0x74;
constexpr uint8_t kLine2 = 0x75; constexpr uint8_t kLine2 = 0x75;
constexpr uint8_t kLine3 = 0x76; constexpr uint8_t kLine3 = 0x76;
static const TextElement TextCommands[] = {
TextElement(0x6B, "W", true, "Window border"),
TextElement(0x6D, "P", true, "Window position"),
TextElement(0x6E, "SPD", true, "Scroll speed"),
TextElement(0x7A, "S", true, "Text draw speed"),
TextElement(0x77, "C", true, "Text color"),
TextElement(0x6A, "L", false, "Player name"),
TextElement(0x74, "1", false, "Line 1"),
TextElement(0x75, "2", false, "Line 2"),
TextElement(0x76, "3", false, "Line 3"),
TextElement(0x7E, "K", false, "Wait for key"),
TextElement(0x73, "V", false, "Scroll text"),
TextElement(0x78, "WT", true, "Delay X"),
TextElement(0x6C, "N", true, "BCD number"),
TextElement(0x79, "SFX", true, "Sound effect"),
TextElement(0x71, "CH3", false, "Choose 3"),
TextElement(0x72, "CH2", false, "Choose 2 high"),
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
TextElement(0x69, "CHI", false, "Choose item"),
TextElement(0x67, "IMG", false, "Next attract image"),
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
TextElement(0x70, "NONO", false, "Crash"),
};
static std::vector<TextElement> SpecialChars = {
TextElement(0x43, "...", false, "Ellipsis …"),
TextElement(0x4D, "UP", false, "Arrow ↑"),
TextElement(0x4E, "DOWN", false, "Arrow ↓"),
TextElement(0x4F, "LEFT", false, "Arrow ←"),
TextElement(0x50, "RIGHT", false, "Arrow →"),
TextElement(0x5B, "A", false, "Button Ⓐ"),
TextElement(0x5C, "B", false, "Button Ⓑ"),
TextElement(0x5D, "X", false, "Button ⓧ"),
TextElement(0x5E, "Y", false, "Button ⓨ"),
TextElement(0x52, "HP1L", false, "1 HP left"),
TextElement(0x53, "HP1R", false, "1 HP right"),
TextElement(0x54, "HP2L", false, "2 HP left"),
TextElement(0x55, "HP3L", false, "3 HP left"),
TextElement(0x56, "HP3R", false, "3 HP right"),
TextElement(0x57, "HP4L", false, "4 HP left"),
TextElement(0x58, "HP4R", false, "4 HP right"),
TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
TextElement(0x4A, "LFL", false, "Link face left"),
TextElement(0x4B, "LFR", false, "Link face right"),
};
static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
{0x00, 'A'},
{0x01, 'B'},
{0x02, 'C'},
{0x03, 'D'},
{0x04, 'E'},
{0x05, 'F'},
{0x06, 'G'},
{0x07, 'H'},
{0x08, 'I'},
{0x09, 'J'},
{0x0A, 'K'},
{0x0B, 'L'},
{0x0C, 'M'},
{0x0D, 'N'},
{0x0E, 'O'},
{0x0F, 'P'},
{0x10, 'Q'},
{0x11, 'R'},
{0x12, 'S'},
{0x13, 'T'},
{0x14, 'U'},
{0x15, 'V'},
{0x16, 'W'},
{0x17, 'X'},
{0x18, 'Y'},
{0x19, 'Z'},
{0x1A, 'a'},
{0x1B, 'b'},
{0x1C, 'c'},
{0x1D, 'd'},
{0x1E, 'e'},
{0x1F, 'f'},
{0x20, 'g'},
{0x21, 'h'},
{0x22, 'i'},
{0x23, 'j'},
{0x24, 'k'},
{0x25, 'l'},
{0x26, 'm'},
{0x27, 'n'},
{0x28, 'o'},
{0x29, 'p'},
{0x2A, 'q'},
{0x2B, 'r'},
{0x2C, 's'},
{0x2D, 't'},
{0x2E, 'u'},
{0x2F, 'v'},
{0x30, 'w'},
{0x31, 'x'},
{0x32, 'y'},
{0x33, 'z'},
{0x34, '0'},
{0x35, '1'},
{0x36, '2'},
{0x37, '3'},
{0x38, '4'},
{0x39, '5'},
{0x3A, '6'},
{0x3B, '7'},
{0x3C, '8'},
{0x3D, '9'},
{0x3E, '!'},
{0x3F, '?'},
{0x40, '-'},
{0x41, '.'},
{0x42, ','},
{0x44, '>'},
{0x45, '('},
{0x46, ')'},
{0x4C, '"'},
{0x51, '\''},
{0x59, ' '},
{0x5A, '<'},
// {0x5F, '¡'}, {0x60, '¡'}, {0x61, '¡'}, {0x62, ' '}, {0x63, ' '}, {0x64,
// ' '},
{0x65, ' '},
{0x66, '_'},
};
static TextElement DictionaryElement = static TextElement DictionaryElement =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary"); TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
class MessageEditor : public Editor, public SharedRom { class MessageEditor : public Editor, public SharedRom {
public: public:
struct DictionaryEntry {
uint8_t ID;
std::string Contents;
std::vector<uint8_t> Data;
int Length;
std::string Token;
DictionaryEntry() = default;
DictionaryEntry(uint8_t i, std::string s)
: Contents(s), ID(i), Length(s.length()) {
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
Data = ParseMessageToData(Contents);
}
bool ContainedInString(std::string s) {
return s.find(Contents) != std::string::npos;
}
std::string ReplaceInstancesOfIn(std::string s) {
std::string replacedString = s;
size_t pos = replacedString.find(Contents);
while (pos != std::string::npos) {
replacedString.replace(pos, Contents.length(), Token);
pos = replacedString.find(Contents, pos + Token.length());
}
return replacedString;
}
};
MessageEditor() { type_ = EditorType::kMessage; } MessageEditor() { type_ = EditorType::kMessage; }
absl::Status Initialize();
absl::Status Update() override; absl::Status Update() override;
void DrawMessageList(); void DrawMessageList();
void DrawCurrentMessage(); void DrawCurrentMessage();
void DrawTextCommands(); void DrawTextCommands();
void DrawDictionary();
absl::Status Initialize(); void ReadAllTextDataV2();
void ReadAllTextData(); [[deprecated]] void ReadAllTextData();
void BuildDictionaryEntries();
absl::Status Cut() override; absl::Status Cut() override;
absl::Status Copy() override; absl::Status Copy() override;
@@ -238,67 +62,47 @@ class MessageEditor : public Editor, public SharedRom {
absl::Status Save(); absl::Status Save();
void Delete(); void Delete();
void SelectAll(); void SelectAll();
// void RegisterTests(ImGuiTestEngine* e) override;
TextElement FindMatchingCommand(uint8_t byte);
TextElement FindMatchingSpecial(uint8_t value);
string ParseTextDataByte(uint8_t value);
DictionaryEntry GetDictionaryFromID(uint8_t value); DictionaryEntry GetDictionaryFromID(uint8_t value);
static uint8_t FindDictionaryEntry(uint8_t value);
static uint8_t FindMatchingCharacter(char value);
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal, void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
int sizex = 1, int sizey = 1); int sizex = 1, int sizey = 1);
void DrawCharacterToPreview(char c); void DrawCharacterToPreview(char c);
void DrawCharacterToPreview(const std::vector<uint8_t>& text); void DrawCharacterToPreview(const std::vector<uint8_t>& text);
void DrawStringToPreview(string str); void DrawStringToPreview(std::string str);
void DrawMessagePreview(); void DrawMessagePreview();
std::string DisplayTextOverflowError(int pos, bool bank); std::string DisplayTextOverflowError(int pos, bool bank);
void InsertCommandButton_Click_1();
void InsertSpecialButton_Click();
void InsertSelectedText(string str);
static const std::vector<DictionaryEntry> AllDicts;
uint8_t width_array[100];
string romname = "";
int text_line = 0;
int text_pos = 0;
int shown_lines = 0;
int selected_tile = 0;
bool skip_next = false;
bool from_form = false;
std::vector<MessageData> ListOfTexts;
std::vector<MessageData> DisplayedMessages;
std::vector<std::string> ParsedMessages;
MessageData CurrentMessage;
private: private:
static const TextElement DictionaryElement; bool skip_next = false;
bool data_loaded_ = false; bool data_loaded_ = false;
int current_message_id_ = 0;
int text_line_ = 0;
int text_position_ = 0;
int shown_lines_ = 0;
uint8_t width_array[kWidthArraySize];
std::string search_text_ = ""; std::string search_text_ = "";
std::vector<uint8_t> font_gfx16_data_;
std::vector<uint8_t> current_font_gfx16_data_;
std::vector<std::string> parsed_messages_;
std::vector<MessageData> list_of_texts_;
std::vector<DictionaryEntry> all_dictionaries_;
MessageData current_message_;
gfx::Bitmap font_gfx_bitmap_;
gfx::Bitmap current_font_gfx16_bitmap_;
gfx::SnesPalette font_preview_colors_;
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)}; gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)};
gui::Canvas current_font_gfx16_canvas_{"##CurrentMessageGfx", gui::Canvas current_font_gfx16_canvas_{"##CurrentMessageGfx",
ImVec2(172, 4096)}; ImVec2(172, 4096)};
gfx::Bitmap font_gfx_bitmap_;
gfx::Bitmap current_font_gfx16_bitmap_;
Bytes font_gfx16_data;
Bytes current_font_gfx16_data_;
gfx::SnesPalette font_preview_colors_;
struct TextBox { struct TextBox {
std::string text; std::string text;
std::string buffer; std::string buffer;
@@ -357,10 +161,7 @@ class MessageEditor : public Editor, public SharedRom {
TextBox message_text_box_; TextBox message_text_box_;
}; };
static std::vector<MessageEditor::DictionaryEntry> AllDictionaries;
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_MESSAGE_EDITOR_H #endif // YAZE_APP_EDITOR_MESSAGE_EDITOR_H

View File

@@ -1,7 +0,0 @@
#include "message_editor.h"
namespace yaze {
namespace app {
namespace editor {} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -9,7 +9,6 @@
#include "app/gui/input.h" #include "app/gui/input.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
absl::Status MusicEditor::Update() { absl::Status MusicEditor::Update() {
@@ -218,5 +217,4 @@ void MusicEditor::DrawToolset() {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,22 +1,17 @@
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H #ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H #define YAZE_APP_EDITOR_MUSIC_EDITOR_H
#include "imgui/imgui.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/editor/code/assembly_editor.h" #include "app/editor/code/assembly_editor.h"
#include "app/editor/utils/editor.h" #include "app/editor/editor.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/music/tracker.h" #include "app/zelda3/music/tracker.h"
// #include "snes_spc/demo/demo_util.h" #include "imgui/imgui.h"
// #include "snes_spc/demo/wave_writer.h"
// #include "snes_spc/snes_spc/spc.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
static const char* kGameSongs[] = {"Title", static const char* kGameSongs[] = {"Title",
@@ -81,24 +76,14 @@ class MusicEditor : public SharedRom, public Editor {
zelda3::music::Tracker music_tracker_; zelda3::music::Tracker music_tracker_;
// Mix_Music* current_song_ = NULL;
AssemblyEditor assembly_editor_; AssemblyEditor assembly_editor_;
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit; ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_Resizable | ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable; 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 editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

View File

@@ -1,14 +1,13 @@
#include "app/editor/overworld/entity.h" #include "app/editor/overworld/entity.h"
#include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/style.h" #include "app/gui/style.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using ImGui::BeginChild; using ImGui::BeginChild;
using ImGui::BeginGroup;
using ImGui::Button; using ImGui::Button;
using ImGui::Checkbox; using ImGui::Checkbox;
using ImGui::EndChild; using ImGui::EndChild;
@@ -16,7 +15,9 @@ using ImGui::SameLine;
using ImGui::Selectable; using ImGui::Selectable;
using ImGui::Text; using ImGui::Text;
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity, constexpr float kInputFieldSize = 30.f;
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity,
ImVec2 canvas_p0, ImVec2 scrolling) { ImVec2 canvas_p0, ImVec2 scrolling) {
// Get the mouse position relative to the canvas // Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO(); const ImGuiIO &io = ImGui::GetIO();
@@ -31,7 +32,7 @@ bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
return false; return false;
} }
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0, void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool free_movement) { ImVec2 scrolling, bool free_movement) {
// Get the mouse position relative to the canvas // Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO(); const ImGuiIO &io = ImGui::GetIO();
@@ -51,19 +52,20 @@ void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
entity->set_y(new_y); entity->set_y(new_y);
} }
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0, void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool &is_dragging_entity, ImVec2 scrolling, bool &is_dragging_entity,
zelda3::OverworldEntity *&dragged_entity, zelda3::GameEntity *&dragged_entity,
zelda3::OverworldEntity *&current_entity, zelda3::GameEntity *&current_entity,
bool free_movement) { bool free_movement) {
std::string entity_type = "Entity"; std::string entity_type = "Entity";
if (entity->type_ == zelda3::OverworldEntity::EntityType::kExit) { if (entity->entity_type_ == zelda3::GameEntity::EntityType::kExit) {
entity_type = "Exit"; entity_type = "Exit";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kEntrance) { } else if (entity->entity_type_ ==
zelda3::GameEntity::EntityType::kEntrance) {
entity_type = "Entrance"; entity_type = "Entrance";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kSprite) { } else if (entity->entity_type_ == zelda3::GameEntity::EntityType::kSprite) {
entity_type = "Sprite"; entity_type = "Sprite";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kItem) { } else if (entity->entity_type_ == zelda3::GameEntity::EntityType::kItem) {
entity_type = "Item"; entity_type = "Item";
} }
const auto is_hovering = const auto is_hovering =
@@ -87,9 +89,9 @@ void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
} else if (is_dragging_entity && dragged_entity == entity) { } else if (is_dragging_entity && dragged_entity == entity) {
if (ImGui::BeginDragDropSource()) { if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity, ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
sizeof(zelda3::OverworldEntity)); sizeof(zelda3::GameEntity));
Text("Moving %s ID: %s", entity_type.c_str(), Text("Moving %s ID: %s", entity_type.c_str(),
core::UppercaseHexByte(entity->entity_id_).c_str()); core::HexByte(entity->entity_id_).c_str());
ImGui::EndDragDropSource(); ImGui::EndDragDropSource();
} }
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement); MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
@@ -125,14 +127,14 @@ bool DrawEntranceInserterPopup() {
// TODO: Implement deleting OverworldEntrance objects, currently only hides them // TODO: Implement deleting OverworldEntrance objects, currently only hides them
bool DrawOverworldEntrancePopup( bool DrawOverworldEntrancePopup(
zelda3::overworld::OverworldEntrance &entrance) { zelda3::OverworldEntrance &entrance) {
static bool set_done = false; static bool set_done = false;
if (set_done) { if (set_done) {
set_done = false; set_done = false;
} }
if (ImGui::BeginPopupModal("Entrance editor", NULL, if (ImGui::BeginPopupModal("Entrance editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) { ImGuiWindowFlags_AlwaysAutoResize)) {
gui::InputHex("Map ID", &entrance.map_id_); gui::InputHexWord("Map ID", &entrance.map_id_);
gui::InputHexByte("Entrance ID", &entrance.entrance_id_, gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
kInputFieldSize + 20); kInputFieldSize + 20);
gui::InputHex("X", &entrance.x_); gui::InputHex("X", &entrance.x_);
@@ -175,7 +177,7 @@ void DrawExitInserterPopup() {
} }
} }
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) { bool DrawExitEditorPopup(zelda3::OverworldExit &exit) {
static bool set_done = false; static bool set_done = false;
if (set_done) { if (set_done) {
set_done = false; set_done = false;
@@ -209,7 +211,7 @@ bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) {
gui::InputHexWord("Room", &exit.room_id_); gui::InputHexWord("Room", &exit.room_id_);
SameLine(); SameLine();
gui::InputHex("Entity ID", &exit.entity_id_, 4); gui::InputHex("Entity ID", &exit.entity_id_, 4);
gui::InputHex("Map", &exit.map_id_); gui::InputHexWord("Map", &exit.map_id_);
SameLine(); SameLine();
Checkbox("Automatic", &exit.is_automatic_); Checkbox("Automatic", &exit.is_automatic_);
@@ -310,12 +312,12 @@ bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) {
void DrawItemInsertPopup() { void DrawItemInsertPopup() {
// Contents of the Context Menu // Contents of the Context Menu
if (ImGui::BeginPopup("Item Inserter")) { if (ImGui::BeginPopup("Item Inserter")) {
static int new_item_id = 0; static size_t new_item_id = 0;
Text("Add Item"); Text("Add Item");
BeginChild("ScrollRegion", ImVec2(150, 150), true, BeginChild("ScrollRegion", ImVec2(150, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGuiWindowFlags_AlwaysVerticalScrollbar);
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) { for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(), if (Selectable(zelda3::kSecretItemNames[i].c_str(),
i == new_item_id)) { i == new_item_id)) {
new_item_id = i; new_item_id = i;
} }
@@ -338,7 +340,7 @@ void DrawItemInsertPopup() {
} }
// TODO: Implement deleting OverworldItem objects, currently only hides them // TODO: Implement deleting OverworldItem objects, currently only hides them
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item) { bool DrawItemEditorPopup(zelda3::OverworldItem &item) {
static bool set_done = false; static bool set_done = false;
if (set_done) { if (set_done) {
set_done = false; set_done = false;
@@ -348,10 +350,10 @@ bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item) {
BeginChild("ScrollRegion", ImVec2(150, 150), true, BeginChild("ScrollRegion", ImVec2(150, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::BeginGroup(); ImGui::BeginGroup();
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) { for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(), if (Selectable(zelda3::kSecretItemNames[i].c_str(),
item.id == i)) { item.id_ == i)) {
item.id = i; item.id_ = i;
} }
} }
ImGui::EndGroup(); ImGui::EndGroup();
@@ -384,7 +386,7 @@ void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
// Initialize items if empty // Initialize items if empty
if (items.empty()) { if (items.empty()) {
for (int i = 0; i < 256; ++i) { for (int i = 0; i < 256; ++i) {
items.push_back(SpriteItem{i, core::kSpriteDefaultNames[i].data()}); items.push_back(SpriteItem{i, zelda3::kSpriteDefaultNames[i].data()});
} }
} }
@@ -486,5 +488,4 @@ bool DrawSpriteEditorPopup(zelda3::Sprite &sprite) {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -3,35 +3,33 @@
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "app/editor/overworld_editor.h"
#include "app/zelda3/common.h" #include "app/zelda3/common.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity, bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity,
ImVec2 canvas_p0, ImVec2 scrolling); ImVec2 canvas_p0, ImVec2 scrolling);
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0, void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool free_movement = false); ImVec2 scrolling, bool free_movement = false);
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0, void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool &is_dragging_entity, ImVec2 scrolling, bool &is_dragging_entity,
zelda3::OverworldEntity *&dragged_entity, zelda3::GameEntity *&dragged_entity,
zelda3::OverworldEntity *&current_entity, zelda3::GameEntity *&current_entity,
bool free_movement = false); bool free_movement = false);
bool DrawEntranceInserterPopup(); bool DrawEntranceInserterPopup();
bool DrawOverworldEntrancePopup(zelda3::overworld::OverworldEntrance &entrance); bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance);
void DrawExitInserterPopup(); void DrawExitInserterPopup();
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit); bool DrawExitEditorPopup(zelda3::OverworldExit &exit);
void DrawItemInsertPopup(); void DrawItemInsertPopup();
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item); bool DrawItemEditorPopup(zelda3::OverworldItem &item);
enum MyItemColumnID { enum MyItemColumnID {
MyItemColumnID_ID, MyItemColumnID_ID,
@@ -82,7 +80,6 @@ void DrawSpriteInserterPopup();
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite); bool DrawSpriteEditorPopup(zelda3::Sprite &sprite);
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H #endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H

View File

@@ -1,48 +1,35 @@
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H #ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#define 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/status.h"
#include "absl/status/statusor.h" #include "app/editor/editor.h"
#include "absl/strings/str_format.h"
#include "app/core/common.h"
#include "app/editor/graphics/gfx_group_editor.h" #include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/tile16_editor.h" #include "app/editor/graphics/tile16_editor.h"
#include "app/editor/overworld/entity.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/input.h"
#include "app/gui/zeml.h" #include "app/gui/zeml.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
static constexpr uint k4BPP = 4; constexpr uint k4BPP = 4;
static constexpr uint kByteSize = 3; constexpr uint kByteSize = 3;
static constexpr uint kMessageIdSize = 5; constexpr uint kMessageIdSize = 5;
static constexpr uint kNumSheetsToLoad = 223; constexpr uint kNumSheetsToLoad = 223;
static constexpr uint kTile8DisplayHeight = 64; constexpr uint kTile8DisplayHeight = 64;
static constexpr float kInputFieldSize = 30.f; constexpr uint kOverworldMapSize = 0x200;
constexpr float kInputFieldSize = 30.f;
static constexpr absl::string_view kToolsetColumnNames[] = { constexpr ImVec2 kOverworldCanvasSize(kOverworldMapSize * 8,
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool", kOverworldMapSize * 8);
"#zoomInTool", "#separator", "#drawTool", "#history", constexpr ImVec2 kCurrentGfxCanvasSize(0x100 + 1, 0x10 * 0x40 + 1);
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool", constexpr ImVec2 kBlocksetCanvasSize(0x100 + 1, 0x4000 + 1);
"#transportTool", "#musicTool", "#separator3", "#tilemapTool", constexpr ImVec2 kGraphicsBinCanvasSize(0x100 + 1, kNumSheetsToLoad * 0x40 + 1);
"propertiesTool"};
constexpr ImGuiTableFlags kOWMapFlags = constexpr ImGuiTableFlags kOWMapFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable; ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
@@ -52,6 +39,14 @@ constexpr ImGuiTableFlags kOWEditFlags =
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV; ImGuiTableFlags_BordersV;
static constexpr absl::string_view kToolsetColumnNames[] = {
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
"#zoomInTool", "#separator", "#drawTool", "#history",
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
"propertiesTool", "#separator4", "#experimentalTool", "#properties",
"#separator5"};
constexpr absl::string_view kWorldList = constexpr absl::string_view kWorldList =
"Light World\0Dark World\0Extra World\0"; "Light World\0Dark World\0Extra World\0";
@@ -61,28 +56,6 @@ constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
constexpr absl::string_view kOWEditTable = "##OWEditTable"; constexpr absl::string_view kOWEditTable = "##OWEditTable";
constexpr absl::string_view kOWMapTable = "#MapSettingsTable"; constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
class EntranceContext {
public:
absl::Status LoadEntranceTileTypes(Rom& rom) {
int offset_low = 0xDB8BF;
int offset_high = 0xDB917;
for (int i = 0; i < 0x2C; i++) {
// Load entrance tile types
ASSIGN_OR_RETURN(auto value_low, rom.ReadWord(offset_low + i));
entrance_tile_types_low_.push_back(value_low);
ASSIGN_OR_RETURN(auto value_high, rom.ReadWord(offset_high + i));
entrance_tile_types_low_.push_back(value_high);
}
return absl::OkStatus();
}
private:
std::vector<uint16_t> entrance_tile_types_low_;
std::vector<uint16_t> entrance_tile_types_high_;
};
/** /**
* @class OverworldEditor * @class OverworldEditor
* @brief Manipulates the Overworld and OverworldMap data in a Rom. * @brief Manipulates the Overworld and OverworldMap data in a Rom.
@@ -99,29 +72,25 @@ class EntranceContext {
* Provides access to the GfxGroupEditor and Tile16Editor through popup windows. * Provides access to the GfxGroupEditor and Tile16Editor through popup windows.
* *
*/ */
class OverworldEditor : public Editor, class OverworldEditor : public Editor, public gfx::GfxContext {
public SharedRom,
public context::GfxContext,
public EntranceContext,
public core::ExperimentFlags {
public: public:
OverworldEditor() { type_ = EditorType::kOverworld; } OverworldEditor(Rom& rom) : rom_(rom) { type_ = EditorType::kOverworld; }
void InitializeZeml(); void Initialize();
absl::Status Update() final; absl::Status Update() final;
absl::Status Undo() { return absl::UnimplementedError("Undo"); } absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() { return absl::UnimplementedError("Redo"); } absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() { return absl::UnimplementedError("Cut"); } absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() { return absl::UnimplementedError("Copy"); } absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() { return absl::UnimplementedError("Paste"); } absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() { return absl::UnimplementedError("Find Unused Tiles"); } absl::Status Find() override {
return absl::UnimplementedError("Find Unused Tiles");
}
absl::Status Save();
auto overworld() { return &overworld_; } auto overworld() { return &overworld_; }
/**
* @brief
*/
int jump_to_tab() { return jump_to_tab_; } int jump_to_tab() { return jump_to_tab_; }
int jump_to_tab_ = -1; int jump_to_tab_ = -1;
@@ -135,11 +104,26 @@ class OverworldEditor : public Editor,
absl::Status LoadGraphics(); absl::Status LoadGraphics();
private: private:
absl::Status UpdateFullscreenCanvas(); /**
* @brief Draws the canvas, tile16 selector, and toolset in fullscreen
*/
void DrawFullscreenCanvas();
absl::Status DrawToolset(); /**
* @brief Toolset for entrances, exits, items, sprites, and transports.
*/
void DrawToolset();
/**
* @brief Draws the overworld map settings. Graphics, palettes, etc.
*/
void DrawOverworldMapSettings(); void DrawOverworldMapSettings();
/**
* @brief Draw the overworld settings for ZSCustomOverworld.
*/
void DrawCustomOverworldMapSettings();
void RefreshChildMap(int i); void RefreshChildMap(int i);
void RefreshOverworldMap(); void RefreshOverworldMap();
absl::Status RefreshMapPalette(); absl::Status RefreshMapPalette();
@@ -155,9 +139,28 @@ class OverworldEditor : public Editor,
void DrawOverworldMaps(); void DrawOverworldMaps();
void DrawOverworldEdits(); void DrawOverworldEdits();
void RenderUpdatedMapBitmap(const ImVec2& click_position, void RenderUpdatedMapBitmap(const ImVec2& click_position,
const Bytes& tile_data); const std::vector<uint8_t>& tile_data);
/**
* @brief Check for changes to the overworld map.
*
* This function either draws the tile painter with the current tile16 or
* group of tile16 data with ow_map_canvas_ and DrawOverworldEdits or it
* checks for left mouse button click/drag to select a tile16 or group of
* tile16 data from the overworld map canvas. Similar to ZScream selection.
*/
void CheckForOverworldEdits(); void CheckForOverworldEdits();
/**
* @brief Draw and create the tile16 IDs that are currently selected.
*/
void CheckForSelectRectangle(); void CheckForSelectRectangle();
/**
* @brief Check for changes to the overworld map. Calls RefreshOverworldMap
* and RefreshTile16Blockset on the current map if it is modified and is
* actively being edited.
*/
absl::Status CheckForCurrentMap(); absl::Status CheckForCurrentMap();
void CheckForMousePan(); void CheckForMousePan();
@@ -175,17 +178,10 @@ class OverworldEditor : public Editor,
void DrawOverworldProperties(); void DrawOverworldProperties();
absl::Status DrawExperimentalModal();
absl::Status UpdateUsageStats(); absl::Status UpdateUsageStats();
void DrawUsageGrid(); void DrawUsageGrid();
void CalculateUsageStats();
absl::Status LoadAnimatedMaps();
void DrawDebugWindow(); void DrawDebugWindow();
auto gfx_group_editor() const { return gfx_group_editor_; }
enum class EditingMode { enum class EditingMode {
DRAW_TILE, DRAW_TILE,
ENTRANCES, ENTRANCES,
@@ -200,84 +196,57 @@ class OverworldEditor : public Editor,
EditingMode current_mode = EditingMode::DRAW_TILE; EditingMode current_mode = EditingMode::DRAW_TILE;
EditingMode previous_mode = EditingMode::DRAW_TILE; EditingMode previous_mode = EditingMode::DRAW_TILE;
enum OverworldProperty {
LW_AREA_GFX,
DW_AREA_GFX,
LW_AREA_PAL,
DW_AREA_PAL,
LW_SPR_GFX_PART1,
LW_SPR_GFX_PART2,
DW_SPR_GFX_PART1,
DW_SPR_GFX_PART2,
LW_SPR_PAL_PART1,
LW_SPR_PAL_PART2,
DW_SPR_PAL_PART1,
DW_SPR_PAL_PART2,
};
int current_world_ = 0; int current_world_ = 0;
int current_map_ = 0; int current_map_ = 0;
int current_parent_ = 0; int current_parent_ = 0;
int current_entrance_id_ = 0;
int current_exit_id_ = 0;
int current_item_id_ = 0;
int current_sprite_id_ = 0;
int current_blockset_ = 0;
int game_state_ = 1; int game_state_ = 1;
int current_tile16_ = 0; int current_tile16_ = 0;
int selected_tile_ = 0;
int current_blockset_ = 0;
int selected_entrance_ = 0; int selected_entrance_ = 0;
int selected_usage_map_ = 0xFFFF; int selected_usage_map_ = 0xFFFF;
char map_gfx_[3] = "";
char map_palette_[3] = "";
char spr_gfx_[3] = "";
char spr_palette_[3] = "";
char message_id_[5] = "";
char staticgfx[16];
uint32_t tilemap_file_offset_high_ = 0;
uint32_t tilemap_file_offset_low_ = 0;
uint32_t light_maps_to_load_ = 0x51;
uint32_t dark_maps_to_load_ = 0x2A;
uint32_t sp_maps_to_load_ = 0x07;
bool opt_enable_grid = true;
bool all_gfx_loaded_ = false; bool all_gfx_loaded_ = false;
bool map_blockset_loaded_ = false; bool map_blockset_loaded_ = false;
bool selected_tile_loaded_ = false; bool selected_tile_loaded_ = false;
bool update_selected_tile_ = true;
bool is_dragging_entrance_ = false;
bool show_tile16_editor_ = false; bool show_tile16_editor_ = false;
bool show_gfx_group_editor_ = false;
bool overworld_canvas_fullscreen_ = false; bool overworld_canvas_fullscreen_ = false;
bool middle_mouse_dragging_ = false; bool middle_mouse_dragging_ = false;
bool is_dragging_entity_ = false; bool is_dragging_entity_ = false;
zelda3::OverworldEntity* dragged_entity_;
zelda3::OverworldEntity* current_entity_;
int current_entrance_id_ = 0; std::vector<uint8_t> selected_tile_data_;
zelda3::overworld::OverworldEntrance current_entrance_; std::vector<std::vector<uint8_t>> tile16_individual_data_;
int current_exit_id_ = 0;
zelda3::overworld::OverworldExit current_exit_;
int current_item_id_ = 0;
zelda3::overworld::OverworldItem current_item_;
int current_sprite_id_ = 0;
zelda3::Sprite current_sprite_;
bool show_experimental = false;
std::string ow_tilemap_filename_ = "";
std::string tile32_configuration_filename_ = "";
Bytes selected_tile_data_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_; std::vector<gfx::Bitmap> tile16_individual_;
std::vector<Bytes> tile8_individual_data_; std::vector<std::vector<uint8_t>> tile8_individual_data_;
std::vector<gfx::Bitmap> tile8_individual_; std::vector<gfx::Bitmap> tile8_individual_;
Rom& rom_;
Tile16Editor tile16_editor_; Tile16Editor tile16_editor_;
GfxGroupEditor gfx_group_editor_; GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
zelda3::overworld::Overworld overworld_;
gui::Canvas ow_map_canvas_{"owMapCanvas", ImVec2(0x200 * 8, 0x200 * 8),
gui::CanvasGridSize::k64x64};
gui::Canvas current_gfx_canvas_{"customGfxCanvas",
ImVec2(0x100 + 1, 0x10 * 0x40 + 1),
gui::CanvasGridSize::k32x32};
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100 + 1, 0x2000 + 1),
gui::CanvasGridSize::k32x32};
gui::Canvas graphics_bin_canvas_{
"graphicsBinCanvas", ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gfx::SnesPalette palette_; gfx::SnesPalette palette_;
gfx::Bitmap selected_tile_bmp_; gfx::Bitmap selected_tile_bmp_;
gfx::Bitmap tile16_blockset_bmp_; gfx::Bitmap tile16_blockset_bmp_;
gfx::Bitmap current_gfx_bmp_; gfx::Bitmap current_gfx_bmp_;
@@ -286,15 +255,38 @@ class OverworldEditor : public Editor,
gfx::BitmapTable maps_bmp_; gfx::BitmapTable maps_bmp_;
gfx::BitmapTable current_graphics_set_; gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_; gfx::BitmapTable sprite_previews_;
gfx::BitmapTable animated_maps_;
OWBlockset refresh_blockset_; zelda3::Overworld overworld_;
zelda3::OverworldBlockset refresh_blockset_;
zelda3::Sprite current_sprite_;
zelda3::OverworldEntrance current_entrance_;
zelda3::OverworldExit current_exit_;
zelda3::OverworldItem current_item_;
zelda3::OverworldEntranceTileTypes entrance_tiletypes_;
zelda3::GameEntity* current_entity_;
zelda3::GameEntity* dragged_entity_;
gui::Canvas ow_map_canvas_{"OwMap", kOverworldCanvasSize,
gui::CanvasGridSize::k64x64};
gui::Canvas current_gfx_canvas_{"CurrentGfx", kCurrentGfxCanvasSize,
gui::CanvasGridSize::k32x32};
gui::Canvas blockset_canvas_{"OwBlockset", kBlocksetCanvasSize,
gui::CanvasGridSize::k32x32};
gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize,
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gui::Table toolset_table_{"##ToolsetTable0", 22, kToolsetTableFlags};
gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags,
ImVec2(0, 0)};
gui::zeml::Node layout_node_; gui::zeml::Node layout_node_;
absl::Status status_; absl::Status status_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

View File

@@ -1,157 +0,0 @@
#include "app/editor/overworld_editor.h"
namespace yaze {
namespace app {
namespace editor {
void OverworldEditor::RefreshChildMap(int map_index) {
overworld_.mutable_overworld_map(map_index)->LoadAreaGraphics();
status_ = overworld_.mutable_overworld_map(map_index)->BuildTileset();
PRINT_IF_ERROR(status_);
status_ = overworld_.mutable_overworld_map(map_index)->BuildTiles16Gfx(
overworld_.tiles16().size());
PRINT_IF_ERROR(status_);
status_ = overworld_.mutable_overworld_map(map_index)->BuildBitmap(
overworld_.GetMapTiles(current_world_));
maps_bmp_[map_index].set_data(
overworld_.mutable_overworld_map(map_index)->bitmap_data());
maps_bmp_[map_index].set_modified(true);
PRINT_IF_ERROR(status_);
}
void OverworldEditor::RefreshOverworldMap() {
std::vector<std::future<void>> futures;
int indices[4];
auto refresh_map_async = [this](int map_index) {
RefreshChildMap(map_index);
};
int source_map_id = current_map_;
bool is_large = overworld_.overworld_map(current_map_)->is_large_map();
if (is_large) {
source_map_id = current_parent_;
// We need to update the map and its siblings if it's a large map
for (int i = 1; i < 4; i++) {
int sibling_index = overworld_.overworld_map(source_map_id)->parent() + i;
if (i >= 2) sibling_index += 6;
futures.push_back(
std::async(std::launch::async, refresh_map_async, sibling_index));
indices[i] = sibling_index;
}
}
indices[0] = source_map_id;
futures.push_back(
std::async(std::launch::async, refresh_map_async, source_map_id));
for (auto &each : futures) {
each.get();
}
int n = is_large ? 4 : 1;
// We do texture updating on the main thread
for (int i = 0; i < n; ++i) {
rom()->UpdateBitmap(&maps_bmp_[indices[i]]);
}
}
absl::Status OverworldEditor::RefreshMapPalette() {
RETURN_IF_ERROR(
overworld_.mutable_overworld_map(current_map_)->LoadPalette());
const auto current_map_palette = overworld_.AreaPalette();
if (overworld_.overworld_map(current_map_)->is_large_map()) {
// We need to update the map and its siblings if it's a large map
for (int i = 1; i < 4; i++) {
int sibling_index = overworld_.overworld_map(current_map_)->parent() + i;
if (i >= 2) sibling_index += 6;
RETURN_IF_ERROR(
overworld_.mutable_overworld_map(sibling_index)->LoadPalette());
RETURN_IF_ERROR(
maps_bmp_[sibling_index].ApplyPalette(current_map_palette));
}
}
RETURN_IF_ERROR(maps_bmp_[current_map_].ApplyPalette(current_map_palette));
return absl::OkStatus();
}
void OverworldEditor::RefreshMapProperties() {
auto &current_ow_map = *overworld_.mutable_overworld_map(current_map_);
if (current_ow_map.is_large_map()) {
// We need to copy the properties from the parent map to the children
for (int i = 1; i < 4; i++) {
int sibling_index = current_ow_map.parent() + i;
if (i >= 2) {
sibling_index += 6;
}
auto &map = *overworld_.mutable_overworld_map(sibling_index);
map.set_area_graphics(current_ow_map.area_graphics());
map.set_area_palette(current_ow_map.area_palette());
map.set_sprite_graphics(game_state_,
current_ow_map.sprite_graphics(game_state_));
map.set_sprite_palette(game_state_,
current_ow_map.sprite_palette(game_state_));
map.set_message_id(current_ow_map.message_id());
}
}
}
absl::Status OverworldEditor::RefreshTile16Blockset() {
if (current_blockset_ ==
overworld_.overworld_map(current_map_)->area_graphics()) {
return absl::OkStatus();
}
current_blockset_ = overworld_.overworld_map(current_map_)->area_graphics();
overworld_.set_current_map(current_map_);
palette_ = overworld_.AreaPalette();
// Create the tile16 blockset image
rom()->UpdateBitmap(&tile16_blockset_bmp_);
RETURN_IF_ERROR(tile16_blockset_bmp_.ApplyPalette(palette_));
// Copy the tile16 data into individual tiles.
auto tile16_data = overworld_.Tile16Blockset();
std::vector<std::future<void>> futures;
// Loop through the tiles and copy their pixel data into separate vectors
for (int i = 0; i < 4096; i++) {
futures.push_back(std::async(
std::launch::async,
[&](int index) {
// Create a new vector for the pixel data of the current tile
Bytes tile_data(16 * 16, 0x00); // More efficient initialization
// Copy the pixel data for the current tile into the vector
for (int ty = 0; ty < 16; ty++) {
for (int tx = 0; tx < 16; tx++) {
int position = tx + (ty * 0x10);
uint8_t value =
tile16_data[(index % 8 * 16) + (index / 8 * 16 * 0x80) +
(ty * 0x80) + tx];
tile_data[position] = value;
}
}
// Add the vector for the current tile to the vector of tile pixel
// data
tile16_individual_[index].set_data(tile_data);
},
i));
}
for (auto &future : futures) {
future.get();
}
// Render the bitmaps of each tile.
for (int id = 0; id < 4096; id++) {
RETURN_IF_ERROR(tile16_individual_[id].ApplyPalette(palette_));
rom()->UpdateBitmap(&tile16_individual_[id]);
}
return absl::OkStatus();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -4,9 +4,9 @@
#include "app/editor/sprite/zsprite.h" #include "app/editor/sprite/zsprite.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/zelda3/sprite/sprite.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using ImGui::BeginTable; using ImGui::BeginTable;
@@ -71,13 +71,13 @@ void SpriteEditor::DrawVanillaSpriteEditor() {
for (int n = 0; n < active_sprites_.Size;) { for (int n = 0; n < active_sprites_.Size;) {
bool open = true; bool open = true;
if (active_sprites_[n] > sizeof(core::kSpriteDefaultNames) / 4) { if (active_sprites_[n] > sizeof(zelda3::kSpriteDefaultNames) / 4) {
active_sprites_.erase(active_sprites_.Data + n); active_sprites_.erase(active_sprites_.Data + n);
continue; continue;
} }
if (ImGui::BeginTabItem( if (ImGui::BeginTabItem(
core::kSpriteDefaultNames[active_sprites_[n]].data(), &open, zelda3::kSpriteDefaultNames[active_sprites_[n]].data(), &open,
ImGuiTabItemFlags_None)) { ImGuiTabItemFlags_None)) {
DrawSpriteCanvas(); DrawSpriteCanvas();
ImGui::EndTabItem(); ImGui::EndTabItem();
@@ -175,7 +175,7 @@ void SpriteEditor::DrawCurrentSheets() {
graphics_sheet_canvas_.DrawTileSelector(32); graphics_sheet_canvas_.DrawTileSelector(32);
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
graphics_sheet_canvas_.DrawBitmap( graphics_sheet_canvas_.DrawBitmap(
rom()->bitmap_manager()[current_sheets_[i]], 1, (i * 0x40) + 1, 2); rom()->gfx_sheets().at(current_sheets_[i]), 1, (i * 0x40) + 1, 2);
} }
graphics_sheet_canvas_.DrawGrid(); graphics_sheet_canvas_.DrawGrid();
graphics_sheet_canvas_.DrawOverlay(); graphics_sheet_canvas_.DrawOverlay();
@@ -188,10 +188,10 @@ void SpriteEditor::DrawSpritesList() {
ImVec2(ImGui::GetContentRegionAvail().x, 0), true, ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
ImGuiWindowFlags_NoDecoration)) { ImGuiWindowFlags_NoDecoration)) {
int i = 0; int i = 0;
for (const auto each_sprite_name : core::kSpriteDefaultNames) { for (const auto each_sprite_name : zelda3::kSpriteDefaultNames) {
rom()->resource_label()->SelectableLabelWithNameEdit( rom()->resource_label()->SelectableLabelWithNameEdit(
current_sprite_id_ == i, "Sprite Names", core::UppercaseHexByte(i), current_sprite_id_ == i, "Sprite Names", core::HexByte(i),
core::kSpriteDefaultNames[i].data()); zelda3::kSpriteDefaultNames[i].data());
if (ImGui::IsItemClicked()) { if (ImGui::IsItemClicked()) {
current_sprite_id_ = i; current_sprite_id_ = i;
if (!active_sprites_.contains(i)) { if (!active_sprites_.contains(i)) {
@@ -243,7 +243,7 @@ void SpriteEditor::DrawCustomSpritesMetadata() {
// ZSprite Maker format open file dialog // ZSprite Maker format open file dialog
if (ImGui::Button("Open ZSprite")) { if (ImGui::Button("Open ZSprite")) {
// Open ZSprite file // Open ZSprite file
std::string file_path = FileDialogWrapper::ShowOpenFileDialog(); std::string file_path = core::FileDialogWrapper::ShowOpenFileDialog();
if (!file_path.empty()) { if (!file_path.empty()) {
zsprite::ZSprite zsprite; zsprite::ZSprite zsprite;
status_ = zsprite.Load(file_path); status_ = zsprite.Load(file_path);
@@ -274,5 +274,4 @@ void SpriteEditor::DrawCustomSpritesMetadata() {
} }
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -3,12 +3,11 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/editor/sprite/zsprite.h" #include "app/editor/sprite/zsprite.h"
#include "app/editor/utils/editor.h" #include "app/editor/editor.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
constexpr ImGuiTabItemFlags kSpriteTabFlags = constexpr ImGuiTabItemFlags kSpriteTabFlags =
@@ -111,7 +110,6 @@ class SpriteEditor : public SharedRom, public Editor {
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H #endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H

View File

@@ -1,19 +1,18 @@
#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H #ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H #define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#include "imgui/imgui.h"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <vector> #include <vector>
#include "app/core/constants.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/gfx/snes_tile.h" #include "app/gfx/snes_tile.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
/** /**
* @brief Namespace for the ZSprite format from Zarby's ZSpriteMaker. * @brief Namespace for the ZSprite format from Zarby's ZSpriteMaker.
@@ -46,7 +45,7 @@ struct OamTile {
struct AnimationGroup { struct AnimationGroup {
AnimationGroup() = default; AnimationGroup() = default;
AnimationGroup(uint8_t fs, uint8_t fe, uint8_t fsp, std::string fn) AnimationGroup(uint8_t fs, uint8_t fe, uint8_t fsp, std::string fn)
: frame_start(fs), frame_end(fe), frame_speed(fsp), frame_name(fn) {} : frame_name(fn), frame_start(fs), frame_end(fe), frame_speed(fsp) {}
std::string frame_name; std::string frame_name;
uint8_t frame_start; uint8_t frame_start;
@@ -390,7 +389,6 @@ struct ZSprite {
} // namespace zsprite } // namespace zsprite
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_SPRITE_ZSPRITE_H #endif // YAZE_APP_EDITOR_SPRITE_ZSPRITE_H

View File

@@ -0,0 +1,141 @@
#include "command_manager.h"
#include <fstream>
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
ImGuiKey MapKeyToImGuiKey(char key) {
switch (key) {
case 'A':
return ImGuiKey_A;
case 'B':
return ImGuiKey_B;
case 'C':
return ImGuiKey_C;
case 'D':
return ImGuiKey_D;
case 'E':
return ImGuiKey_E;
case 'F':
return ImGuiKey_F;
case 'G':
return ImGuiKey_G;
case 'H':
return ImGuiKey_H;
case 'I':
return ImGuiKey_I;
case 'J':
return ImGuiKey_J;
case 'K':
return ImGuiKey_K;
case 'L':
return ImGuiKey_L;
case 'M':
return ImGuiKey_M;
case 'N':
return ImGuiKey_N;
case 'O':
return ImGuiKey_O;
case 'P':
return ImGuiKey_P;
case 'Q':
return ImGuiKey_Q;
case 'R':
return ImGuiKey_R;
case 'S':
return ImGuiKey_S;
case 'T':
return ImGuiKey_T;
case 'U':
return ImGuiKey_U;
case 'V':
return ImGuiKey_V;
case 'W':
return ImGuiKey_W;
case 'X':
return ImGuiKey_X;
case 'Y':
return ImGuiKey_Y;
case 'Z':
return ImGuiKey_Z;
case '/':
return ImGuiKey_Slash;
case '-':
return ImGuiKey_Minus;
default:
return ImGuiKey_COUNT;
}
}
// When the player presses Space, a popup will appear fixed to the bottom of the
// ImGui window with a list of the available key commands which can be used.
void CommandManager::ShowWhichKey() {
if (ImGui::IsKeyPressed(ImGuiKey_Space)) {
ImGui::OpenPopup("WhichKey");
}
ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y - 100),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, 100),
ImGuiCond_Always);
if (ImGui::BeginPopup("WhichKey")) {
// ESC to close the popup
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
ImGui::CloseCurrentPopup();
}
const ImVec4 colors[] = {
ImVec4(0.8f, 0.2f, 0.2f, 1.0f), // Soft Red
ImVec4(0.2f, 0.8f, 0.2f, 1.0f), // Soft Green
ImVec4(0.2f, 0.2f, 0.8f, 1.0f), // Soft Blue
ImVec4(0.8f, 0.8f, 0.2f, 1.0f), // Soft Yellow
ImVec4(0.8f, 0.2f, 0.8f, 1.0f), // Soft Magenta
ImVec4(0.2f, 0.8f, 0.8f, 1.0f) // Soft Cyan
};
const int numColors = sizeof(colors) / sizeof(colors[0]);
int colorIndex = 0;
if (ImGui::BeginTable("CommandsTable", commands_.size(),
ImGuiTableFlags_SizingStretchProp)) {
for (const auto &[shortcut, info] : commands_) {
ImGui::TableNextColumn();
ImGui::TextColored(colors[colorIndex], "%c: %s",
info.command_info.mnemonic,
info.command_info.name.c_str());
colorIndex = (colorIndex + 1) % numColors;
}
ImGui::EndTable();
}
ImGui::EndPopup();
}
}
void CommandManager::SaveKeybindings(const std::string &filepath) {
std::ofstream out(filepath);
if (out.is_open()) {
for (const auto &[shortcut, info] : commands_) {
out << shortcut << " " << info.command_info.mnemonic << " "
<< info.command_info.name << " " << info.command_info.desc << "\n";
}
out.close();
}
}
void CommandManager::LoadKeybindings(const std::string &filepath) {
std::ifstream in(filepath);
if (in.is_open()) {
commands_.clear();
std::string shortcut, name, desc;
char mnemonic;
while (in >> shortcut >> mnemonic >> name >> desc) {
commands_[shortcut].command_info = {nullptr, mnemonic, name, desc};
}
in.close();
}
}
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,83 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_COMMAND_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_COMMAND_MANAGER_H
#include <functional>
#include <string>
#include <unordered_map>
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
ImGuiKey MapKeyToImGuiKey(char key);
class CommandManager {
public:
CommandManager() = default;
~CommandManager() = default;
using Command = std::function<void()>;
struct CommandInfo {
Command command;
char mnemonic;
std::string name;
std::string desc;
CommandInfo(Command command, char mnemonic, const std::string &name,
const std::string &desc)
: command(std::move(command)),
mnemonic(mnemonic),
name(name),
desc(desc) {}
CommandInfo() = default;
};
// New command info which supports subsections of commands
struct CommandInfoOrPrefix {
CommandInfo command_info;
std::unordered_map<std::string, CommandInfoOrPrefix> subcommands;
CommandInfoOrPrefix(CommandInfo command_info)
: command_info(std::move(command_info)) {}
CommandInfoOrPrefix() = default;
};
void RegisterPrefix(const std::string &group_name, const char prefix,
const std::string &name, const std::string &desc) {
commands_[group_name].command_info = {nullptr, prefix, name, desc};
}
void RegisterSubcommand(const std::string &group_name,
const std::string &shortcut, const char mnemonic,
const std::string &name, const std::string &desc,
Command command) {
commands_[group_name].subcommands[shortcut].command_info = {
command, mnemonic, name, desc};
}
void RegisterCommand(const std::string &shortcut, Command command,
char mnemonic, const std::string &name,
const std::string &desc) {
commands_[shortcut].command_info = {std::move(command), mnemonic, name,
desc};
}
void ExecuteCommand(const std::string &shortcut) {
if (commands_.find(shortcut) != commands_.end()) {
commands_[shortcut].command_info.command();
}
}
void ShowWhichKey();
void SaveKeybindings(const std::string &filepath);
void LoadKeybindings(const std::string &filepath);
private:
std::unordered_map<std::string, CommandInfoOrPrefix> commands_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_COMMAND_MANAGER_H

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