backend-infra-engineer: Pre-0.2.2 2024 Q3 snapshot

This commit is contained in:
scawful
2024-07-31 12:42:04 -04:00
parent 92cc574e15
commit 75bf38fa71
166 changed files with 18107 additions and 9518 deletions

View File

@@ -34,6 +34,9 @@ jobs:
- name: Install Audio Libs - name: Install Audio Libs
run: sudo apt install libwavpack-dev run: sudo apt install libwavpack-dev
- name: Install Abseil-cpp
run: sudo apt install libabsl-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

12
.gitmodules vendored
View File

@@ -7,9 +7,6 @@
[submodule "src/lib/ImGuiColorTextEdit"] [submodule "src/lib/ImGuiColorTextEdit"]
path = src/lib/ImGuiColorTextEdit path = src/lib/ImGuiColorTextEdit
url = https://github.com/BalazsJako/ImGuiColorTextEdit.git url = https://github.com/BalazsJako/ImGuiColorTextEdit.git
[submodule "src/lib/sneshacking"]
path = src/lib/sneshacking
url = https://github.com/Skarsnik/sneshacking.git
[submodule "assets/asm/alttp-hacker-workspace"] [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
@@ -22,9 +19,6 @@
[submodule "src/lib/asar"] [submodule "src/lib/asar"]
path = src/lib/asar path = src/lib/asar
url = https://github.com/RPGHacker/asar.git url = https://github.com/RPGHacker/asar.git
[submodule "src/lib/snes_spc"] [submodule "src/lib/imgui_test_engine"]
path = src/lib/snes_spc path = src/lib/imgui_test_engine
url = https://github.com/blarggs-audio-libraries/snes_spc.git url = https://github.com/ocornut/imgui_test_engine.git
[submodule "src/lib/SDL_mixer"]
path = src/lib/SDL_mixer
url = https://github.com/libsdl-org/SDL_mixer.git

View File

@@ -3,55 +3,32 @@ cmake_minimum_required(VERSION 3.10)
# Yet Another Zelda3 Editor # Yet Another Zelda3 Editor
# by scawful # by scawful
project(yaze VERSION 0.01) project(yaze VERSION 0.10)
# C++ Standard Specifications # C++ Standard Specifications
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_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)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_LINKER_FLAGS \"-Wl,--no-undefined -Wl,--no-undefined\")
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
set(BUILD_SHARED_LIBS ON) set(BUILD_SHARED_LIBS OFF)
set(CMAKE_FIND_FRAMEWORK LAST)
# Abseil Standard Specifications # Abseil Standard Specifications
include(cmake/absl.cmake) include(cmake/absl.cmake)
add_subdirectory(src/lib/abseil-cpp)
include(cmake/openssl.cmake)
# Video Libraries # Video Libraries
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
find_package(OpenGL REQUIRED)
include(cmake/sdl2.cmake) include(cmake/sdl2.cmake)
# Asar # Asar
# add_subdirectory(src/lib/asar/src) add_subdirectory(src/lib/asar/src)
# include(cmake/asar.cmake) include(cmake/asar.cmake)
# snes-spc
ADD_DEFINITIONS(-DSNES_SPC_EXPORTS)
set(SNES_SPC_SOURCES
"../src/lib/snes_spc/snes_spc/spc.cpp"
"../src/lib/snes_spc/snes_spc/SNES_SPC.cpp"
"../src/lib/snes_spc/snes_spc/SNES_SPC_misc.cpp"
"../src/lib/snes_spc/snes_spc/SNES_SPC_state.cpp"
"../src/lib/snes_spc/snes_spc/SPC_DSP.cpp"
"../src/lib/snes_spc/snes_spc/dsp.cpp"
"../src/lib/snes_spc/snes_spc/SPC_Filter.cpp"
"../src/lib/snes_spc/demo/wave_writer.c"
"../src/lib/snes_spc/demo/demo_util.c"
)
include_directories(src/lib/snes_spc/snes_spc)
ADD_LIBRARY(snes_spc STATIC ${SNES_SPC_SOURCES} src/app/zelda3/music/spc700.def)
# ImGui # ImGui
include(cmake/imgui.cmake) include(cmake/imgui.cmake)
# Project Files # Project Files
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(test)

View File

@@ -2689,7 +2689,7 @@ CALL_GRAPH = YES
# The default value is: NO. # The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
CALLER_GRAPH = YES CALLER_GRAPH = NO
# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical # If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
# hierarchy of all classes instead of a textual one. # hierarchy of all classes instead of a textual one.

View File

@@ -34,7 +34,7 @@ Building and installation
## 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.
- For developers, please refer to [infrastructure.md](docs/infrastructure.md) for information on the project's infrastructure. - For developers, please refer to the [documentation](https://scawful.github.io/yaze/index.html) for information on the project's infrastructure.
License License
-------- --------
@@ -44,9 +44,10 @@ SDL2, ImGui and Abseil are subject to respective licenses.
Screenshots Screenshots
-------- --------
![image](https://user-images.githubusercontent.com/47263509/194669806-2b0da68d-9d38-4f52-bcce-c60ee861092c.png) ![image](https://github.com/scawful/yaze/assets/47263509/8b62b142-1de4-4ca4-8c49-d50c08ba4c8e)
![image](https://github.com/scawful/yaze/assets/47263509/8913f7ff-6345-4295-ae05-782fd3949eb5) ![image](https://github.com/scawful/yaze/assets/47263509/d8f0039d-d2e4-47d7-b420-554b20ac626f)
![image](https://github.com/scawful/yaze/assets/47263509/34b36666-cbea-420b-af90-626099470ae4)
![image](https://github.com/scawful/yaze/assets/47263509/e1cf3edb-a59e-4f0a-b4e0-d68925803e58)

View File

@@ -0,0 +1,25 @@
BeginTabBar title="##OwEditorTabBar" {
BeginTabItem title="Map Editor" {
Function id="owToolset",
Table id="##owEditTable" count="2" flags="Resizable|Reorderable|Hideable|BordersOuter|BordersV" {
TableSetupColumn title="Canvas" flags="WidthStretch",
TableSetupColumn title="Tile Selector" flags="WidthFixed" width="256",
TableHeadersRow
TableNextRow,
TableNextColumn,
Function id="OverworldCanvas",
TableNextColumn,
Function id="OverworldTileSelector",
}
}
BeginTabItem title="Tile16 Editor" {
Function id="OwTile16Editor"
}
BeginTabItem title "Graphics Group Editor" {
Function id="OwGfxGroupEditor"
}
BeginTabItem title="Usage Statistics" {
Function id="OwUsageStats"
}
}

View File

@@ -1,3 +1,4 @@
find_package(absl)
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)

View File

@@ -21,6 +21,14 @@ target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
target_compile_definitions(ImGuiColorTextEdit PUBLIC 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)
file(GLOB IMGUI_TEST_ENGINE_SOURCES ${IMGUI_TEST_ENGINE_PATH}/*.cpp)
add_library("ImGuiTestEngine" STATIC ${IMGUI_TEST_ENGINE_SOURCES})
target_include_directories(ImGuiTestEngine PUBLIC ${IMGUI_PATH})
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

View File

@@ -1,5 +0,0 @@
if (UNIX)
set(OPENSSL_INCLUDE_DIR "/usr/local/Cellar/openssl@1.1/1.1.1q/include")
set(OPENSSL_CRYPTO_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libcrypto.dylib")
set(OPENSSL_SSL_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libssl.dylib")
endif()

View File

@@ -1,19 +1,6 @@
# SDL2, SDL2_image and SDL2_mixer # SDL2
if (UNIX) if (UNIX)
add_subdirectory(src/lib/SDL) add_subdirectory(src/lib/SDL)
else() else()
find_package(SDL2) find_package(SDL2)
endif() endif()
set(SDL2MIXER_OPUS OFF)
set(SDL2MIXER_FLAC OFF)
set(SDL2MIXER_MOD OFF)
set(SDL2MIXER_MIDI_FLUIDSYNTH OFF)
find_library(SDL_MIXER_LIBRARY
NAMES SDL_mixer
HINTS
ENV SDLMIXERDIR
ENV SDLDIR
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
)
add_subdirectory(src/lib/SDL_mixer)
find_package(SDL2_image)

View File

@@ -3,22 +3,7 @@ set(
app/core/common.cc app/core/common.cc
app/core/controller.cc app/core/controller.cc
app/core/labeling.cc app/core/labeling.cc
) app/emu/emulator.cc
set(
YAZE_APP_EDITOR_SRC
app/editor/dungeon_editor.cc
app/editor/graphics_editor.cc
app/editor/master_editor.cc
app/editor/overworld_editor.cc
app/editor/screen_editor.cc
app/editor/sprite_editor.cc
app/editor/modules/music_editor.cc
app/editor/modules/palette_editor.cc
app/editor/modules/assembly_editor.cc
app/editor/modules/tile16_editor.cc
app/editor/modules/gfx_group_editor.cc
app/editor/context/gfx_context.cc
) )
set( set(
@@ -32,32 +17,18 @@ set(
app/gfx/tilesheet.cc app/gfx/tilesheet.cc
) )
set(
YAZE_APP_ZELDA3_SRC
app/zelda3/overworld/overworld_map.cc
app/zelda3/overworld/overworld.cc
app/zelda3/screen/inventory.cc
app/zelda3/screen/title_screen.cc
app/zelda3/sprite/sprite.cc
app/zelda3/music/tracker.cc
app/zelda3/dungeon/room.cc
app/zelda3/dungeon/room_object.cc
app/zelda3/dungeon/object_renderer.cc
)
set( set(
YAZE_GUI_SRC YAZE_GUI_SRC
app/gui/asset_browser.cc
app/gui/canvas.cc app/gui/canvas.cc
app/gui/input.cc app/gui/input.cc
app/gui/style.cc app/gui/style.cc
app/gui/widgets.cc
app/gui/color.cc app/gui/color.cc
app/gui/pipeline.cc app/gui/zeml.cc
) )
set( set(
YAZE_APP_EMU_SRC YAZE_APP_EMU_SRC
app/emu/emulator.cc
app/emu/audio/apu.cc app/emu/audio/apu.cc
app/emu/audio/spc700.cc app/emu/audio/spc700.cc
app/emu/audio/dsp.cc app/emu/audio/dsp.cc
@@ -92,6 +63,7 @@ if(APPLE)
app/core/platform/app_delegate.mm app/core/platform/app_delegate.mm
app/core/platform/font_loader.mm app/core/platform/font_loader.mm
app/core/platform/clipboard.mm app/core/platform/clipboard.mm
app/core/platform/file_path.mm
) )
find_library(COCOA_LIBRARY Cocoa) find_library(COCOA_LIBRARY Cocoa)
@@ -102,7 +74,7 @@ if(APPLE)
endif() endif()
include(app/CMakeLists.txt) include(app/CMakeLists.txt)
include(cli/CMakeLists.txt) # include(cli/CMakeLists.txt) Excluded for now, macOS include breaks action build
if (UNIX) if (UNIX)
target_compile_definitions(yaze PRIVATE "linux") target_compile_definitions(yaze PRIVATE "linux")
@@ -136,3 +108,5 @@ set_target_properties(yaze
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res" LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
) )
endif() endif()
add_subdirectory(test)

View File

@@ -1,3 +1,6 @@
include(app/editor/CMakeLists.txt)
include(app/zelda3/CMakeLists.txt)
add_executable( add_executable(
yaze yaze
app/yaze.cc app/yaze.cc
@@ -9,14 +12,15 @@ add_executable(
${YAZE_APP_ZELDA3_SRC} ${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC} ${YAZE_GUI_SRC}
${IMGUI_SRC} ${IMGUI_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
) )
target_include_directories( target_include_directories(
yaze PUBLIC yaze PUBLIC
lib/ lib/
app/ app/
lib/SDL_mixer/include/
${CMAKE_SOURCE_DIR}/src/ ${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR} ${SDL2_INCLUDE_DIR}
) )
@@ -27,7 +31,7 @@ target_link_libraries(
${SDL_TARGETS} ${SDL_TARGETS}
${PNG_LIBRARIES} ${PNG_LIBRARIES}
${CMAKE_DL_LIBS} ${CMAKE_DL_LIBS}
SDL2_mixer ImGuiTestEngine
ImGui ImGui
) )

View File

@@ -1,6 +1,6 @@
#include "common.h" #include "common.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
@@ -26,11 +26,11 @@ std::string UppercaseHexByte(uint8_t byte, bool leading) {
return result; return result;
} }
std::string UppercaseHexWord(uint16_t word) { std::string UppercaseHexWord(uint16_t word) {
std::string result = absl::StrFormat("0x%04x", word); std::string result = absl::StrFormat("0x%04X", word);
return result; return result;
} }
std::string UppercaseHexLong(uint32_t dword) { std::string UppercaseHexLong(uint32_t dword) {
std::string result = absl::StrFormat("0x%08x", dword); std::string result = absl::StrFormat("0x%06X", dword);
return result; return result;
} }
@@ -174,6 +174,14 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
// Initialize the static member // Initialize the static member
std::stack<ImGuiID> ImGuiIdIssuer::idStack; std::stack<ImGuiID> ImGuiIdIssuer::idStack;
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc) {
uint32_t ret = (PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr];
if (pc) {
return SnesToPc(ret);
}
return ret;
}
} // namespace core } // namespace core
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,7 +1,7 @@
#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 "imgui/imgui.h"
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
@@ -32,7 +32,7 @@ class ExperimentFlags {
bool kUseBitmapManager = true; bool kUseBitmapManager = true;
// Log instructions to the GUI debugger. // Log instructions to the GUI debugger.
bool kLogInstructions = false; bool kLogInstructions = true;
// Flag to enable ImGui input config flags. Currently is // Flag to enable ImGui input config flags. Currently is
// handled manually by controller class but should be // handled manually by controller class but should be
@@ -42,6 +42,9 @@ class ExperimentFlags {
// 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;
// Flag to enable the saving of gfx groups to the rom.
bool kSaveGfxGroups = false;
// Flag to enable the change queue, which could have any anonymous // Flag to enable the change queue, which could have any anonymous
// save routine for the Rom. In practice, just the overworld tilemap // save routine for the Rom. In practice, just the overworld tilemap
// and tile32 save. // and tile32 save.
@@ -66,6 +69,9 @@ class ExperimentFlags {
// 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.
@@ -181,6 +187,11 @@ class Logger {
static std::ofstream fout("log.txt", std::ios::out | std::ios::app); static std::ofstream fout("log.txt", std::ios::out | std::ios::app);
fout << message << std::endl; fout << message << std::endl;
} }
// log to console
static void logc(std::string message) { logs.emplace_back(message); }
static std::vector<std::string> logs;
}; };
std::string UppercaseHexByte(uint8_t byte, bool leading = false); std::string UppercaseHexByte(uint8_t byte, bool leading = false);
@@ -205,10 +216,16 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
uint16_t ldle16b(uint8_t const *const p_arr); 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);
void stle32b(uint8_t *const p_arr, uint32_t const p_val);
void stle32b_i(uint8_t *const p_arr, size_t const p_index, struct FolderItem {
uint32_t const p_val); std::string name;
std::vector<FolderItem> subfolders;
std::vector<std::string> files;
};
typedef struct FolderItem FolderItem;
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true);
} // namespace core } // namespace core
} // namespace app } // namespace app

View File

@@ -5,10 +5,6 @@
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#define BASIC_BUTTON(w) if (ImGui::Button(w))
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) { #define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
#define END_TAB_BAR() \ #define END_TAB_BAR() \
ImGui::EndTabBar(); \ ImGui::EndTabBar(); \
@@ -19,11 +15,6 @@
ImGui::EndTabItem(); \ ImGui::EndTabItem(); \
} }
#define MENU_BAR() if (ImGui::BeginMenuBar()) {
#define END_MENU_BAR() \
ImGui::EndMenuBar(); \
}
#define MENU_ITEM(w) if (ImGui::MenuItem(w)) #define MENU_ITEM(w) if (ImGui::MenuItem(w))
#define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v)) #define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v))
@@ -145,7 +136,7 @@ namespace app {
namespace core { namespace core {
constexpr uint32_t kRedPen = 0xFF0000FF; constexpr uint32_t kRedPen = 0xFF0000FF;
constexpr float kYazeVersion = 0.07; constexpr float kYazeVersion = 0.2;
// ============================================================================ // ============================================================================
// Magic numbers // Magic numbers
@@ -213,122 +204,90 @@ constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
constexpr int GraveLinkSpecialHole = 0x46DD9; // short constexpr int GraveLinkSpecialHole = 0x46DD9; // short
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
// ============================================================================
// Palettes Related Variables - This contain all the palettes of the game
// ============================================================================
constexpr int overworldPaletteMain = 0xDE6C8;
constexpr int overworldPaletteAuxialiary = 0xDE86C;
constexpr int overworldPaletteAnimated = 0xDE604;
constexpr int globalSpritePalettesLW = 0xDD218;
constexpr int globalSpritePalettesDW = 0xDD290;
// Green, Blue, Red, Bunny, Electrocuted (15 colors each)
constexpr int armorPalettes = 0xDD308;
constexpr int spritePalettesAux1 = 0xDD39E; // 7 colors each
constexpr int spritePalettesAux2 = 0xDD446; // 7 colors each
constexpr int spritePalettesAux3 = 0xDD4E0; // 7 colors each
constexpr int swordPalettes = 0xDD630; // 3 colors each - 4 entries
constexpr int shieldPalettes = 0xDD648; // 4 colors each - 3 entries
constexpr int hudPalettes = 0xDD660;
constexpr int dungeonMapPalettes = 0xDD70A; // 21 colors
constexpr int dungeonMainPalettes = 0xDD734; //(15*6) colors each - 20 entries
constexpr int dungeonMapBgPalettes = 0xDE544; // 16*6
// Mirrored Value at 0x75645 : 0x75625
constexpr int hardcodedGrassLW = 0x5FEA9;
constexpr int hardcodedGrassDW = 0x05FEB3; // 0x7564F
constexpr int hardcodedGrassSpecial = 0x75640;
constexpr int overworldMiniMapPalettes = 0x55B27;
constexpr int triforcePalette = 0x64425;
constexpr int crystalPalette = 0xF4CD3;
constexpr int customAreaSpecificBGPalette =
0x140000; // 2 bytes for each overworld area (320)
constexpr int customAreaSpecificBGASM = 0x140150;
constexpr int customAreaSpecificBGEnabled =
0x140140; // 1 byte, not 0 if enabled
// ============================================================================ // ============================================================================
// Names // Names
// ============================================================================ // ============================================================================
static const absl::string_view RoomEffect[] = {"Nothing", static const std::string RoomEffect[] = {"Nothing",
"Nothing", "Nothing",
"Moving Floor", "Moving Floor",
"Moving Water", "Moving Water",
"Trinexx Shell", "Trinexx Shell",
"Red Flashes", "Red Flashes",
"Light Torch to See Floor", "Light Torch to See Floor",
"Ganon's Darkness"}; "Ganon's Darkness"};
static const absl::string_view RoomTag[] = {"Nothing", static const std::string RoomTag[] = {"Nothing",
"NW Kill Enemy to Open", "NW Kill Enemy to Open",
"NE Kill Enemy to Open", "NE Kill Enemy to Open",
"SW Kill Enemy to Open", "SW Kill Enemy to Open",
"SE Kill Enemy to Open", "SE Kill Enemy to Open",
"W Kill Enemy to Open", "W Kill Enemy to Open",
"E Kill Enemy to Open", "E Kill Enemy to Open",
"N Kill Enemy to Open", "N Kill Enemy to Open",
"S Kill Enemy to Open", "S Kill Enemy to Open",
"Clear Quadrant to Open", "Clear Quadrant to Open",
"Clear Full Tile to Open", "Clear Full Tile to Open",
"NW Push Block to Open", "NW Push Block to Open",
"NE Push Block to Open", "NE Push Block to Open",
"SW Push Block to Open", "SW Push Block to Open",
"SE Push Block to Open", "SE Push Block to Open",
"W Push Block to Open", "W Push Block to Open",
"E Push Block to Open", "E Push Block to Open",
"N Push Block to Open", "N Push Block to Open",
"S Push Block to Open", "S Push Block to Open",
"Push Block to Open", "Push Block to Open",
"Pull Lever to Open", "Pull Lever to Open",
"Collect Prize to Open", "Collect Prize to Open",
"Hold Switch Open Door", "Hold Switch Open Door",
"Toggle Switch to Open Door", "Toggle Switch to Open Door",
"Turn off Water", "Turn off Water",
"Turn on Water", "Turn on Water",
"Water Gate", "Water Gate",
"Water Twin", "Water Twin",
"Moving Wall Right", "Moving Wall Right",
"Moving Wall Left", "Moving Wall Left",
"Crash", "Crash",
"Crash", "Crash",
"Push Switch Exploding Wall", "Push Switch Exploding Wall",
"Holes 0", "Holes 0",
"Open Chest (Holes 0)", "Open Chest (Holes 0)",
"Holes 1", "Holes 1",
"Holes 2", "Holes 2",
"Defeat Boss for Dungeon Prize", "Defeat Boss for Dungeon Prize",
"SE Kill Enemy to Push Block", "SE Kill Enemy to Push Block",
"Trigger Switch Chest", "Trigger Switch Chest",
"Pull Lever Exploding Wall", "Pull Lever Exploding Wall",
"NW Kill Enemy for Chest", "NW Kill Enemy for Chest",
"NE Kill Enemy for Chest", "NE Kill Enemy for Chest",
"SW Kill Enemy for Chest", "SW Kill Enemy for Chest",
"SE Kill Enemy for Chest", "SE Kill Enemy for Chest",
"W Kill Enemy for Chest", "W Kill Enemy for Chest",
"E Kill Enemy for Chest", "E Kill Enemy for Chest",
"N Kill Enemy for Chest", "N Kill Enemy for Chest",
"S Kill Enemy for Chest", "S Kill Enemy for Chest",
"Clear Quadrant for Chest", "Clear Quadrant for Chest",
"Clear Full Tile for Chest", "Clear Full Tile for Chest",
"Light Torches to Open", "Light Torches to Open",
"Holes 3", "Holes 3",
"Holes 4", "Holes 4",
"Holes 5", "Holes 5",
"Holes 6", "Holes 6",
"Agahnim Room", "Agahnim Room",
"Holes 7", "Holes 7",
"Holes 8", "Holes 8",
"Open Chest for Holes 8", "Open Chest for Holes 8",
"Push Block for Chest", "Push Block for Chest",
"Clear Room for Triforce Door", "Clear Room for Triforce Door",
"Light Torches for Chest", "Light Torches for Chest",
"Kill Boss Again"}; "Kill Boss Again"};
static const absl::string_view SecretItemNames[] = { static const std::string SecretItemNames[] = {
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack", "Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
"Bomb", "Heart ", "Blue Rupee", "Bomb", "Heart ", "Blue Rupee",
@@ -340,7 +299,7 @@ static const absl::string_view SecretItemNames[] = {
"Hole", "Warp", "Staircase", "Bombable", "Switch"}; "Hole", "Warp", "Staircase", "Bombable", "Switch"};
static const absl::string_view TileTypeNames[] = { static const std::string TileTypeNames[] = {
"$00 Nothing (standard floor)", "$00 Nothing (standard floor)",
"$01 Collision", "$01 Collision",
"$02 Collision", "$02 Collision",
@@ -598,7 +557,7 @@ static const absl::string_view TileTypeNames[] = {
"$FE Door X top? (unused?)", "$FE Door X top? (unused?)",
"$FF Door X top? (unused?)"}; "$FF Door X top? (unused?)"};
static const absl::string_view kSpriteDefaultNames[]{ static const std::string kSpriteDefaultNames[]{
"00 Raven", "00 Raven",
"01 Vulture", "01 Vulture",
"02 Flying Stalfos Head", "02 Flying Stalfos Head",
@@ -857,7 +816,7 @@ static const absl::string_view kSpriteDefaultNames[]{
"FF", "FF",
}; };
static const absl::string_view overlordnames[] = { static const std::string overlordnames[] = {
"Overlord_SpritePositionTarget", "Overlord_SpritePositionTarget",
"Overlord_AllDirectionMetalBallFactory", "Overlord_AllDirectionMetalBallFactory",
"Overlord_CascadeMetalBallFactory", "Overlord_CascadeMetalBallFactory",

View File

@@ -1,11 +1,22 @@
#include "controller.h" #include "controller.h"
#include <SDL.h> #include <SDL.h>
#include <SDL_mixer.h>
#include <imgui/backends/imgui_impl_sdl2.h> #include "imgui/backends/imgui_impl_sdl2.h"
#include <imgui/backends/imgui_impl_sdlrenderer2.h> #include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <imgui/imgui_internal.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>
@@ -22,8 +33,37 @@ namespace core {
namespace { namespace {
constexpr ImGuiWindowFlags kMainEditorFlags =
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
using ::ImVec2;
using ImGui::Begin;
using ImGui::End;
using ImGui::GetIO;
using ImGui::NewFrame;
using ImGui::SetNextWindowPos;
using ImGui::SetNextWindowSize;
void NewMasterFrame() {
const ImGuiIO &io = GetIO();
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
NewFrame();
SetNextWindowPos(gui::kZeroPos);
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
SetNextWindowSize(dimensions, ImGuiCond_Always);
if (!Begin("##YazeMain", nullptr, kMainEditorFlags)) {
End();
return;
}
}
void InitializeKeymap() { void InitializeKeymap() {
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_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT); io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN); io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
@@ -49,6 +89,44 @@ void InitializeKeymap() {
io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8); io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8);
io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9); io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9);
io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0); 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) { void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
@@ -66,25 +144,63 @@ void InitializeClipboard() {
io.ClipboardUserData = nullptr; io.ClipboardUserData = nullptr;
} }
void HandleKeyDown(SDL_Event &event) { void HandleKeyDown(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN); 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) { switch (event.key.keysym.sym) {
case SDLK_UP:
case SDLK_DOWN:
case SDLK_RETURN:
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
case SDLK_LSHIFT: case SDLK_LSHIFT:
case SDLK_LCTRL: case SDLK_LCTRL:
case SDLK_TAB: case SDLK_TAB:
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN); io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
break; break;
case SDLK_z:
editor.emulator().snes().SetButtonState(1, 0, true);
break;
case SDLK_a:
editor.emulator().snes().SetButtonState(1, 1, true);
break;
case SDLK_RSHIFT:
editor.emulator().snes().SetButtonState(1, 2, true);
break;
case SDLK_RETURN:
editor.emulator().snes().SetButtonState(1, 3, true);
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;
default: default:
break; break;
} }
} }
void HandleKeyUp(SDL_Event &event) { void HandleKeyUp(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
int key = event.key.keysym.scancode; int key = event.key.keysym.scancode;
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown)); IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
@@ -93,6 +209,47 @@ void HandleKeyUp(SDL_Event &event) {
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 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) { void ChangeWindowSizeEvent(SDL_Event &event) {
@@ -117,12 +274,36 @@ void HandleMouseMovement(int &wheel) {
} // namespace } // namespace
absl::Status Controller::OnEntry() { absl::Status Controller::OnEntry(std::string filename) {
#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(CreateSDL_Window())
RETURN_IF_ERROR(CreateRenderer()) RETURN_IF_ERROR(CreateRenderer())
RETURN_IF_ERROR(CreateGuiContext()) 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(); InitializeKeymap();
master_editor_.SetupScreen(renderer_); master_editor_.SetupScreen(renderer_, filename);
active_ = true; active_ = true;
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -135,10 +316,10 @@ void Controller::OnInput() {
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
switch (event.type) { switch (event.type) {
case SDL_KEYDOWN: case SDL_KEYDOWN:
HandleKeyDown(event); HandleKeyDown(event, master_editor_);
break; break;
case SDL_KEYUP: case SDL_KEYUP:
HandleKeyUp(event); HandleKeyUp(event, master_editor_);
break; break;
case SDL_TEXTINPUT: case SDL_TEXTINPUT:
io.AddInputCharactersUTF8(event.text.text); io.AddInputCharactersUTF8(event.text.text);
@@ -166,26 +347,57 @@ void Controller::OnInput() {
HandleMouseMovement(wheel); HandleMouseMovement(wheel);
} }
void Controller::OnLoad() { PRINT_IF_ERROR(master_editor_.Update()); } absl::Status Controller::OnLoad() {
if (master_editor_.quit()) {
active_ = false;
}
NewMasterFrame();
RETURN_IF_ERROR(master_editor_.Update());
return absl::OkStatus();
}
void Controller::DoRender() const { void Controller::DoRender() const {
ImGui::Render(); ImGui::Render();
SDL_RenderClear(renderer_.get()); SDL_RenderClear(renderer_.get());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get());
SDL_RenderPresent(renderer_.get()); SDL_RenderPresent(renderer_.get());
} }
void Controller::OnExit() { void Controller::OnExit() {
master_editor_.Shutdown(); ImGui::DestroyContext();
Mix_CloseAudio(); if (flags()->kLoadAudioDevice) {
ImGui_ImplSDLRenderer2_Shutdown(); SDL_PauseAudioDevice(audio_device_, 1);
ImGui_ImplSDL2_Shutdown(); SDL_CloseAudioDevice(audio_device_);
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::CreateSDL_Window() {
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { auto sdl_flags = SDL_INIT_VIDEO | 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) {
return absl::InternalError( return absl::InternalError(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError())); absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
} else { } else {
@@ -206,11 +418,6 @@ absl::Status Controller::CreateSDL_Window() {
return absl::InternalError( return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError())); absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
} }
// Initialize SDL_mixer
if (Mix_OpenAudio(32000, MIX_DEFAULT_FORMAT, 2, 1024) < 0) {
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
Mix_GetError());
}
} }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -264,13 +471,13 @@ absl::Status Controller::CreateGuiContext() {
absl::Status Controller::LoadFontFamilies() const { absl::Status Controller::LoadFontFamilies() const {
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO();
// Define constants const char *font_path = "assets/font/";
static const char *KARLA_REGULAR = "assets/font/Karla-Regular.ttf"; static const char *KARLA_REGULAR = "Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "assets/font/Roboto-Medium.ttf"; static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "assets/font/Cousine-Regular.ttf"; static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char *DROID_SANS = "assets/font/DroidSans.ttf"; static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "assets/font/NotoSansJP.ttf"; static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "assets/font/IBMPlexSansJP-Bold.ttf"; static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 14.0f; static const float FONT_SIZE_DEFAULT = 14.0f;
static const float FONT_SIZE_DROID_SANS = 16.0f; static const float FONT_SIZE_DROID_SANS = 16.0f;
static const float ICON_FONT_SIZE = 18.0f; static const float ICON_FONT_SIZE = 18.0f;
@@ -299,23 +506,78 @@ absl::Status Controller::LoadFontFamilies() const {
float font_size = float font_size =
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT; (font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
if (!io.Fonts->AddFontFromFileTTF(font_path, font_size)) { 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( return absl::InternalError(
absl::StrFormat("Failed to load font from %s", font_path)); absl::StrFormat("Failed to load font from %s", actual_font_path));
} }
// Merge icon set // Merge icon set
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE, 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); &icons_config, icons_ranges);
// Merge Japanese font // Merge Japanese font
io.Fonts->AddFontFromFileTTF(NOTO_SANS_JP, 18.0f, &japanese_font_config, 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()); io.Fonts->GetGlyphRangesJapanese());
} }
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Controller::LoadAudioDevice() {
SDL_AudioSpec want, have;
SDL_memset(&want, 0, sizeof(want));
want.freq = audio_frequency_;
want.format = AUDIO_S16;
want.channels = 2;
want.samples = 2048;
want.callback = NULL; // Uses the queue
audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (audio_device_ == 0) {
return absl::InternalError(
absl::StrFormat("Failed to open audio: %s\n", SDL_GetError()));
}
audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
master_editor_.emulator().set_audio_buffer(audio_buffer_);
SDL_PauseAudioDevice(audio_device_, 0);
return absl::OkStatus();
}
} // namespace core } // namespace core
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -2,10 +2,11 @@
#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_sdl2.h"
#include <imgui/backends/imgui_impl_sdlrenderer2.h> #include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include <imgui/imgui.h> #include "imgui/imconfig.h"
#include <imgui/imgui_internal.h> #include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include <memory> #include <memory>
@@ -22,6 +23,8 @@ namespace yaze {
namespace app { 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.
* *
@@ -31,12 +34,20 @@ namespace core {
class Controller : public ExperimentFlags { class Controller : public ExperimentFlags {
public: public:
bool IsActive() const { return active_; } bool IsActive() const { return active_; }
absl::Status OnEntry(); absl::Status OnEntry(std::string filename = "");
void OnInput(); void OnInput();
void OnLoad(); absl::Status OnLoad();
void DoRender() const; void DoRender() const;
void OnExit(); void OnExit();
absl::Status CreateSDL_Window();
absl::Status CreateRenderer();
absl::Status CreateGuiContext();
absl::Status LoadFontFamilies() const;
absl::Status LoadAudioDevice();
auto master_editor() -> editor::MasterEditor & { return master_editor_; }
private: private:
struct sdl_deleter { struct sdl_deleter {
void operator()(SDL_Window *p) const { void operator()(SDL_Window *p) const {
@@ -52,16 +63,17 @@ class Controller : public ExperimentFlags {
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); } void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
}; };
absl::Status CreateSDL_Window();
absl::Status CreateRenderer();
absl::Status CreateGuiContext();
absl::Status LoadFontFamilies() const;
void CloseWindow() { active_ = false; } void CloseWindow() { active_ = false; }
friend int ::main(int argc, char **argv); friend int ::main(int argc, char **argv);
bool active_; bool active_;
Platform platform_;
editor::MasterEditor master_editor_; editor::MasterEditor master_editor_;
int audio_frequency_ = 48000;
int16_t *audio_buffer_;
SDL_AudioDeviceID audio_device_;
std::shared_ptr<SDL_Window> window_; std::shared_ptr<SDL_Window> window_;
std::shared_ptr<SDL_Renderer> renderer_; std::shared_ptr<SDL_Renderer> renderer_;
}; };

View File

@@ -1,7 +1,7 @@
#include "app/core/labeling.h" #include "app/core/labeling.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 <cstdint>
#include <fstream> #include <fstream>
@@ -12,6 +12,7 @@
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/gui/icons.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
@@ -111,10 +112,13 @@ void ResourceLabelManager::SelectableLabelWithNameEdit(
} }
if (ImGui::BeginPopupContextItem(label_id.c_str())) { if (ImGui::BeginPopupContextItem(label_id.c_str())) {
char* new_label = labels_[type][key].data(); std::string* new_label = &labels_[type][key];
if (ImGui::InputText("##Label", new_label, labels_[type][key].size() + 1, if (ImGui::InputText("##Label", new_label,
ImGuiInputTextFlags_EnterReturnsTrue)) { ImGuiInputTextFlags_EnterReturnsTrue)) {
labels_[type][key] = new_label; labels_[type][key] = *new_label;
}
if (ImGui::Button(ICON_MD_CLOSE)) {
ImGui::CloseCurrentPopup();
} }
ImGui::EndPopup(); ImGui::EndPopup();
} }

View File

@@ -21,10 +21,7 @@ namespace core {
static constexpr absl::string_view kDefaultTypes[] = { static constexpr absl::string_view kDefaultTypes[] = {
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"}; "Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
class ResourceLabelManager { struct ResourceLabelManager {
public:
ResourceLabelManager() = default;
bool LoadLabels(const std::string& filename); bool LoadLabels(const std::string& filename);
bool SaveLabels(); bool SaveLabels();
void DisplayLabels(bool* p_open); void DisplayLabels(bool* p_open);
@@ -38,7 +35,6 @@ class ResourceLabelManager {
std::string CreateOrGetLabel(const std::string& type, const std::string& key, std::string CreateOrGetLabel(const std::string& type, const std::string& key,
const absl::string_view& defaultValue); const absl::string_view& defaultValue);
private:
bool labels_loaded_ = false; bool labels_loaded_ = false;
std::string filename_; std::string filename_;
struct ResourceType { struct ResourceType {

View File

@@ -1,6 +1,8 @@
#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
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@@ -11,4 +13,6 @@ void InitializeCocoa();
} // extern "C" } // extern "C"
#endif #endif
#endif // TARGET_OS_MAC
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H #endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H

View File

@@ -1,12 +1,26 @@
// AppDelegate.mm // AppDelegate.mm
#import <Cocoa/Cocoa.h>
#import "app/core/controller.h"
#import "app/editor/utils/editor.h"
#import "app/core/platform/app_delegate.h" #import "app/core/platform/app_delegate.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/rom.h" #import "app/rom.h"
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#include <TargetConditionals.h>
#import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1
/* iOS in Xcode simulator */
#elif TARGET_OS_IPHONE == 1
/* iOS */
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate> @interface AppDelegate : NSObject <NSApplicationDelegate>
- (void)setupMenus; - (void)setupMenus;
// - (void)changeApplicationIcon; // - (void)changeApplicationIcon;
@@ -195,7 +209,14 @@
} }
- (void)openFileAction:(id)sender { - (void)openFileAction:(id)sender {
yaze::app::SharedRom::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog()); if (!yaze::app::SharedRom::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog())
.ok()) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Error"];
[alert setInformativeText:@"Failed to load file."];
[alert addButtonWithTitle:@"OK"];
[alert runModal];
}
} }
- (void)cutAction:(id)sender { - (void)cutAction:(id)sender {
@@ -216,3 +237,7 @@ extern "C" void InitializeCocoa() {
} }
@end @end
#endif
#endif

View File

@@ -3,6 +3,7 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#ifdef TARGET_OS_MAC
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) { void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
@@ -40,3 +41,5 @@ void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& he
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGContextRelease(context); CGContextRelease(context);
} }
#endif

View File

@@ -1,3 +1,6 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#include <string> #include <string>
#ifdef _WIN32 #ifdef _WIN32
@@ -42,13 +45,39 @@ class FileDialogWrapper {
#elif defined(__APPLE__) #elif defined(__APPLE__)
#include "TargetConditionals.h"
#include <string> #include <string>
#include <vector>
#ifdef TARGET_OS_MAC
// Other kinds of Mac OS
class FileDialogWrapper { class FileDialogWrapper {
public: public:
static std::string ShowOpenFileDialog(); 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);
}; };
#elif TARGET_OS_IPHONE
// 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__) #elif defined(__linux__)
class FileDialogWrapper { class FileDialogWrapper {
@@ -63,3 +92,5 @@ class FileDialogWrapper {
#else #else
#error "Unsupported platform." #error "Unsupported platform."
#endif #endif
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H

View File

@@ -1,6 +1,49 @@
#import <Cocoa/Cocoa.h>
#include <string>
#include <vector>
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#include <TargetConditionals.h>
#import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1
/* iOS in Xcode simulator */
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 {};
}
#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 {};
}
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
std::string FileDialogWrapper::ShowOpenFileDialog() { std::string FileDialogWrapper::ShowOpenFileDialog() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel]; NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:YES]; [openPanel setCanChooseFiles:YES];
@@ -15,3 +58,59 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
return ""; return "";
} }
std::string FileDialogWrapper::ShowOpenFolderDialog() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO];
[openPanel setCanChooseDirectories:YES];
[openPanel setAllowsMultipleSelection:NO];
if ([openPanel runModal] == NSModalResponseOK) {
NSURL* url = [[openPanel URLs] objectAtIndex:0];
NSString* path = [url path];
return std::string([path UTF8String]);
}
return "";
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) {
std::vector<std::string> filenames;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator =
[fileManager enumeratorAtPath:[NSString stringWithUTF8String:folder.c_str()]];
NSString* file;
while (file = [enumerator nextObject]) {
if ([file hasPrefix:@"."]) {
continue;
}
filenames.push_back(std::string([file UTF8String]));
}
return filenames;
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) {
std::vector<std::string> subdirectories;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator =
[fileManager enumeratorAtPath:[NSString stringWithUTF8String:folder.c_str()]];
NSString* file;
while (file = [enumerator nextObject]) {
if ([file hasPrefix:@"."]) {
continue;
}
BOOL isDirectory;
NSString* path =
[NSString stringWithFormat:@"%@/%@", [NSString stringWithUTF8String:folder.c_str()], file];
[fileManager fileExistsAtPath:path isDirectory:&isDirectory];
if (isDirectory) {
subdirectories.push_back(std::string([file UTF8String]));
}
}
return subdirectories;
}
#else
// Unsupported platform
#endif // TARGET_OS_MAC
#endif // __APPLE__ && __MACH__

View File

@@ -0,0 +1,6 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H
#define YAZE_APP_CORE_PLATFORM_FILE_PATH_H
std::string GetBundleResourcePath();
#endif // YAZE_APP_CORE_PLATFORM_FILE_PATH_H

View File

@@ -0,0 +1,26 @@
#include <iostream>
#include <string>
#if defined(__APPLE__) && defined(__MACH__)
#include <Foundation/Foundation.h>
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
std::string GetBundleResourcePath() {}
#elif TARGET_OS_IPHONE == 1
std::string GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#elif TARGET_OS_MAC == 1
std::string GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#endif
#endif

View File

@@ -1,6 +1,6 @@
#include "app/core/platform/font_loader.h" #include "app/core/platform/font_loader.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <string> #include <string>
#include <vector> #include <vector>

View File

@@ -2,14 +2,25 @@
#ifndef FONTLOADER_H #ifndef FONTLOADER_H
#define FONTLOADER_H #define FONTLOADER_H
// Function declaration for loading system fonts into ImGui #include "TargetConditionals.h"
void LoadSystemFonts();
#ifdef _WIN32 #ifdef _WIN32
#include <Windows.h> #include <Windows.h>
// Windows specific function declaration for loading system fonts into ImGui // Windows specific function declaration for loading system fonts into ImGui
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);
#elif __APPLE__
#ifdef TARGET_OS_MAC
void LoadSystemFonts();
#elif TARGET_OS_IPHONE
void LoadSystemFonts();
#endif
#endif #endif
#endif // FONTLOADER_H #endif // FONTLOADER_H

View File

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

138
src/app/core/project.h Normal file
View File

@@ -0,0 +1,138 @@
#ifndef YAZE_APP_CORE_PROJECT_H
#define YAZE_APP_CORE_PROJECT_H
#include "absl/strings/match.h"
#include <fstream>
#include <string>
#include <string_view>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "app/core/common.h"
namespace yaze {
namespace app {
constexpr absl::string_view kProjectFileExtension = ".yaze";
constexpr absl::string_view kProjectFileFilter =
"Yaze Project Files (*.yaze)\0*.yaze\0";
constexpr absl::string_view kPreviousRomFilenameDelimiter =
"PreviousRomFilename";
constexpr absl::string_view kEndOfProjectFile = "EndOfProjectFile";
/**
* @struct Project
* @brief Represents a project in the application.
*
* A project is a collection of files and resources that are used in the
* creation of a Zelda3 hack that can be saved and loaded. This makes it so the
* user can have different rom file names for a single project and keep track of
* backups.
*/
struct Project : public core::ExperimentFlags {
/**
* @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;
project_opened_ = true;
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() {
if (name.empty() || filepath.empty() || rom_filename_.empty() ||
code_folder_.empty() || labels_filename_.empty()) {
return absl::InvalidArgumentError(
"Project fields cannot be empty. Please load a rom file, set your "
"code folder, and set your labels file. See HELP for more details.");
}
return absl::OkStatus();
}
absl::Status SerializeExperimentFlags() {
auto flags = mutable_flags();
// TODO: Serialize flags
return absl::OkStatus();
}
bool project_opened_ = false;
std::string name;
std::string filepath;
std::string rom_filename_ = "";
std::string code_folder_ = "";
std::string labels_filename_ = "";
std::vector<std::string> previous_rom_filenames_;
};
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_PROJECT_H

19
src/app/core/testable.h Normal file
View File

@@ -0,0 +1,19 @@
#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,21 @@
set(
YAZE_APP_EDITOR_SRC
app/editor/dungeon/dungeon_editor.cc
app/editor/master_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/music/music_editor.cc
app/editor/message/message_editor.cc
app/editor/message/message_editor_test.cc
app/editor/code/assembly_editor.cc
app/editor/graphics/screen_editor.cc
app/editor/graphics/graphics_editor.cc
app/editor/graphics/palette_editor.cc
app/editor/graphics/tile16_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
)

View File

@@ -0,0 +1,367 @@
#include "assembly_editor.h"
#include "ImGuiColorTextEdit/TextEditor.h"
#include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "core/constants.h"
namespace yaze {
namespace app {
namespace editor {
namespace {
std::vector<std::string> RemoveIgnoredFiles(
const std::vector<std::string>& files,
const std::vector<std::string>& ignored_files) {
std::vector<std::string> filtered_files;
for (const auto& file : files) {
// Remove subdirectory files
if (file.find('/') != std::string::npos) {
continue;
}
// Make sure the file has an extension
if (file.find('.') == std::string::npos) {
continue;
}
if (std::find(ignored_files.begin(), ignored_files.end(), file) ==
ignored_files.end()) {
filtered_files.push_back(file);
}
}
return filtered_files;
}
core::FolderItem LoadFolder(const std::string& folder) {
// Check if .gitignore exists in the folder
std::ifstream gitignore(folder + "/.gitignore");
std::vector<std::string> ignored_files;
if (gitignore.good()) {
std::string line;
while (std::getline(gitignore, line)) {
if (line[0] == '#') {
continue;
}
if (line[0] == '!') {
// Ignore the file
continue;
}
ignored_files.push_back(line);
}
}
core::FolderItem current_folder;
current_folder.name = folder;
auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
for (const auto& folder :
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
core::FolderItem folder_item;
folder_item.name = folder;
std::string full_folder = current_folder.name + "/" + folder;
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
for (const auto& files : folder_files) {
// Remove subdirectory files
if (files.find('/') != std::string::npos) {
continue;
}
// Make sure the file has an extension
if (files.find('.') == std::string::npos) {
continue;
}
if (std::find(ignored_files.begin(), ignored_files.end(), files) !=
ignored_files.end()) {
continue;
}
folder_item.files.push_back(files);
}
for (const auto& subdir :
FileDialogWrapper::GetSubdirectoriesInFolder(full_folder)) {
core::FolderItem subfolder_item;
subfolder_item.name = subdir;
subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir);
folder_item.subfolders.push_back(subfolder_item);
}
current_folder.subfolders.push_back(folder_item);
}
return current_folder;
}
} // namespace
void AssemblyEditor::OpenFolder(const std::string& folder_path) {
current_folder_ = LoadFolder(folder_path);
}
void AssemblyEditor::Update(bool& is_loaded) {
ImGui::Begin("Assembly Editor", &is_loaded);
if (ImGui::BeginMenuBar()) {
DrawFileMenu();
DrawEditMenu();
ImGui::EndMenuBar();
}
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor");
ImGui::End();
}
void AssemblyEditor::InlineUpdate() {
ChangeActiveFile("assets/asm/template_song.asm");
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor", ImVec2(0, 0));
}
void AssemblyEditor::UpdateCodeView() {
ImGui::BeginTable("##table_view", 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
ImGuiTableFlags_Resizable);
// Table headers
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 256.0f);
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
// Table data
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (current_folder_.name != "") {
DrawCurrentFolder();
} else {
if (ImGui::Button("Open Folder")) {
current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
}
}
ImGui::TableNextColumn();
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor");
ImGui::EndTable();
}
void AssemblyEditor::DrawCurrentFolder() {
if (ImGui::BeginChild("##current_folder", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (ImGui::BeginTable("##file_table", 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Sortable)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
for (const auto& file : current_folder_.files) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(file.c_str())) {
ChangeActiveFile(absl::StrCat(current_folder_.name, "/", file));
}
ImGui::TableNextColumn();
ImGui::Text("File");
}
for (const auto& subfolder : current_folder_.subfolders) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::TreeNode(subfolder.name.c_str())) {
for (const auto& file : subfolder.files) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(file.c_str())) {
ChangeActiveFile(absl::StrCat(current_folder_.name, "/",
subfolder.name, "/", file));
}
ImGui::TableNextColumn();
ImGui::Text("File");
}
ImGui::TreePop();
} else {
ImGui::TableNextColumn();
ImGui::Text("Folder");
}
}
ImGui::EndTable();
}
ImGui::EndChild();
}
}
void AssemblyEditor::DrawFileTabView() {
static int next_tab_id = 0;
if (ImGui::BeginTabBar("AssemblyFileTabBar", ImGuiTabBarFlags_None)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_None)) {
if (std::find(active_files_.begin(), active_files_.end(),
current_file_id_) != active_files_.end()) {
// Room is already open
next_tab_id++;
}
active_files_.push_back(next_tab_id++); // Add new tab
}
// Submit our regular tabs
for (int n = 0; n < active_files_.Size;) {
bool open = true;
if (ImGui::BeginTabItem(files_[active_files_[n]].data(), &open,
ImGuiTabItemFlags_None)) {
auto cpos = text_editor_.GetCursorPosition();
{
std::ifstream t(current_file_);
if (t.good()) {
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
text_editor_.SetText(str);
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error opening file: %s\n", current_file_.c_str());
}
}
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
open_files_[active_files_[n]].Render("##asm_editor");
ImGui::EndTabItem();
}
if (!open)
active_files_.erase(active_files_.Data + n);
else
n++;
}
ImGui::EndTabBar();
}
ImGui::Separator();
}
void AssemblyEditor::DrawFileMenu() {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) {
ImGuiFileDialog::Instance()->OpenDialog(
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
}
if (ImGui::MenuItem("Save", "Ctrl+S")) {
// TODO: Implement this
}
ImGui::EndMenu();
}
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
if (ImGuiFileDialog::Instance()->IsOk()) {
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
}
ImGuiFileDialog::Instance()->Close();
}
}
void AssemblyEditor::DrawEditMenu() {
if (ImGui::BeginMenu("Edit")) {
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
text_editor_.Undo();
}
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
text_editor_.Redo();
}
ImGui::Separator();
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
text_editor_.Cut();
}
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
text_editor_.Copy();
}
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
text_editor_.Paste();
}
ImGui::Separator();
if (ImGui::MenuItem("Find", "Ctrl+F")) {
// TODO: Implement this.
}
ImGui::EndMenu();
}
}
void AssemblyEditor::SetEditorText() {
if (!file_is_loaded_) {
std::ifstream t(current_file_);
if (t.good()) {
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
text_editor_.SetText(str);
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n",
current_file_.c_str());
}
file_is_loaded_ = true;
}
}
absl::Status AssemblyEditor::Cut() {
text_editor_.Cut();
return absl::OkStatus();
}
absl::Status AssemblyEditor::Copy() {
text_editor_.Copy();
return absl::OkStatus();
}
absl::Status AssemblyEditor::Paste() {
text_editor_.Paste();
return absl::OkStatus();
}
absl::Status AssemblyEditor::Undo() {
text_editor_.Undo();
return absl::OkStatus();
}
absl::Status AssemblyEditor::Redo() {
text_editor_.Redo();
return absl::OkStatus();
}
absl::Status AssemblyEditor::Update() { return absl::OkStatus(); }
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,79 @@
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include <fstream>
#include <istream>
#include <string>
#include "app/core/common.h"
#include "app/editor/utils/editor.h"
#include "app/gui/style.h"
namespace yaze {
namespace app {
namespace editor {
/**
* @class AssemblyEditor
* @brief Text editor for modifying assembly code.
*/
class AssemblyEditor : public Editor {
public:
AssemblyEditor() {
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
text_editor_.SetPalette(TextEditor::GetDarkPalette());
text_editor_.SetShowWhitespaces(false);
type_ = EditorType::kAssembly;
}
void ChangeActiveFile(const std::string_view &filename) {
current_file_ = filename;
file_is_loaded_ = false;
}
void Update(bool &is_loaded);
void InlineUpdate();
void UpdateCodeView();
absl::Status Cut() override;
absl::Status Copy() override;
absl::Status Paste() override;
absl::Status Undo() override;
absl::Status Redo() override;
absl::Status Find() override { return absl::UnimplementedError("Find"); }
absl::Status Update() override;
void OpenFolder(const std::string &folder_path);
private:
void DrawFileMenu();
void DrawEditMenu();
void SetEditorText();
void DrawCurrentFolder();
void DrawFileTabView();
bool file_is_loaded_ = false;
std::vector<std::string> files_;
std::vector<TextEditor> open_files_;
ImVector<int> active_files_;
int current_file_id_ = 0;
std::string current_file_;
core::FolderItem current_folder_;
TextEditor text_editor_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif

View File

@@ -0,0 +1,88 @@
#ifndef 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 "app/core/common.h"
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h"
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_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/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/style.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::SameLine;
using ImGui::Text;
struct MemoryEditorWithDiffChecker : public SharedRom {
void Update(bool &show_memory_editor) {
static MemoryEditor mem_edit;
static MemoryEditor comp_edit;
static bool show_compare_rom = false;
static Rom comparison_rom;
ImGui::Begin("Hex Editor", &show_memory_editor);
if (ImGui::Button("Compare Rom")) {
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name));
show_compare_rom = true;
}
static uint64_t convert_address = 0;
gui::InputHex("SNES to PC", (int *)&convert_address, 6, 200.f);
SameLine();
Text("%x", core::SnesToPc(convert_address));
// mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
BEGIN_TABLE("Memory Comparison", 2, ImGuiTableFlags_Resizable);
SETUP_COLUMN("Source")
SETUP_COLUMN("Dest")
NEXT_COLUMN()
Text("%s", rom()->filename().data());
mem_edit.DrawContents((void *)&(*rom()), rom()->size());
NEXT_COLUMN()
if (show_compare_rom) {
comp_edit.SetComparisonData((void *)&(*rom()));
ImGui::BeginGroup();
ImGui::BeginChild("Comparison ROM");
Text("%s", comparison_rom.filename().data());
comp_edit.DrawContents((void *)&(comparison_rom), comparison_rom.size());
ImGui::EndChild();
ImGui::EndGroup();
}
END_TABLE()
ImGui::End();
}
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H

View File

@@ -1,42 +0,0 @@
#ifndef YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_
#define YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_
#include <cstdint>
#include <vector>
#include "absl/status/status.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
namespace context {
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_;
};
} // namespace context
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_

View File

@@ -1,14 +1,14 @@
#include "dungeon_editor.h" #include "dungeon_editor.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/labeling.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/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/pipeline.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 "app/zelda3/dungeon/room_names.h"
@@ -18,15 +18,26 @@ namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
constexpr ImGuiTableFlags kDungeonObjectTableFlags = using ImGui::BeginChild;
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | using ImGui::BeginTabBar;
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | using ImGui::BeginTabItem;
ImGuiTableFlags_BordersV; using ImGui::BeginTable;
using ImGui::Button;
using ImGui::EndChild;
using ImGui::EndTabBar;
using ImGui::EndTabItem;
using ImGui::RadioButton;
using ImGui::SameLine;
using ImGui::TableHeadersRow; using ImGui::TableHeadersRow;
using ImGui::TableNextColumn; using ImGui::TableNextColumn;
using ImGui::TableNextRow; using ImGui::TableNextRow;
using ImGui::TableSetupColumn; using ImGui::TableSetupColumn;
using ImGui::Text;
constexpr ImGuiTableFlags kDungeonObjectTableFlags =
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV;
absl::Status DungeonEditor::Update() { absl::Status DungeonEditor::Update() {
if (!is_loaded_ && rom()->is_loaded()) { if (!is_loaded_ && rom()->is_loaded()) {
@@ -41,7 +52,7 @@ absl::Status DungeonEditor::Update() {
TAB_BAR("##DungeonEditorTabBar") TAB_BAR("##DungeonEditorTabBar")
TAB_ITEM("Room Editor") TAB_ITEM("Room Editor")
UpdateDungeonRoomView(); status_ = UpdateDungeonRoomView();
END_TAB_ITEM() END_TAB_ITEM()
TAB_ITEM("Usage Statistics") TAB_ITEM("Usage Statistics")
if (is_loaded_) { if (is_loaded_) {
@@ -82,7 +93,14 @@ absl::Status DungeonEditor::Initialize() {
} }
LoadDungeonRoomSize(); LoadDungeonRoomSize();
LoadRoomEntrances(); // LoadRoomEntrances
for (int i = 0; i < 0x07; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, true));
}
for (int i = 0; i < 0x85; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, false));
}
// Load the palette group and palette for the dungeon // Load the palette group and palette for the dungeon
full_palette_ = dungeon_man_pal_group[current_palette_group_id_]; full_palette_ = dungeon_man_pal_group[current_palette_group_id_];
@@ -92,7 +110,7 @@ absl::Status DungeonEditor::Initialize() {
graphics_bin_ = *rom()->mutable_bitmap_manager(); graphics_bin_ = *rom()->mutable_bitmap_manager();
// 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].get()); room_gfx_sheets_.emplace_back(&graphics_bin_[block]);
} }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -100,16 +118,16 @@ absl::Status DungeonEditor::Initialize() {
absl::Status DungeonEditor::RefreshGraphics() { absl::Status DungeonEditor::RefreshGraphics() {
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
int block = rooms_[current_room_id_].blocks()[i]; int block = rooms_[current_room_id_].blocks()[i];
RETURN_IF_ERROR(graphics_bin_[block].get()->ApplyPaletteWithTransparent( RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
current_palette_group_[current_palette_id_], 0)); current_palette_group_[current_palette_id_], 0));
rom()->UpdateBitmap(graphics_bin_[block].get(), true); rom()->UpdateBitmap(&graphics_bin_[block], true);
} }
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++) { for (int i = 9; i < 16; i++) {
int block = rooms_[current_room_id_].blocks()[i]; int block = rooms_[current_room_id_].blocks()[i];
graphics_bin_[block].get()->ApplyPaletteWithTransparent( RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
sprites_aux1_pal_group[current_palette_id_], 0); sprites_aux1_pal_group[current_palette_id_], 0));
rom()->UpdateBitmap(graphics_bin_[block].get(), true); rom()->UpdateBitmap(&graphics_bin_[block], true);
} }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -168,8 +186,7 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() {
ImGui::End(); ImGui::End();
} }
if (ImGui::BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, ImVec2(0, 0))) {
ImVec2(0, 0))) {
TableSetupColumn("Room Selector"); TableSetupColumn("Room Selector");
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch, TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x); ImGui::GetContentRegionAvail().x);
@@ -198,8 +215,8 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() {
} }
void DungeonEditor::DrawToolset() { void DungeonEditor::DrawToolset() {
if (ImGui::BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit, if (BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit,
ImVec2(0, 0))) { ImVec2(0, 0))) {
TableSetupColumn("#undoTool"); TableSetupColumn("#undoTool");
TableSetupColumn("#redoTool"); TableSetupColumn("#redoTool");
TableSetupColumn("#separator"); TableSetupColumn("#separator");
@@ -215,47 +232,43 @@ void DungeonEditor::DrawToolset() {
TableSetupColumn("#blockTool"); TableSetupColumn("#blockTool");
TableNextColumn(); TableNextColumn();
if (ImGui::Button(ICON_MD_UNDO)) { if (Button(ICON_MD_UNDO)) {
PRINT_IF_ERROR(Undo()); PRINT_IF_ERROR(Undo());
} }
TableNextColumn(); TableNextColumn();
if (ImGui::Button(ICON_MD_REDO)) { if (Button(ICON_MD_REDO)) {
PRINT_IF_ERROR(Redo()); PRINT_IF_ERROR(Redo());
} }
TableNextColumn(); TableNextColumn();
ImGui::Text(ICON_MD_MORE_VERT); Text(ICON_MD_MORE_VERT);
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_NONE, if (RadioButton(ICON_MD_FILTER_NONE, background_type_ == kBackgroundAny)) {
background_type_ == kBackgroundAny)) {
background_type_ = kBackgroundAny; background_type_ = kBackgroundAny;
} }
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_1, if (RadioButton(ICON_MD_FILTER_1, background_type_ == kBackground1)) {
background_type_ == kBackground1)) {
background_type_ = kBackground1; background_type_ = kBackground1;
} }
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_2, if (RadioButton(ICON_MD_FILTER_2, background_type_ == kBackground2)) {
background_type_ == kBackground2)) {
background_type_ = kBackground2; background_type_ = kBackground2;
} }
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_FILTER_3, if (RadioButton(ICON_MD_FILTER_3, background_type_ == kBackground3)) {
background_type_ == kBackground3)) {
background_type_ = kBackground3; background_type_ = kBackground3;
} }
TableNextColumn(); TableNextColumn();
ImGui::Text(ICON_MD_MORE_VERT); Text(ICON_MD_MORE_VERT);
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) { if (RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
placement_type_ = kSprite; placement_type_ = kSprite;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
@@ -263,7 +276,7 @@ void DungeonEditor::DrawToolset() {
} }
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) { if (RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
placement_type_ = kItem; placement_type_ = kItem;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
@@ -271,7 +284,7 @@ void DungeonEditor::DrawToolset() {
} }
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) { if (RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
placement_type_ = kDoor; placement_type_ = kDoor;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
@@ -279,7 +292,7 @@ void DungeonEditor::DrawToolset() {
} }
TableNextColumn(); TableNextColumn();
if (ImGui::RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) { if (RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) {
placement_type_ = kBlock; placement_type_ = kBlock;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
@@ -287,7 +300,7 @@ void DungeonEditor::DrawToolset() {
} }
TableNextColumn(); TableNextColumn();
if (ImGui::Button(ICON_MD_PALETTE)) { if (Button(ICON_MD_PALETTE)) {
palette_showing_ = !palette_showing_; palette_showing_ = !palette_showing_;
} }
@@ -301,8 +314,8 @@ void DungeonEditor::DrawRoomSelector() {
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);
ImGui::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::dungeon::kRoomNames) {
rom()->resource_label()->SelectableLabelWithNameEdit( rom()->resource_label()->SelectableLabelWithNameEdit(
@@ -318,86 +331,66 @@ void DungeonEditor::DrawRoomSelector() {
i += 1; i += 1;
} }
} }
ImGui::EndChild(); EndChild();
} }
} }
using ImGui::Separator;
void DungeonEditor::DrawEntranceSelector() { void DungeonEditor::DrawEntranceSelector() {
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
gui::InputHexWord("Entrance ID", auto current_entrance = entrances_[current_entrance_id_];
&entrances_[current_entrance_id_].entrance_id_); gui::InputHexWord("Entrance ID", &current_entrance.entrance_id_);
gui::InputHexWord("Room ID", &current_entrance.room_, 50.f, true);
SameLine();
gui::InputHexWord("Room ID", &entrances_[current_entrance_id_].room_, 50.f, gui::InputHexByte("Dungeon ID", &current_entrance.dungeon_id_, 50.f, true);
gui::InputHexByte("Blockset", &current_entrance.blockset_, 50.f, true);
SameLine();
gui::InputHexByte("Music", &current_entrance.music_, 50.f, true);
SameLine();
gui::InputHexByte("Floor", &current_entrance.floor_);
Separator();
gui::InputHexWord("Player X ", &current_entrance.x_position_);
SameLine();
gui::InputHexWord("Player Y ", &current_entrance.y_position_);
gui::InputHexWord("Camera X", &current_entrance.camera_trigger_x_);
SameLine();
gui::InputHexWord("Camera Y", &current_entrance.camera_trigger_y_);
gui::InputHexWord("Scroll X ", &current_entrance.camera_x_);
SameLine();
gui::InputHexWord("Scroll Y ", &current_entrance.camera_y_);
gui::InputHexWord("Exit", &current_entrance.exit_, 50.f, true);
Separator();
Text("Camera Boundaries");
Separator();
Text("\t\t\t\t\tNorth East South West");
gui::InputHexByte("Quadrant", &current_entrance.camera_boundary_qn_, 50.f,
true); true);
ImGui::SameLine(); SameLine();
gui::InputHexByte("Dungeon ID", gui::InputHexByte("", &current_entrance.camera_boundary_qe_, 50.f, true);
&entrances_[current_entrance_id_].dungeon_id_, 50.f, SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qs_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qw_, 50.f, true);
gui::InputHexByte("Full room", &current_entrance.camera_boundary_fn_, 50.f,
true); true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fe_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fs_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fw_, 50.f, true);
gui::InputHexByte("Blockset", &entrances_[current_entrance_id_].blockset_, if (BeginChild("EntranceSelector", ImVec2(0, 0), true,
50.f, true); ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
ImGui::SameLine();
gui::InputHexByte("Music", &entrances_[current_entrance_id_].music_, 50.f,
true);
ImGui::SameLine();
gui::InputHexByte("Floor", &entrances_[current_entrance_id_].floor_);
ImGui::Separator();
gui::InputHexWord("Player X ",
&entrances_[current_entrance_id_].x_position_);
ImGui::SameLine();
gui::InputHexWord("Player Y ",
&entrances_[current_entrance_id_].y_position_);
gui::InputHexWord("Camera X",
&entrances_[current_entrance_id_].camera_trigger_x_);
ImGui::SameLine();
gui::InputHexWord("Camera Y",
&entrances_[current_entrance_id_].camera_trigger_y_);
gui::InputHexWord("Scroll X ",
&entrances_[current_entrance_id_].camera_x_);
ImGui::SameLine();
gui::InputHexWord("Scroll Y ",
&entrances_[current_entrance_id_].camera_y_);
gui::InputHexWord("Exit", &entrances_[current_entrance_id_].exit_, 50.f,
true);
ImGui::Separator();
ImGui::Text("Camera Boundaries");
ImGui::Separator();
ImGui::Text("\t\t\t\t\tNorth East South West");
gui::InputHexByte("Quadrant",
&entrances_[current_entrance_id_].camera_boundary_qn_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_qe_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_qs_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_qw_,
50.f, true);
gui::InputHexByte("Full room",
&entrances_[current_entrance_id_].camera_boundary_fn_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_fe_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_fs_,
50.f, true);
ImGui::SameLine();
gui::InputHexByte("", &entrances_[current_entrance_id_].camera_boundary_fw_,
50.f, true);
if (ImGui::BeginChild("EntranceSelector", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
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",
@@ -412,15 +405,15 @@ void DungeonEditor::DrawEntranceSelector() {
} }
} }
} }
ImGui::EndChild(); EndChild();
} }
} }
void DungeonEditor::DrawDungeonTabView() { void DungeonEditor::DrawDungeonTabView() {
static int next_tab_id = 0; static int next_tab_id = 0;
if (ImGui::BeginTabBar("MyTabBar", kDungeonTabBarFlags)) { if (BeginTabBar("MyTabBar", kDungeonTabBarFlags)) {
if (ImGui::TabItemButton("+", kDungeonTabFlags)) { if (ImGui::TabItemButton(ICON_MD_ADD, kDungeonTabFlags)) {
if (std::find(active_rooms_.begin(), active_rooms_.end(), if (std::find(active_rooms_.begin(), active_rooms_.end(),
current_room_id_) != active_rooms_.end()) { current_room_id_) != active_rooms_.end()) {
// Room is already open // Room is already open
@@ -438,11 +431,10 @@ void DungeonEditor::DrawDungeonTabView() {
continue; continue;
} }
if (ImGui::BeginTabItem( if (BeginTabItem(zelda3::dungeon::kRoomNames[active_rooms_[n]].data(),
zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open, &open, ImGuiTabItemFlags_None)) {
ImGuiTabItemFlags_None)) {
DrawDungeonCanvas(active_rooms_[n]); DrawDungeonCanvas(active_rooms_[n]);
ImGui::EndTabItem(); EndTabItem();
} }
if (!open) if (!open)
@@ -451,33 +443,33 @@ void DungeonEditor::DrawDungeonTabView() {
n++; n++;
} }
ImGui::EndTabBar(); EndTabBar();
} }
ImGui::Separator(); Separator();
} }
void DungeonEditor::DrawDungeonCanvas(int room_id) { void DungeonEditor::DrawDungeonCanvas(int room_id) {
ImGui::BeginGroup(); ImGui::BeginGroup();
gui::InputHexByte("Layout", &rooms_[room_id].layout); gui::InputHexByte("Layout", &rooms_[room_id].layout);
ImGui::SameLine(); SameLine();
gui::InputHexByte("Blockset", &rooms_[room_id].blockset); gui::InputHexByte("Blockset", &rooms_[room_id].blockset);
ImGui::SameLine(); SameLine();
gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset); gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset);
ImGui::SameLine(); SameLine();
gui::InputHexByte("Palette", &rooms_[room_id].palette); gui::InputHexByte("Palette", &rooms_[room_id].palette);
gui::InputHexByte("Floor1", &rooms_[room_id].floor1); gui::InputHexByte("Floor1", &rooms_[room_id].floor1);
ImGui::SameLine(); SameLine();
gui::InputHexByte("Floor2", &rooms_[room_id].floor2); gui::InputHexByte("Floor2", &rooms_[room_id].floor2);
ImGui::SameLine(); SameLine();
gui::InputHexWord("Message ID", &rooms_[room_id].message_id_); gui::InputHexWord("Message ID", &rooms_[room_id].message_id_);
ImGui::SameLine(); SameLine();
ImGui::EndGroup(); ImGui::EndGroup();
@@ -492,7 +484,8 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
void DungeonEditor::DrawRoomGraphics() { void DungeonEditor::DrawRoomGraphics() {
const auto height = 0x40; const auto height = 0x40;
room_gfx_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x10 * 0x40 + 1)); const int num_sheets = 0x10;
room_gfx_canvas_.DrawBackground(ImVec2(0x100 + 1, num_sheets * height + 1));
room_gfx_canvas_.DrawContextMenu(); room_gfx_canvas_.DrawContextMenu();
room_gfx_canvas_.DrawTileSelector(32); room_gfx_canvas_.DrawTileSelector(32);
if (is_loaded_) { if (is_loaded_) {
@@ -505,7 +498,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].get()->texture(), (void*)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));
@@ -517,34 +510,34 @@ void DungeonEditor::DrawRoomGraphics() {
} }
void DungeonEditor::DrawTileSelector() { void DungeonEditor::DrawTileSelector() {
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) { if (BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
if (ImGui::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);
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) { ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
DrawRoomGraphics(); DrawRoomGraphics();
} }
ImGui::EndChild(); EndChild();
ImGui::EndTabItem(); EndTabItem();
} }
if (ImGui::BeginTabItem("Object Renderer")) { if (BeginTabItem("Object Renderer")) {
DrawObjectRenderer(); DrawObjectRenderer();
ImGui::EndTabItem(); EndTabItem();
} }
ImGui::EndTabBar(); EndTabBar();
} }
} }
void DungeonEditor::DrawObjectRenderer() { void DungeonEditor::DrawObjectRenderer() {
if (ImGui::BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags, if (BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags,
ImVec2(0, 0))) { ImVec2(0, 0))) {
TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch, TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x); ImGui::GetContentRegionAvail().x);
TableSetupColumn("Canvas"); TableSetupColumn("Canvas");
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true); BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
int selected_object = 0; int selected_object = 0;
int i = 0; int i = 0;
@@ -560,12 +553,11 @@ void DungeonEditor::DrawObjectRenderer() {
i += 1; i += 1;
} }
ImGui::EndChild(); EndChild();
// Right side of the table - Canvas // Right side of the table - Canvas
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), true);
true);
object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1)); object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
object_canvas_.DrawContextMenu(); object_canvas_.DrawContextMenu();
@@ -576,7 +568,7 @@ void DungeonEditor::DrawObjectRenderer() {
object_canvas_.DrawGrid(32.0f); object_canvas_.DrawGrid(32.0f);
object_canvas_.DrawOverlay(); object_canvas_.DrawOverlay();
ImGui::EndChild(); EndChild();
ImGui::EndTable(); ImGui::EndTable();
} }
@@ -589,16 +581,6 @@ void DungeonEditor::DrawObjectRenderer() {
} }
} }
void DungeonEditor::LoadRoomEntrances() {
for (int i = 0; i < 0x07; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, true));
}
for (int i = 0; i < 0x85; ++i) {
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, false));
}
}
// ============================================================================ // ============================================================================
void DungeonEditor::CalculateUsageStats() { void DungeonEditor::CalculateUsageStats() {
@@ -663,16 +645,16 @@ void RenderUnusedSets(const absl::flat_hash_map<T, int>& usage_map, int max_set,
} }
for (const auto& set : unused_sets) { for (const auto& set : unused_sets) {
if (spriteset_offset != 0x00) { if (spriteset_offset != 0x00) {
ImGui::Text("%#02x, %#02x", set, (set + spriteset_offset)); Text("%#02x, %#02x", set, (set + spriteset_offset));
} else { } else {
ImGui::Text("%#02x", set); Text("%#02x", set);
} }
} }
} }
} // namespace } // namespace
void DungeonEditor::DrawUsageStats() { void DungeonEditor::DrawUsageStats() {
if (ImGui::Button("Refresh")) { if (Button("Refresh")) {
selected_blockset_ = 0xFFFF; selected_blockset_ = 0xFFFF;
selected_spriteset_ = 0xFFFF; selected_spriteset_ = 0xFFFF;
selected_palette_ = 0xFFFF; selected_palette_ = 0xFFFF;
@@ -684,9 +666,9 @@ void DungeonEditor::DrawUsageStats() {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
if (ImGui::BeginTable("DungeonUsageStatsTable", 8, if (BeginTable("DungeonUsageStatsTable", 8,
kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit, kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit,
ImGui::GetContentRegionAvail())) { ImGui::GetContentRegionAvail())) {
TableSetupColumn("Blockset Usage"); TableSetupColumn("Blockset Usage");
TableSetupColumn("Unused Blockset"); TableSetupColumn("Unused Blockset");
TableSetupColumn("Palette Usage"); TableSetupColumn("Palette Usage");
@@ -699,51 +681,50 @@ void DungeonEditor::DrawUsageStats() {
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true, BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar); ImGuiWindowFlags_HorizontalScrollbar);
RenderSetUsage(blockset_usage_, selected_blockset_); RenderSetUsage(blockset_usage_, selected_blockset_);
ImGui::EndChild(); EndChild();
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true, BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar); ImGuiWindowFlags_HorizontalScrollbar);
RenderUnusedSets(blockset_usage_, 0x25); RenderUnusedSets(blockset_usage_, 0x25);
ImGui::EndChild(); EndChild();
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("PaletteUsageScroll", ImVec2(0, 0), true, BeginChild("PaletteUsageScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar); ImGuiWindowFlags_HorizontalScrollbar);
RenderSetUsage(palette_usage_, selected_palette_); RenderSetUsage(palette_usage_, selected_palette_);
ImGui::EndChild(); EndChild();
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true, BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar); ImGuiWindowFlags_HorizontalScrollbar);
RenderUnusedSets(palette_usage_, 0x48); RenderUnusedSets(palette_usage_, 0x48);
ImGui::EndChild(); EndChild();
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true, BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar); ImGuiWindowFlags_HorizontalScrollbar);
RenderSetUsage(spriteset_usage_, selected_spriteset_, 0x40); RenderSetUsage(spriteset_usage_, selected_spriteset_, 0x40);
ImGui::EndChild(); EndChild();
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true, BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar); ImGuiWindowFlags_HorizontalScrollbar);
RenderUnusedSets(spriteset_usage_, 0x90, 0x40); RenderUnusedSets(spriteset_usage_, 0x90, 0x40);
ImGui::EndChild(); EndChild();
TableNextColumn(); TableNextColumn();
ImGui::BeginChild("UsageGrid", ImVec2(0, 0), true, BeginChild("UsageGrid", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar); ImGuiWindowFlags_HorizontalScrollbar);
ImGui::Text("%s", Text("%s", absl::StrFormat("Total size of all rooms: %d hex format: %#06x",
absl::StrFormat("Total size of all rooms: %d hex format: %#06x", total_room_size_, total_room_size_)
total_room_size_, total_room_size_) .c_str());
.c_str());
DrawUsageGrid(); DrawUsageGrid();
ImGui::EndChild(); EndChild();
TableNextColumn(); TableNextColumn();
if (selected_blockset_ < 0x25) { if (selected_blockset_ < 0x25) {
@@ -801,10 +782,10 @@ void DungeonEditor::DrawUsageGrid() {
ImGuiCol_Button, ImGuiCol_Button,
ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color
} }
if (ImGui::Button(absl::StrFormat( if (Button(absl::StrFormat("%#x",
"%#x", rooms_[row * squaresWide + col].room_size()) rooms_[row * squaresWide + col].room_size())
.c_str(), .c_str(),
ImVec2(55, 30))) { ImVec2(55, 30))) {
// Switch over to the room editor tab // Switch over to the room editor tab
// and add a room tab by the ID of the square // and add a room tab by the ID of the square
// that was clicked // that was clicked
@@ -826,20 +807,20 @@ void DungeonEditor::DrawUsageGrid() {
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
// Display a tooltip with all the room properties // Display a tooltip with all the room properties
ImGui::BeginTooltip(); ImGui::BeginTooltip();
ImGui::Text("Room ID: %d", row * squaresWide + col); Text("Room ID: %d", row * squaresWide + col);
ImGui::Text("Blockset: %#02x", room.blockset); Text("Blockset: %#02x", room.blockset);
ImGui::Text("Spriteset: %#02x", room.spriteset); Text("Spriteset: %#02x", room.spriteset);
ImGui::Text("Palette: %#02x", room.palette); Text("Palette: %#02x", room.palette);
ImGui::Text("Floor1: %#02x", room.floor1); Text("Floor1: %#02x", room.floor1);
ImGui::Text("Floor2: %#02x", room.floor2); Text("Floor2: %#02x", room.floor2);
ImGui::Text("Message ID: %#04x", room.message_id_); Text("Message ID: %#04x", room.message_id_);
ImGui::Text("Size: %#06x", room.room_size()); Text("Size: %#016llx", room.room_size());
ImGui::Text("Size Pointer: %#06x", room.room_size_ptr()); Text("Size Pointer: %#016llx", room.room_size_ptr());
ImGui::EndTooltip(); ImGui::EndTooltip();
} }
// Keep squares in the same line // Keep squares in the same line
ImGui::SameLine(); SameLine();
} }
} }
} }

View File

@@ -1,13 +1,13 @@
#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 "imgui/imgui.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/editor/utils/editor.h"
#include "app/core/labeling.h" #include "app/core/labeling.h"
#include "app/editor/modules/gfx_group_editor.h" #include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/modules/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/gui/icons.h"
#include "app/rom.h" #include "app/rom.h"
@@ -46,12 +46,15 @@ class DungeonEditor : public Editor,
public SharedRom, public SharedRom,
public core::ExperimentFlags { public core::ExperimentFlags {
public: public:
DungeonEditor() { type_ = EditorType::kDungeon; }
absl::Status Update() override; absl::Status Update() override;
absl::Status Cut() override { return absl::OkStatus(); } absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Copy() override { return absl::OkStatus(); } absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Paste() override { return absl::OkStatus(); } absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Undo() override { return absl::OkStatus(); } absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Redo() override { return absl::OkStatus(); } absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
void add_room(int i) { active_rooms_.push_back(i); } void add_room(int i) { active_rooms_.push_back(i); }
@@ -74,8 +77,6 @@ class DungeonEditor : public Editor,
void DrawTileSelector(); void DrawTileSelector();
void DrawObjectRenderer(); void DrawObjectRenderer();
void LoadRoomEntrances();
void CalculateUsageStats(); void CalculateUsageStats();
void DrawUsageStats(); void DrawUsageStats();
void DrawUsageGrid(); void DrawUsageGrid();
@@ -141,6 +142,8 @@ class DungeonEditor : public Editor,
std::unordered_map<int, int> room_size_addresses_; std::unordered_map<int, int> room_size_addresses_;
std::unordered_map<int, ImVec4> room_palette_; std::unordered_map<int, ImVec4> room_palette_;
absl::Status status_;
}; };
} // namespace editor } // namespace editor

View File

@@ -0,0 +1,311 @@
#include "gfx_group_editor.h"
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::BeginChild;
using ImGui::BeginGroup;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
using ImGui::BeginTable;
using ImGui::ColorButton;
using ImGui::EndChild;
using ImGui::EndGroup;
using ImGui::EndTabBar;
using ImGui::EndTabItem;
using ImGui::EndTable;
using ImGui::GetContentRegionAvail;
using ImGui::GetStyle;
using ImGui::IsItemClicked;
using ImGui::PopID;
using ImGui::PushID;
using ImGui::SameLine;
using ImGui::Selectable;
using ImGui::Separator;
using ImGui::SetNextItemWidth;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
using gfx::kPaletteGroupNames;
using gfx::PaletteCategory;
absl::Status GfxGroupEditor::Update() {
if (BeginTabBar("GfxGroupEditor")) {
if (BeginTabItem("Main")) {
gui::InputHexByte("Selected Blockset", &selected_blockset_,
(uint8_t)0x24);
rom()->resource_label()->SelectableLabelWithNameEdit(
false, "blockset", "0x" + std::to_string(selected_blockset_),
"Blockset " + std::to_string(selected_blockset_));
DrawBlocksetViewer();
EndTabItem();
}
if (BeginTabItem("Rooms")) {
gui::InputHexByte("Selected Blockset", &selected_roomset_, (uint8_t)81);
rom()->resource_label()->SelectableLabelWithNameEdit(
false, "roomset", "0x" + std::to_string(selected_roomset_),
"Roomset " + std::to_string(selected_roomset_));
DrawRoomsetViewer();
EndTabItem();
}
if (BeginTabItem("Sprites")) {
gui::InputHexByte("Selected Spriteset", &selected_spriteset_,
(uint8_t)143);
rom()->resource_label()->SelectableLabelWithNameEdit(
false, "spriteset", "0x" + std::to_string(selected_spriteset_),
"Spriteset " + std::to_string(selected_spriteset_));
Text("Values");
DrawSpritesetViewer();
EndTabItem();
}
if (BeginTabItem("Palettes")) {
DrawPaletteViewer();
EndTabItem();
}
EndTabBar();
}
return absl::OkStatus();
}
void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
if (BeginTable("##BlocksetTable", sheet_only ? 1 : 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
ImVec2(0, 0))) {
if (!sheet_only) {
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
}
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
if (!sheet_only) {
TableNextColumn();
{
BeginGroup();
for (int i = 0; i < 8; i++) {
SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->main_blockset_ids[selected_blockset_][i]);
}
EndGroup();
}
}
TableNextColumn();
{
BeginGroup();
for (int i = 0; i < 8; i++) {
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
auto sheet = rom()->bitmap_manager()[sheet_id];
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 22);
}
EndGroup();
}
EndTable();
}
}
void GfxGroupEditor::DrawRoomsetViewer() {
Text("Values - Overwrites 4 of main blockset");
if (BeginTable("##Roomstable", 3,
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
ImVec2(0, 0))) {
TableSetupColumn("List", ImGuiTableColumnFlags_WidthFixed, 100);
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
TableNextColumn();
{
BeginChild("##RoomsetList");
for (int i = 0; i < 0x51; i++) {
BeginGroup();
std::string roomset_label = absl::StrFormat("0x%02X", i);
rom()->resource_label()->SelectableLabelWithNameEdit(
false, "roomset", roomset_label, "Roomset " + roomset_label);
if (IsItemClicked()) {
selected_roomset_ = i;
}
EndGroup();
}
EndChild();
}
TableNextColumn();
{
BeginGroup();
for (int i = 0; i < 4; i++) {
SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->room_blockset_ids[selected_roomset_][i]);
}
EndGroup();
}
TableNextColumn();
{
BeginGroup();
for (int i = 0; i < 4; i++) {
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
auto sheet = rom()->bitmap_manager()[sheet_id];
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 23);
}
EndGroup();
}
EndTable();
}
}
void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
if (BeginTable("##SpritesTable", sheet_only ? 1 : 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
ImVec2(0, 0))) {
if (!sheet_only) {
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
}
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
if (!sheet_only) {
TableNextColumn();
{
BeginGroup();
for (int i = 0; i < 4; i++) {
SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->spriteset_ids[selected_spriteset_][i]);
}
EndGroup();
}
}
TableNextColumn();
{
BeginGroup();
for (int i = 0; i < 4; i++) {
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
auto sheet = rom()->bitmap_manager()[115 + sheet_id];
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 24);
}
EndGroup();
}
EndTable();
}
}
namespace {
void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
if (palette.empty()) {
return;
}
for (int n = 0; n < palette.size(); n++) {
PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
// Small icon of the color in the palette
if (gui::SnesColorButton(absl::StrCat("Palette", n), palette[n],
ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip)) {
}
PopID();
}
}
} // namespace
void GfxGroupEditor::DrawPaletteViewer() {
gui::InputHexByte("Selected Paletteset", &selected_paletteset_);
if (selected_paletteset_ >= 71) {
selected_paletteset_ = 71;
}
rom()->resource_label()->SelectableLabelWithNameEdit(
false, "paletteset", "0x" + std::to_string(selected_paletteset_),
"Paletteset " + std::to_string(selected_paletteset_));
uint8_t &dungeon_main_palette_val =
rom()->paletteset_ids[selected_paletteset_][0];
uint8_t &dungeon_spr_pal_1_val =
rom()->paletteset_ids[selected_paletteset_][1];
uint8_t &dungeon_spr_pal_2_val =
rom()->paletteset_ids[selected_paletteset_][2];
uint8_t &dungeon_spr_pal_3_val =
rom()->paletteset_ids[selected_paletteset_][3];
gui::InputHexByte("Dungeon Main", &dungeon_main_palette_val);
rom()->resource_label()->SelectableLabelWithNameEdit(
false, kPaletteGroupNames[PaletteCategory::kDungeons].data(),
std::to_string(dungeon_main_palette_val), "Unnamed dungeon palette");
auto &palette = *rom()->mutable_palette_group()->dungeon_main.mutable_palette(
rom()->paletteset_ids[selected_paletteset_][0]);
DrawPaletteFromPaletteGroup(palette);
Separator();
gui::InputHexByte("Dungeon Spr Pal 1", &dungeon_spr_pal_1_val);
auto &spr_aux_pal1 =
*rom()->mutable_palette_group()->sprites_aux1.mutable_palette(
rom()->paletteset_ids[selected_paletteset_][1]);
DrawPaletteFromPaletteGroup(spr_aux_pal1);
SameLine();
rom()->resource_label()->SelectableLabelWithNameEdit(
false, kPaletteGroupNames[PaletteCategory::kSpritesAux1].data(),
std::to_string(dungeon_spr_pal_1_val), "Dungeon Spr Pal 1");
Separator();
gui::InputHexByte("Dungeon Spr Pal 2", &dungeon_spr_pal_2_val);
auto &spr_aux_pal2 =
*rom()->mutable_palette_group()->sprites_aux2.mutable_palette(
rom()->paletteset_ids[selected_paletteset_][2]);
DrawPaletteFromPaletteGroup(spr_aux_pal2);
SameLine();
rom()->resource_label()->SelectableLabelWithNameEdit(
false, kPaletteGroupNames[PaletteCategory::kSpritesAux2].data(),
std::to_string(dungeon_spr_pal_2_val), "Dungeon Spr Pal 2");
Separator();
gui::InputHexByte("Dungeon Spr Pal 3", &dungeon_spr_pal_3_val);
auto &spr_aux_pal3 =
*rom()->mutable_palette_group()->sprites_aux3.mutable_palette(
rom()->paletteset_ids[selected_paletteset_][3]);
DrawPaletteFromPaletteGroup(spr_aux_pal3);
SameLine();
rom()->resource_label()->SelectableLabelWithNameEdit(
false, kPaletteGroupNames[PaletteCategory::kSpritesAux3].data(),
std::to_string(dungeon_spr_pal_3_val), "Dungeon Spr Pal 3");
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,21 +1,19 @@
#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 "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/editor/utils/editor.h" #include "app/editor/utils/editor.h"
#include "app/editor/modules/palette_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/icons.h"
#include "app/gui/pipeline.h" #include "app/gui/style.h"
#include "app/gui/widgets.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
@@ -42,7 +40,9 @@ class GfxGroupEditor : public SharedRom {
selected_spriteset_ = spriteset; selected_spriteset_ = spriteset;
} }
void InitBlockset(gfx::Bitmap tile16_blockset); void InitBlockset(gfx::Bitmap* tile16_blockset) {
tile16_blockset_bmp_ = tile16_blockset;
}
private: private:
int preview_palette_id_ = 0; int preview_palette_id_ = 0;
@@ -50,8 +50,7 @@ class GfxGroupEditor : public SharedRom {
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;
uint8_t selected_paletteset_ = 0;
PaletteEditor palette_editor_;
gui::Canvas blockset_canvas_; gui::Canvas blockset_canvas_;
gui::Canvas roomset_canvas_; gui::Canvas roomset_canvas_;
@@ -59,7 +58,7 @@ class GfxGroupEditor : public SharedRom {
gfx::SnesPalette palette_; gfx::SnesPalette palette_;
gfx::PaletteGroup palette_group_; gfx::PaletteGroup palette_group_;
gfx::Bitmap tile16_blockset_bmp_; gfx::Bitmap* tile16_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_; std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_; std::vector<gfx::Bitmap> tile16_individual_;

View File

@@ -1,22 +1,23 @@
#include "app/editor/graphics_editor.h" #include "graphics_editor.h"
#include <ImGuiFileDialog/ImGuiFileDialog.h> #include "ImGuiFileDialog/ImGuiFileDialog.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <imgui/misc/cpp/imgui_stdlib.h> #include "imgui/misc/cpp/imgui_stdlib.h"
#include <imgui_memory_editor.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/editor/modules/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/input.h" #include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
@@ -24,12 +25,18 @@ namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
using gfx::kPaletteGroupAddressesKeys;
using ImGui::Button; using ImGui::Button;
using ImGui::InputInt; using ImGui::InputInt;
using ImGui::InputText; 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 |
@@ -40,9 +47,20 @@ constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton; ImGuiTabBarFlags_TabListPopupButton;
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
absl::Status GraphicsEditor::Update() { absl::Status GraphicsEditor::Update() {
TAB_BAR("##TabBar") TAB_BAR("##TabBar")
status_ = UpdateGfxEdit(); status_ = UpdateGfxEdit();
TAB_ITEM("Sheet Browser")
if (asset_browser_.Initialized == false) {
asset_browser_.Initialize(rom()->mutable_bitmap_manager());
}
asset_browser_.Draw(rom()->mutable_bitmap_manager());
END_TAB_ITEM()
status_ = UpdateScadView(); status_ = UpdateScadView();
status_ = UpdateLinkGfxView(); status_ = UpdateLinkGfxView();
END_TAB_BAR() END_TAB_BAR()
@@ -109,7 +127,7 @@ 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()->bitmap_manager().shared_bitmap(current_sheet_).GetPngData();
CopyImageToClipboard(png_data); CopyImageToClipboard(png_data);
} }
HOVER_HINT("Copy to Clipboard"); HOVER_HINT("Copy to Clipboard");
@@ -124,10 +142,8 @@ void GraphicsEditor::DrawGfxEditToolset() {
->mutable_bitmap_manager() ->mutable_bitmap_manager()
->mutable_bitmap(current_sheet_) ->mutable_bitmap(current_sheet_)
->Create(width, height, 8, png_data); ->Create(width, height, 8, png_data);
rom()->UpdateBitmap(rom() rom()->UpdateBitmap(
->mutable_bitmap_manager() rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_));
->mutable_bitmap(current_sheet_)
.get());
} }
} }
HOVER_HINT("Paste from Clipboard"); HOVER_HINT("Paste from Clipboard");
@@ -148,7 +164,7 @@ void GraphicsEditor::DrawGfxEditToolset() {
TableNextColumn(); TableNextColumn();
auto bitmap = rom()->bitmap_manager()[current_sheet_]; auto bitmap = rom()->bitmap_manager()[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();
auto color = auto color =
@@ -161,7 +177,7 @@ void GraphicsEditor::DrawGfxEditToolset() {
} }
TableNextColumn(); TableNextColumn();
gui::InputHexByte("Tile Size", &tile_size_, 0x01); gui::InputHexByte("Tile Size", &tile_size_);
ImGui::EndTable(); ImGui::EndTable();
} }
@@ -172,30 +188,38 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
"##GfxEditChild", ImVec2(0, 0), true, "##GfxEditChild", ImVec2(0, 0), true,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
// TODO: Update the interaction for multi select on sheets
static ImGuiSelectionBasicStorage selection;
ImGuiMultiSelectFlags flags =
ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
flags, selection.Size, rom()->bitmap_manager().size());
selection.ApplyRequests(ms_io);
ImGuiListClipper clipper;
clipper.Begin(rom()->bitmap_manager().size());
if (ms_io->RangeSrcItem != -1)
clipper.IncludeItemByIndex(
(int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
for (auto& [key, value] : rom()->bitmap_manager()) { for (auto& [key, value] : rom()->bitmap_manager()) {
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_; gui::Canvas graphics_bin_canvas_;
// auto select_tile_event = [&]() {
// };
// graphics_bin_canvas_.UpdateEvent(
// select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_,
// /*grid_size=*/16.0f);
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.get()->is_active()) { if (value.is_active()) {
auto texture = value.get()->texture(); auto texture = value.texture();
graphics_bin_canvas_.draw_list()->AddImage( graphics_bin_canvas_.draw_list()->AddImage(
(void*)texture, (void*)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 +
value.get()->width() * sheet_scale_, value.width() * sheet_scale_,
graphics_bin_canvas_.zero_point().y + graphics_bin_canvas_.zero_point().y +
value.get()->height() * sheet_scale_)); value.height() * sheet_scale_));
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
current_sheet_ = key; current_sheet_ = key;
@@ -224,6 +248,8 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
ImGui::EndChild(); ImGui::EndChild();
} }
ImGui::PopStyleVar(); ImGui::PopStyleVar();
ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io);
ImGui::EndChild(); ImGui::EndChild();
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -232,8 +258,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
static int next_tab_id = 0; static int next_tab_id = 0;
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) { if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
if (ImGui::TabItemButton( if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
"+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) { ImGuiTabItemFlags_NoTooltip)) {
open_sheets_.insert(next_tab_id++); open_sheets_.insert(next_tab_id++);
} }
@@ -269,7 +295,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
}; };
current_sheet_canvas_.UpdateColorPainter( current_sheet_canvas_.UpdateColorPainter(
*rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event, rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
tile_size_, current_scale_); tile_size_, current_scale_);
ImGui::EndChild(); ImGui::EndChild();
@@ -302,7 +328,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()->bitmap_manager()[id], current_color_,
[&]() { [&]() {
}, },
@@ -322,12 +348,10 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
} }
absl::Status GraphicsEditor::UpdatePaletteColumn() { absl::Status GraphicsEditor::UpdatePaletteColumn() {
auto palette_group = *rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
auto palette = palette_group.palette(edit_palette_index_);
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
auto palette_group = *rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
auto palette = palette_group.palette(edit_palette_index_);
gui::TextWithSeparators("ROM Palette"); gui::TextWithSeparators("ROM Palette");
ImGui::SetNextItemWidth(100.f); ImGui::SetNextItemWidth(100.f);
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_, ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
@@ -335,21 +359,20 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
IM_ARRAYSIZE(kPaletteGroupAddressesKeys)); IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
ImGui::SetNextItemWidth(100.f); ImGui::SetNextItemWidth(100.f);
gui::InputHex("Palette Group Index", &edit_palette_index_); gui::InputHex("Palette Group Index", &edit_palette_index_);
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
palette);
if (refresh_graphics_ && !open_sheets_.empty()) {
RETURN_IF_ERROR(
rom()->bitmap_manager()[current_sheet_].ApplyPaletteWithTransparent(
palette, edit_palette_sub_index_));
rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_),
true);
refresh_graphics_ = false;
}
} }
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
palette);
if (refresh_graphics_ && !open_sheets_.empty()) {
RETURN_IF_ERROR(
rom()->bitmap_manager()[current_sheet_]->ApplyPaletteWithTransparent(
palette, edit_palette_sub_index_));
rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get(),
true);
refresh_graphics_ = false;
}
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -366,10 +389,12 @@ absl::Status GraphicsEditor::UpdateLinkGfxView() {
NEXT_COLUMN(); NEXT_COLUMN();
link_canvas_.DrawBackground(); link_canvas_.DrawBackground();
link_canvas_.DrawGrid(16.0f); link_canvas_.DrawGrid(16.0f);
int i = 0; int i = 0;
for (auto [key, link_sheet] : rom()->link_graphics()) { for (auto [key, link_sheet] : *rom()->mutable_link_graphics()) {
int x_offset = 0; int x_offset = 0;
int y_offset = core::kTilesheetHeight * i * 4; int y_offset = core::kTilesheetHeight * i * 4;
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,7 +460,7 @@ absl::Status GraphicsEditor::UpdateScadView() {
if (super_donkey_) { if (super_donkey_) {
if (refresh_graphics_) { if (refresh_graphics_) {
for (int i = 0; i < graphics_bin_.size(); i++) { for (int i = 0; i < graphics_bin_.size(); i++) {
graphics_bin_[i].ApplyPalette( status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]); col_file_palette_group_[current_palette_index_]);
rom()->UpdateBitmap(&graphics_bin_[i]); rom()->UpdateBitmap(&graphics_bin_[i]);
} }
@@ -498,19 +523,21 @@ absl::Status GraphicsEditor::DrawCgxImport() {
is_open_ = true; is_open_ = true;
cgx_loaded_ = true; cgx_loaded_ = true;
}); });
gui::ButtonPipe("Copy CGX Path",
[this]() { ImGui::SetClipboardText(cgx_file_path_); });
gui::ButtonPipe("Load CGX Data", [this]() { if (ImGui::Button("Copy CGX Path")) {
ImGui::SetClipboardText(cgx_file_path_);
}
if (ImGui::Button("Load CGX Data")) {
status_ = gfx::scad_format::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_, status_ = gfx::scad_format::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
decoded_cgx_, extra_cgx_data_); decoded_cgx_, extra_cgx_data_);
cgx_bitmap_.InitializeFromData(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_); rom()->RenderBitmap(&cgx_bitmap_);
} }
}); }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -532,7 +559,7 @@ absl::Status GraphicsEditor::DrawScrImport() {
InputInt("SCR Mod", &scr_mod_value_); InputInt("SCR Mod", &scr_mod_value_);
gui::ButtonPipe("Load Scr Data", [this]() { if (ImGui::Button("Load Scr Data")) {
status_ = status_ =
gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_); gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
@@ -540,12 +567,12 @@ absl::Status GraphicsEditor::DrawScrImport() {
status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_, status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_,
decoded_scr_data_, decoded_cgx_); decoded_scr_data_, decoded_cgx_);
scr_bitmap_.InitializeFromData(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_); rom()->RenderBitmap(&scr_bitmap_);
} }
}); }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -582,8 +609,9 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
is_open_ = true; is_open_ = true;
}); });
gui::ButtonPipe("Copy COL Path", if (ImGui::Button("Copy Col Path")) {
[this]() { ImGui::SetClipboardText(col_file_path_); }); ImGui::SetClipboardText(col_file_path_);
}
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
gui::TextWithSeparators("ROM Palette"); gui::TextWithSeparators("ROM Palette");
@@ -655,8 +683,9 @@ absl::Status GraphicsEditor::DrawFileImport() {
is_open_ = true; is_open_ = true;
}); });
gui::ButtonPipe("Copy File Path", if (Button("Copy File Path")) {
[this]() { ImGui::SetClipboardText(file_path_); }); ImGui::SetClipboardText(file_path_);
}
gui::InputHex("BIN Offset", &current_offset_); gui::InputHex("BIN Offset", &current_offset_);
gui::InputHex("BIN Size", &bin_size_); gui::InputHex("BIN Size", &bin_size_);
@@ -675,7 +704,7 @@ absl::Status GraphicsEditor::DrawFileImport() {
absl::Status GraphicsEditor::DrawClipboardImport() { absl::Status GraphicsEditor::DrawClipboardImport() {
gui::TextWithSeparators("Clipboard Import"); gui::TextWithSeparators("Clipboard Import");
gui::ButtonPipe("Paste from Clipboard", [this]() { 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 = Bytes(text, text + strlen(text));
@@ -684,12 +713,12 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
is_open_ = true; is_open_ = true;
open_memory_editor_ = true; open_memory_editor_ = true;
} }
}); }
gui::InputHex("Offset", &clipboard_offset_); gui::InputHex("Offset", &clipboard_offset_);
gui::InputHex("Size", &clipboard_size_); gui::InputHex("Size", &clipboard_size_);
gui::InputHex("Num Sheets", &num_sheets_to_load_); gui::InputHex("Num Sheets", &num_sheets_to_load_);
gui::ButtonPipe("Decompress Clipboard Data", [this]() { if (Button("Decompress Clipboard Data")) {
if (temp_rom_.is_loaded()) { if (temp_rom_.is_loaded()) {
status_ = DecompressImportData(0x40000); status_ = DecompressImportData(0x40000);
} else { } else {
@@ -697,7 +726,7 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
"Please paste data into the clipboard before " "Please paste data into the clipboard before "
"decompressing."); "decompressing.");
} }
}); }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -740,9 +769,9 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
auto palette_group = rom()->palette_group().overworld_animated; auto palette_group = rom()->palette_group().overworld_animated;
z3_rom_palette_ = palette_group[current_palette_]; z3_rom_palette_ = palette_group[current_palette_];
if (col_file_) { if (col_file_) {
bin_bitmap_.ApplyPalette(col_file_palette_); status_ = bin_bitmap_.ApplyPalette(col_file_palette_);
} else { } else {
bin_bitmap_.ApplyPalette(z3_rom_palette_); status_ = bin_bitmap_.ApplyPalette(z3_rom_palette_);
} }
} }
@@ -765,7 +794,7 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight, gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet); core::kTilesheetDepth, converted_sheet);
if (col_file_) { if (col_file_) {
graphics_bin_[i].ApplyPalette( status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]); col_file_palette_group_[current_palette_index_]);
} else { } else {
// ROM palette // ROM palette
@@ -773,7 +802,7 @@ 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_);
graphics_bin_[i].ApplyPalette(z3_rom_palette_); status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
} }
rom()->RenderBitmap(&graphics_bin_[i]); rom()->RenderBitmap(&graphics_bin_[i]);
@@ -791,14 +820,14 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight, gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet); core::kTilesheetDepth, converted_sheet);
if (col_file_) { if (col_file_) {
graphics_bin_[i].ApplyPalette( status_ = graphics_bin_[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_);
graphics_bin_[i].ApplyPalette(z3_rom_palette_); status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
} }
rom()->RenderBitmap(&graphics_bin_[i]); rom()->RenderBitmap(&graphics_bin_[i]);

View File

@@ -1,19 +1,20 @@
#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 "ImGuiFileDialog/ImGuiFileDialog.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <imgui/misc/cpp/imgui_stdlib.h> #include "imgui/misc/cpp/imgui_stdlib.h"
#include <imgui_memory_editor.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/editor/modules/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/input.h"
#include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
@@ -43,22 +44,6 @@ const std::string kSuperDonkeySprites[] = {
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D", "BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
"C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"}; "C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"};
constexpr const char* kPaletteGroupAddressesKeys[] = {
"ow_main", "ow_aux", "ow_animated", "hud",
"global_sprites", "armors", "swords", "shields",
"sprites_aux1", "sprites_aux2", "sprites_aux3", "dungeon_main",
"grass", "3d_object", "ow_mini_map",
};
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
/** /**
* @class GraphicsEditor * @class GraphicsEditor
* @brief Allows the user to edit graphics sheets from the game or view * @brief Allows the user to edit graphics sheets from the game or view
@@ -73,9 +58,18 @@ constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
* drawing toolsets, palette controls, clipboard imports, experimental features, * drawing toolsets, palette controls, clipboard imports, experimental features,
* and memory editor. * and memory editor.
*/ */
class GraphicsEditor : public SharedRom { class GraphicsEditor : public SharedRom, public Editor {
public: public:
absl::Status Update(); GraphicsEditor() { type_ = EditorType::kGraphics; }
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
private: private:
enum class GfxEditMode { enum class GfxEditMode {
@@ -163,6 +157,8 @@ class GraphicsEditor : public SharedRom {
char tilemap_file_path_[256] = ""; char tilemap_file_path_[256] = "";
char tilemap_file_name_[256] = ""; char tilemap_file_name_[256] = "";
gui::GfxSheetAssetBrowser asset_browser_;
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect; GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
Rom temp_rom_; Rom temp_rom_;
@@ -193,9 +189,10 @@ class GraphicsEditor : public SharedRom {
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 current_sheet_canvas_{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",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4), ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k16x16}; gui::CanvasGridSize::k16x16};
absl::Status status_; absl::Status status_;

View File

@@ -0,0 +1,469 @@
#include "palette_editor.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 app {
namespace editor {
using ImGui::AcceptDragDropPayload;
using ImGui::BeginChild;
using ImGui::BeginDragDropTarget;
using ImGui::BeginGroup;
using ImGui::BeginPopup;
using ImGui::BeginPopupContextItem;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::ColorButton;
using ImGui::ColorPicker4;
using ImGui::EndChild;
using ImGui::EndDragDropTarget;
using ImGui::EndGroup;
using ImGui::EndPopup;
using ImGui::EndTable;
using ImGui::GetContentRegionAvail;
using ImGui::GetStyle;
using ImGui::OpenPopup;
using ImGui::PopID;
using ImGui::PushID;
using ImGui::SameLine;
using ImGui::Selectable;
using ImGui::Separator;
using ImGui::SetClipboardText;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetColumnIndex;
using ImGui::TableSetupColumn;
using ImGui::Text;
using ImGui::TreeNode;
using ImGui::TreePop;
using namespace gfx;
constexpr ImGuiTableFlags kPaletteTableFlags =
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
constexpr ImGuiColorEditFlags kPalButtonFlags2 = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip;
constexpr ImGuiColorEditFlags kColorPopupFlags =
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
namespace {
int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
#ifdef IMGUI_USE_STB_SPRINTF
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
#else
int w = vsnprintf(buf, buf_size, fmt, args);
#endif
va_end(args);
if (buf == nullptr) return w;
if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
buf[w] = 0;
return w;
}
static inline float color_saturate(float f) {
return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
}
#define F32_TO_INT8_SAT(_VAL) \
((int)(color_saturate(_VAL) * 255.0f + \
0.5f)) // Saturated, always output 0..255
} // namespace
absl::Status PaletteEditor::Update() {
if (rom()->is_loaded()) {
// Initialize the labels
for (int i = 0; i < kNumPalettes; i++) {
rom()->resource_label()->CreateOrGetLabel(
"Palette Group Name", std::to_string(i),
std::string(kPaletteGroupNames[i]));
}
} else {
return absl::NotFoundError("ROM not open, no palettes to display");
}
if (BeginTable("paletteEditorTable", 2, kPaletteTableFlags, ImVec2(0, 0))) {
TableSetupColumn("Palette Groups", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
TableSetupColumn("Palette Sets and Metadata",
ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
TableHeadersRow();
TableNextRow();
TableNextColumn();
DrawModifiedColors();
DrawCustomPalette();
Separator();
gui::SnesColorEdit4("Current Color Picker", &current_color_,
ImGuiColorEditFlags_NoAlpha);
Separator();
DisplayCategoryTable();
TableNextColumn();
gfx_group_editor_.DrawPaletteViewer();
Separator();
static bool in_use = false;
ImGui::Checkbox("Palette in use? ", &in_use);
Separator();
static std::string palette_notes = "Notes about the palette";
ImGui::InputTextMultiline("Notes", palette_notes.data(), 1024,
ImVec2(-1, ImGui::GetTextLineHeight() * 4),
ImGuiInputTextFlags_AllowTabInput);
EndTable();
}
CLEAR_AND_RETURN_STATUS(status_)
return absl::OkStatus();
}
void PaletteEditor::DrawCustomPalette() {
if (BeginChild("ColorPalette", ImVec2(0, 40), true,
ImGuiWindowFlags_HorizontalScrollbar)) {
for (int i = 0; i < custom_palette_.size(); i++) {
PushID(i);
SameLine(0.0f, GetStyle().ItemSpacing.y);
gui::SnesColorEdit4("##customPalette", &custom_palette_[i],
ImGuiColorEditFlags_NoInputs);
// Accept a drag drop target which adds a color to the custom_palette_
if (BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
ImVec4 color = ImVec4(0, 0, 0, 1.0f);
memcpy((float*)&color, payload->Data, sizeof(float));
custom_palette_.push_back(SnesColor(color));
}
EndDragDropTarget();
}
PopID();
}
SameLine();
if (ImGui::Button("Add Color")) {
custom_palette_.push_back(SnesColor(0x7FFF));
}
SameLine();
if (ImGui::Button("Export to Clipboard")) {
std::string clipboard;
for (const auto& color : custom_palette_) {
clipboard += absl::StrFormat("$%04X,", color.snes());
}
SetClipboardText(clipboard.c_str());
}
}
EndChild();
}
void PaletteEditor::DisplayCategoryTable() {
if (BeginTable("Category Table", 8,
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_SizingStretchSame |
ImGuiTableFlags_Hideable,
ImVec2(0, 0))) {
TableSetupColumn("Weapons and Gear");
TableSetupColumn("Overworld and Area Colors");
TableSetupColumn("Global Sprites");
TableSetupColumn("Sprites Aux1");
TableSetupColumn("Sprites Aux2");
TableSetupColumn("Sprites Aux3");
TableSetupColumn("Maps and Items");
TableSetupColumn("Dungeons");
TableHeadersRow();
TableNextRow();
TableSetColumnIndex(0);
if (TreeNode("Sword")) {
status_ = DrawPaletteGroup(PaletteCategory::kSword);
TreePop();
}
if (TreeNode("Shield")) {
status_ = DrawPaletteGroup(PaletteCategory::kShield);
TreePop();
}
if (TreeNode("Clothes")) {
status_ = DrawPaletteGroup(PaletteCategory::kClothes, true);
TreePop();
}
TableSetColumnIndex(1);
gui::BeginChildWithScrollbar("##WorldPaletteScrollRegion");
if (TreeNode("World Colors")) {
status_ = DrawPaletteGroup(PaletteCategory::kWorldColors);
TreePop();
}
if (TreeNode("Area Colors")) {
status_ = DrawPaletteGroup(PaletteCategory::kAreaColors);
TreePop();
}
EndChild();
TableSetColumnIndex(2);
status_ = DrawPaletteGroup(PaletteCategory::kGlobalSprites, true);
TableSetColumnIndex(3);
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux1);
TableSetColumnIndex(4);
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux2);
TableSetColumnIndex(5);
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux3);
TableSetColumnIndex(6);
gui::BeginChildWithScrollbar("##MapPaletteScrollRegion");
if (TreeNode("World Map")) {
status_ = DrawPaletteGroup(PaletteCategory::kWorldMap, true);
TreePop();
}
if (TreeNode("Dungeon Map")) {
status_ = DrawPaletteGroup(PaletteCategory::kDungeonMap);
TreePop();
}
if (TreeNode("Triforce")) {
status_ = DrawPaletteGroup(PaletteCategory::kTriforce);
TreePop();
}
if (TreeNode("Crystal")) {
status_ = DrawPaletteGroup(PaletteCategory::kCrystal);
TreePop();
}
EndChild();
TableSetColumnIndex(7);
gui::BeginChildWithScrollbar("##DungeonPaletteScrollRegion");
status_ = DrawPaletteGroup(PaletteCategory::kDungeons, true);
EndChild();
EndTable();
}
}
absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
if (!rom()->is_loaded()) {
return absl::NotFoundError("ROM not open, no palettes to display");
}
auto palette_group_name = kPaletteGroupNames[category];
gfx::PaletteGroup* palette_group =
rom()->mutable_palette_group()->get_group(palette_group_name.data());
const auto size = palette_group->size();
static bool edit_color = false;
for (int j = 0; j < size; j++) {
gfx::SnesPalette* palette = palette_group->mutable_palette(j);
auto pal_size = palette->size();
for (int n = 0; n < pal_size; n++) {
PushID(n);
if (!right_side) {
if ((n % 7) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
} else {
if ((n % 15) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
}
auto popup_id =
absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
// Small icon of the color in the palette
if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
kPalNoAlpha)) {
ASSIGN_OR_RETURN(current_color_, palette->GetColor(n));
}
if (BeginPopupContextItem(popup_id.c_str())) {
RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
}
PopID();
}
SameLine();
rom()->resource_label()->SelectableLabelWithNameEdit(
false, palette_group_name.data(), /*key=*/std::to_string(j),
"Unnamed Palette");
if (right_side) Separator();
}
return absl::OkStatus();
}
void PaletteEditor::DrawModifiedColors() {
if (BeginChild("ModifiedColors", ImVec2(0, 100), true,
ImGuiWindowFlags_HorizontalScrollbar)) {
for (int i = 0; i < history_.size(); i++) {
PushID(i);
gui::SnesColorEdit4("Original ", &history_.GetOriginalColor(i),
ImGuiColorEditFlags_NoInputs);
SameLine(0.0f, GetStyle().ItemSpacing.y);
gui::SnesColorEdit4("Modified ", &history_.GetModifiedColor(i),
ImGuiColorEditFlags_NoInputs);
PopID();
}
}
EndChild();
}
absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
int j, int n) {
auto col = gfx::ToFloatArray(palette[n]);
auto original_color = palette[n];
if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
/*palette_index=*/j, /*color_index=*/n,
original_color, palette[n]);
palette[n].set_modified(true);
}
if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
if (BeginPopup("Copy")) {
int cr = F32_TO_INT8_SAT(col[0]);
int cg = F32_TO_INT8_SAT(col[1]);
int cb = F32_TO_INT8_SAT(col[2]);
char buf[64];
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
col[1], col[2]);
if (Selectable(buf)) SetClipboardText(buf);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
if (Selectable(buf)) SetClipboardText(buf);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
if (Selectable(buf)) SetClipboardText(buf);
// SNES Format
CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
ConvertRGBtoSNES(ImVec4(col[0], col[1], col[2], 1.0f)));
if (Selectable(buf)) SetClipboardText(buf);
EndPopup();
}
EndPopup();
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,
int index) {
if (index >= palette.size()) {
return absl::InvalidArgumentError("Index out of bounds");
}
// Get the current color
ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
auto currentColor = color.rgb();
if (ColorPicker4("Color Picker", (float*)&palette[index])) {
// The color was modified, update it in the palette
palette(index, currentColor);
}
return absl::OkStatus();
}
absl::Status PaletteEditor::ResetColorToOriginal(
gfx::SnesPalette& palette, int index,
const gfx::SnesPalette& originalPalette) {
if (index >= palette.size() || index >= originalPalette.size()) {
return absl::InvalidArgumentError("Index out of bounds");
}
ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
auto originalColor = color.rgb();
palette(index, originalColor);
return absl::OkStatus();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,9 +1,11 @@
#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 "imgui/imgui.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/utils/editor.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/icons.h" #include "app/gui/icons.h"
@@ -13,18 +15,6 @@ namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
constexpr int kNumPalettes = 11;
static constexpr absl::string_view kPaletteCategoryNames[] = {
"Sword", "Shield", "Clothes", "World Colors",
"Area Colors", "Enemies", "Dungeons", "World Map",
"Dungeon Map", "Triforce", "Crystal"};
static constexpr absl::string_view kPaletteGroupNames[] = {
"swords", "shields", "armors", "ow_main",
"ow_aux", "global_sprites", "dungeon_main", "ow_mini_map",
"ow_mini_map", "3d_object", "3d_object"};
namespace palette_internal { namespace palette_internal {
struct PaletteChange { struct PaletteChange {
std::string group_name; std::string group_name;
@@ -56,9 +46,9 @@ class PaletteEditorHistory {
} }
// Restore the original color // Restore the original color
gfx::SnesColor GetOriginalColor(const std::string& groupName, gfx::SnesColor RestoreOriginalColor(const std::string& groupName,
size_t paletteIndex, size_t paletteIndex,
size_t colorIndex) const { size_t colorIndex) const {
for (const auto& change : recentChanges) { for (const auto& change : recentChanges) {
if (change.group_name == groupName && if (change.group_name == groupName &&
change.palette_index == paletteIndex && change.palette_index == paletteIndex &&
@@ -71,6 +61,15 @@ class PaletteEditorHistory {
return gfx::SnesColor(); return gfx::SnesColor();
} }
auto size() const { return recentChanges.size(); }
gfx::SnesColor& GetModifiedColor(size_t index) {
return recentChanges[index].new_color;
}
gfx::SnesColor& GetOriginalColor(size_t index) {
return recentChanges[index].original_color;
}
private: private:
std::deque<PaletteChange> recentChanges; std::deque<PaletteChange> recentChanges;
static const size_t maxHistorySize = 50; // or any other number you deem fit static const size_t maxHistorySize = 50; // or any other number you deem fit
@@ -81,21 +80,36 @@ class PaletteEditorHistory {
* @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.
*/ */
class PaletteEditor : public SharedRom { class PaletteEditor : public SharedRom, public Editor {
public: public:
absl::Status Update(); PaletteEditor() {
absl::Status DrawPaletteGroups(); type_ = EditorType::kPalette;
custom_palette_.push_back(gfx::SnesColor(0x7FFF));
}
absl::Status Update() override;
absl::Status Cut() override { return absl::OkStatus(); }
absl::Status Copy() override { return absl::OkStatus(); }
absl::Status Paste() override { return absl::OkStatus(); }
absl::Status Undo() override { return absl::OkStatus(); }
absl::Status Redo() override { return absl::OkStatus(); }
absl::Status Find() override { return absl::OkStatus(); }
void DisplayCategoryTable();
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); void DisplayPalette(gfx::SnesPalette& palette, bool loaded);
void DrawPortablePalette(gfx::SnesPalette& palette); absl::Status DrawPaletteGroup(int category, bool right_side = false);
absl::Status DrawPaletteGroup(int category);
void DrawCustomPalette();
void DrawModifiedColors();
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) { absl::Status InitializeSavedPalette(const gfx::SnesPalette& palette) {
for (int n = 0; n < palette.size(); n++) { for (int n = 0; n < palette.size(); n++) {
ASSIGN_OR_RETURN(auto color, palette.GetColor(n)); ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
@@ -108,18 +122,15 @@ class PaletteEditor : public SharedRom {
} }
absl::Status status_; absl::Status status_;
palette_internal::PaletteEditorHistory history_;
ImVec4 saved_palette_[256] = {};
gfx::SnesColor current_color_; gfx::SnesColor current_color_;
ImGuiColorEditFlags color_popup_flags = GfxGroupEditor gfx_group_editor_;
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha; std::vector<gfx::SnesColor> custom_palette_;
ImGuiColorEditFlags palette_button_flags_2 = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker | ImVec4 saved_palette_[256] = {};
ImGuiColorEditFlags_NoTooltip;
palette_internal::PaletteEditorHistory history_;
}; };
} // namespace editor } // namespace editor

View File

@@ -1,6 +1,6 @@
#include "app/editor/screen_editor.h" #include "screen_editor.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
@@ -25,9 +25,7 @@ namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); } absl::Status ScreenEditor::Update() {
void ScreenEditor::Update() {
TAB_BAR("##TabBar") TAB_BAR("##TabBar")
TAB_ITEM("Dungeon Maps") TAB_ITEM("Dungeon Maps")
if (rom()->is_loaded()) { if (rom()->is_loaded()) {
@@ -39,6 +37,8 @@ void ScreenEditor::Update() {
DrawTitleScreenEditor(); DrawTitleScreenEditor();
DrawNamingScreenEditor(); DrawNamingScreenEditor();
END_TAB_BAR() END_TAB_BAR()
return absl::OkStatus();
} }
void ScreenEditor::DrawInventoryMenuEditor() { void ScreenEditor::DrawInventoryMenuEditor() {
@@ -46,7 +46,7 @@ void ScreenEditor::DrawInventoryMenuEditor() {
static bool create = false; static bool create = false;
if (!create && rom()->is_loaded()) { if (!create && rom()->is_loaded()) {
inventory_.Create(); status_ = inventory_.Create();
palette_ = inventory_.Palette(); palette_ = inventory_.Palette();
create = true; create = true;
} }
@@ -74,7 +74,7 @@ void ScreenEditor::DrawInventoryMenuEditor() {
tilesheet_canvas_.DrawOverlay(); tilesheet_canvas_.DrawOverlay();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
gui::DisplayPalette(palette_, create); status_ = gui::DisplayPalette(palette_, create);
ImGui::EndTable(); ImGui::EndTable();
} }
@@ -233,8 +233,8 @@ absl::Status ScreenEditor::LoadDungeonMapTile16() {
tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4); tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4);
} }
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()); rom()->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) {
@@ -293,13 +293,7 @@ void ScreenEditor::DrawDungeonMapsTabs() {
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));
// GFX.drawText(
// e.Graphics, 16 + ((i % 5) * 32), 20 + ((i / 5) * 32),
} }
// if (dungmapSelectedTile == i)
// Constants.AzurePen2,
// 10 + ((i % 5) * 32), 12 + ((i / 5) * 32), 32, 32));
} }
screen_canvas_.DrawGrid(64.f, 5); screen_canvas_.DrawGrid(64.f, 5);
@@ -369,6 +363,9 @@ void ScreenEditor::DrawDungeonMapsEditor() {
rom()->resource_label()->SelectableLabelWithNameEdit( rom()->resource_label()->SelectableLabelWithNameEdit(
selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i), selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
dungeon_names[i]); dungeon_names[i]);
if (ImGui::IsItemClicked()) {
selected_dungeon = i;
}
} }
// Map column // Map column

View File

@@ -1,12 +1,13 @@
#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 "imgui/imgui.h"
#include <array> #include <array>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/constants.h" #include "app/core/constants.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"
@@ -36,10 +37,21 @@ namespace editor {
* *
* The class inherits from the SharedRom class. * The class inherits from the SharedRom class.
*/ */
class ScreenEditor : public SharedRom { class ScreenEditor : public SharedRom, public Editor {
public: public:
ScreenEditor(); ScreenEditor() {
void Update(); screen_canvas_.SetCanvasSize(ImVec2(512, 512));
type_ = EditorType::kScreen;
}
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
absl::Status SaveDungeonMaps(); absl::Status SaveDungeonMaps();
@@ -83,6 +95,8 @@ class ScreenEditor : public SharedRom {
gfx::BitmapTable sheets_; gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_; gfx::Tilesheet tile16_sheet_;
absl::Status status_;
}; };
} // namespace editor } // namespace editor

View File

@@ -1,12 +1,13 @@
#include "tile16_editor.h" #include "tile16_editor.h"
#include <imgui/imgui.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/editor/modules/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/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"
@@ -15,9 +16,7 @@
#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/gui/pipeline.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/gui/widgets.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
@@ -26,50 +25,73 @@ namespace app {
namespace editor { namespace editor {
using ImGui::BeginChild; using ImGui::BeginChild;
using ImGui::BeginMenu;
using ImGui::BeginMenuBar;
using ImGui::BeginTabBar; using ImGui::BeginTabBar;
using ImGui::BeginTabItem; using ImGui::BeginTabItem;
using ImGui::BeginTable; using ImGui::BeginTable;
using ImGui::Button;
using ImGui::Checkbox;
using ImGui::Combo; using ImGui::Combo;
using ImGui::EndChild; using ImGui::EndChild;
using ImGui::EndMenu;
using ImGui::EndMenuBar;
using ImGui::EndTabBar; using ImGui::EndTabBar;
using ImGui::EndTabItem; using ImGui::EndTabItem;
using ImGui::EndTable;
using ImGui::GetContentRegionAvail;
using ImGui::Separator;
using ImGui::TableHeadersRow; using ImGui::TableHeadersRow;
using ImGui::TableNextColumn; using ImGui::TableNextColumn;
using ImGui::TableNextRow; using ImGui::TableNextRow;
using ImGui::TableSetupColumn; using ImGui::TableSetupColumn;
using ImGui::Text;
absl::Status Tile16Editor::InitBlockset(
gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual,
uint8_t all_tiles_types[0x200]) {
all_tiles_types_ = all_tiles_types;
tile16_blockset_bmp_ = tile16_blockset_bmp;
tile16_individual_ = tile16_individual;
current_gfx_bmp_ = current_gfx_bmp;
tile8_gfx_data_ = current_gfx_bmp_.vector();
RETURN_IF_ERROR(LoadTile8());
ImVector<std::string> tile16_names;
for (int i = 0; i < 0x200; ++i) {
std::string str = core::UppercaseHexByte(all_tiles_types_[i]);
tile16_names.push_back(str);
}
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
*tile8_source_canvas_.custom_labels_enabled() = true;
return absl::OkStatus();
}
absl::Status Tile16Editor::Update() { absl::Status Tile16Editor::Update() {
if (rom()->is_loaded() && !map_blockset_loaded_) { if (!map_blockset_loaded_) {
RETURN_IF_ERROR(LoadTile8()); return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
ImVector<std::string> tile16_names;
for (int i = 0; i < 0x200; ++i) {
std::string str = core::UppercaseHexByte(all_tiles_types_[i]);
tile16_names.push_back(str);
}
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
*tile8_source_canvas_.custom_labels_enabled() = true;
} }
RETURN_IF_ERROR(DrawMenu()); RETURN_IF_ERROR(DrawMenu());
if (BeginTabBar("Tile16 Editor Tabs")) { if (BeginTabBar("Tile16 Editor Tabs")) {
RETURN_IF_ERROR(DrawTile16Editor()); RETURN_IF_ERROR(DrawTile16Editor());
RETURN_IF_ERROR(UpdateTile16Transfer()); RETURN_IF_ERROR(UpdateTile16Transfer());
ImGui::EndTabBar(); EndTabBar();
} }
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Tile16Editor::DrawMenu() { absl::Status Tile16Editor::DrawMenu() {
if (ImGui::BeginMenuBar()) { if (BeginMenuBar()) {
if (ImGui::BeginMenu("View")) { if (BeginMenu("View")) {
ImGui::Checkbox("Show Collision Types", Checkbox("Show Collision Types",
tile8_source_canvas_.custom_labels_enabled()); tile8_source_canvas_.custom_labels_enabled());
ImGui::EndMenu(); EndMenu();
} }
ImGui::EndMenuBar(); EndMenuBar();
} }
return absl::OkStatus(); return absl::OkStatus();
@@ -80,9 +102,9 @@ absl::Status Tile16Editor::DrawTile16Editor() {
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE, if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) { ImVec2(0, 0))) {
TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed, TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
ImGui::GetContentRegionAvail().x); GetContentRegionAvail().x);
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch, TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x); GetContentRegionAvail().x);
TableHeadersRow(); TableHeadersRow();
TableNextRow(); TableNextRow();
TableNextColumn(); TableNextColumn();
@@ -92,10 +114,10 @@ absl::Status Tile16Editor::DrawTile16Editor() {
RETURN_IF_ERROR(UpdateTile16Edit()); RETURN_IF_ERROR(UpdateTile16Edit());
RETURN_IF_ERROR(DrawTileEditControls()); RETURN_IF_ERROR(DrawTileEditControls());
ImGui::EndTable(); EndTable();
} }
ImGui::EndTabItem(); EndTabItem();
} }
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -108,26 +130,23 @@ absl::Status Tile16Editor::UpdateBlockset() {
{ {
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();
ImGui::EndChild(); EndChild();
} }
if (!blockset_canvas_.points().empty()) { if (!blockset_canvas_.points().empty()) {
uint16_t x = blockset_canvas_.points().front().x / 32;
uint16_t y = blockset_canvas_.points().front().y / 32;
// notify_tile16.mutable_get() = x + (y * 8);
notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos(); notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos();
notify_tile16.apply_changes(); notify_tile16.apply_changes();
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_); rom()->RenderBitmap(current_tile16_bmp_);
} }
} }
@@ -158,7 +177,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]);
} }
@@ -170,9 +189,8 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
absl::Status Tile16Editor::UpdateTile16Edit() { absl::Status Tile16Editor::UpdateTile16Edit() {
auto ow_main_pal_group = rom()->palette_group().overworld_main; auto ow_main_pal_group = rom()->palette_group().overworld_main;
if (ImGui::BeginChild("Tile8 Selector", if (BeginChild("Tile8 Selector", ImVec2(GetContentRegionAvail().x, 0x175),
ImVec2(ImGui::GetContentRegionAvail().x, 0x175), true)) {
true)) {
tile8_source_canvas_.DrawBackground(); tile8_source_canvas_.DrawBackground();
tile8_source_canvas_.DrawContextMenu(&current_gfx_bmp_); tile8_source_canvas_.DrawContextMenu(&current_gfx_bmp_);
if (tile8_source_canvas_.DrawTileSelector(32)) { if (tile8_source_canvas_.DrawTileSelector(32)) {
@@ -185,7 +203,7 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
tile8_source_canvas_.DrawGrid(); tile8_source_canvas_.DrawGrid();
tile8_source_canvas_.DrawOverlay(); tile8_source_canvas_.DrawOverlay();
} }
ImGui::EndChild(); EndChild();
// The user selected a tile8 // The user selected a tile8
if (!tile8_source_canvas_.points().empty()) { if (!tile8_source_canvas_.points().empty()) {
@@ -199,31 +217,31 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]); rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]);
} }
if (ImGui::BeginChild("Tile16 Editor Options", if (BeginChild("Tile16 Editor Options",
ImVec2(ImGui::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_); rom()->UpdateBitmap(current_tile16_bmp_);
} }
} }
tile16_edit_canvas_.DrawGrid(); tile16_edit_canvas_.DrawGrid();
tile16_edit_canvas_.DrawOverlay(); tile16_edit_canvas_.DrawOverlay();
} }
ImGui::EndChild(); EndChild();
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Tile16Editor::DrawTileEditControls() { absl::Status Tile16Editor::DrawTileEditControls() {
ImGui::Separator(); Separator();
ImGui::Text("Tile16 ID: %d", current_tile16_); Text("Tile16 ID: %d", current_tile16_);
ImGui::Text("Tile8 ID: %d", current_tile8_); Text("Tile8 ID: %d", current_tile8_);
ImGui::Text("Options:"); Text("Options:");
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()) {
@@ -241,15 +259,15 @@ absl::Status Tile16Editor::DrawTileEditControls() {
RETURN_IF_ERROR( RETURN_IF_ERROR(
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value)); current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
RETURN_IF_ERROR( RETURN_IF_ERROR(
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value)); current_tile16_bmp_->ApplyPaletteWithTransparent(palette, value));
rom()->UpdateBitmap(&current_gfx_bmp_); rom()->UpdateBitmap(&current_gfx_bmp_);
rom()->UpdateBitmap(&current_tile16_bmp_); rom()->UpdateBitmap(current_tile16_bmp_);
} }
} }
ImGui::Checkbox("X Flip", &x_flip); Checkbox("X Flip", &x_flip);
ImGui::Checkbox("Y Flip", &y_flip); Checkbox("Y Flip", &y_flip);
ImGui::Checkbox("Priority Tile", &priority_tile); Checkbox("Priority Tile", &priority_tile);
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -308,9 +326,9 @@ absl::Status Tile16Editor::UpdateTile16Transfer() {
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE, if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) { ImVec2(0, 0))) {
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed, TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
ImGui::GetContentRegionAvail().x / 2); GetContentRegionAvail().x / 2);
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed, TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
ImGui::GetContentRegionAvail().x / 2); GetContentRegionAvail().x / 2);
TableHeadersRow(); TableHeadersRow();
TableNextRow(); TableNextRow();
@@ -320,17 +338,17 @@ absl::Status Tile16Editor::UpdateTile16Transfer() {
TableNextColumn(); TableNextColumn();
RETURN_IF_ERROR(UpdateTransferTileCanvas()); RETURN_IF_ERROR(UpdateTransferTileCanvas());
ImGui::EndTable(); EndTable();
} }
ImGui::EndTabItem(); EndTabItem();
} }
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Tile16Editor::UpdateTransferTileCanvas() { absl::Status Tile16Editor::UpdateTransferTileCanvas() {
// Create a button for loading another ROM // Create a button for loading another ROM
if (ImGui::Button("Load ROM")) { if (Button("Load ROM")) {
ImGuiFileDialog::Instance()->OpenDialog( ImGuiFileDialog::Instance()->OpenDialog(
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", "."); "ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", ".");
} }
@@ -353,9 +371,9 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
palette_ = transfer_overworld_.AreaPalette(); palette_ = transfer_overworld_.AreaPalette();
// Create the tile16 blockset image // Create the tile16 blockset image
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x80, RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
transfer_overworld_.Tile16Blockset(), 0x80, 0x2000, 0x80, transfer_overworld_.Tile16Blockset(),
transfer_blockset_bmp_, palette_)); transfer_blockset_bmp_, palette_));
transfer_blockset_loaded_ = true; transfer_blockset_loaded_ = true;
} }
@@ -367,6 +385,16 @@ 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 app
} // namespace yaze } // namespace yaze

View File

@@ -1,22 +1,21 @@
#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 "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/editor/context/gfx_context.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/modules/palette_editor.h"
#include "app/editor/utils/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/gfx/tilesheet.h"
#include "app/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/zelda3/overworld/overworld.h" #include "app/zelda3/overworld/overworld.h"
@@ -29,6 +28,11 @@ namespace editor {
*/ */
class Tile16Editor : public context::GfxContext, public SharedRom { class Tile16Editor : public context::GfxContext, public SharedRom {
public: public:
absl::Status InitBlockset(gfx::Bitmap* tile16_blockset_bmp,
gfx::Bitmap current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual,
uint8_t all_tiles_types[0x200]);
absl::Status Update(); absl::Status Update();
absl::Status DrawMenu(); absl::Status DrawMenu();
@@ -44,28 +48,9 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
absl::Status UpdateTransferTileCanvas(); absl::Status UpdateTransferTileCanvas();
void InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
gfx::Bitmap current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual,
uint8_t all_tiles_types[0x200]) {
all_tiles_types_ = all_tiles_types;
tile16_blockset_bmp_ = tile16_blockset_bmp;
tile16_individual_ = tile16_individual;
current_gfx_bmp_ = current_gfx_bmp;
tile8_gfx_data_ = current_gfx_bmp_.vector();
}
absl::Status LoadTile8(); absl::Status LoadTile8();
absl::Status set_tile16(int id) { absl::Status 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();
}
private: private:
bool map_blockset_loaded_ = false; bool map_blockset_loaded_ = false;
@@ -79,13 +64,6 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
core::NotifyValue<uint32_t> notify_tile16; core::NotifyValue<uint32_t> notify_tile16;
core::NotifyValue<uint8_t> notify_palette; core::NotifyValue<uint8_t> notify_palette;
// Canvas dimensions
int canvas_width;
int canvas_height;
// Texture ID for the canvas
int texture_id;
// Various options for the Tile16 Editor // Various options for the Tile16 Editor
bool x_flip; bool x_flip;
bool y_flip; bool y_flip;
@@ -95,26 +73,24 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
uint8_t* all_tiles_types_; uint8_t* all_tiles_types_;
// Tile16 blockset for selecting the tile to edit // Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{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_{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_;
gfx::Bitmap current_tile8_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",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4), ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k32x32}; gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_; gfx::Bitmap current_gfx_bmp_;
std::vector<gfx::Tilesheet> current_tilesheets_;
gui::Canvas transfer_canvas_; gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_; gfx::Bitmap transfer_blockset_bmp_;
gfx::Bitmap transfer_current_bmp_;
std::vector<Bytes> tile16_individual_data_; std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_; std::vector<gfx::Bitmap> tile16_individual_;

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,36 @@
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H #ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
#define YAZE_APP_EDITOR_MASTER_EDITOR_H #define YAZE_APP_EDITOR_MASTER_EDITOR_H
#define IMGUI_DEFINE_MATH_OPERATORS 1 #define IMGUI_DEFINE_MATH_OPERATORS
#include <ImGuiColorTextEdit/TextEditor.h> #include "ImGuiColorTextEdit/TextEditor.h"
#include <ImGuiFileDialog/ImGuiFileDialog.h> #include "ImGuiFileDialog/ImGuiFileDialog.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <imgui/misc/cpp/imgui_stdlib.h> #include "imgui/misc/cpp/imgui_stdlib.h"
#include <imgui_memory_editor.h> #include "imgui_memory_editor.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/editor/context/gfx_context.h" #include "app/core/project.h"
#include "app/editor/dungeon_editor.h" #include "app/editor/code/assembly_editor.h"
#include "app/editor/graphics_editor.h" #include "app/editor/code/memory_editor.h"
#include "app/editor/modules/assembly_editor.h" #include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/modules/music_editor.h" #include "app/editor/graphics/graphics_editor.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/message/message_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h" #include "app/editor/overworld_editor.h"
#include "app/editor/screen_editor.h" #include "app/editor/settings_editor.h"
#include "app/editor/sprite_editor.h" #include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/gfx_context.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"
#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/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {
@@ -56,14 +59,29 @@ class MasterEditor : public SharedRom,
public context::GfxContext, public context::GfxContext,
public core::ExperimentFlags { public core::ExperimentFlags {
public: public:
MasterEditor() { current_editor_ = &overworld_editor_; } MasterEditor() {
current_editor_ = &overworld_editor_;
active_editors_.push_back(&overworld_editor_);
active_editors_.push_back(&dungeon_editor_);
active_editors_.push_back(&graphics_editor_);
active_editors_.push_back(&palette_editor_);
active_editors_.push_back(&sprite_editor_);
active_editors_.push_back(&message_editor_);
}
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer); void SetupScreen(std::shared_ptr<SDL_Renderer> renderer,
std::string filename = "");
absl::Status Update(); absl::Status Update();
void Shutdown() { overworld_editor_.Shutdown(); } auto emulator() -> emu::Emulator& { return emulator_; }
auto quit() { return quit_; }
auto overworld_editor() -> OverworldEditor& { return overworld_editor_; }
private: private:
void ManageActiveEditors();
void ManageKeyboardShortcuts();
void OpenRomOrProject(const std::string& filename);
void DrawFileDialog(); void DrawFileDialog();
void DrawStatusPopup(); void DrawStatusPopup();
void DrawAboutPopup(); void DrawAboutPopup();
@@ -73,10 +91,16 @@ class MasterEditor : public SharedRom,
void DrawFileMenu(); void DrawFileMenu();
void DrawEditMenu(); void DrawEditMenu();
void DrawViewMenu(); void DrawViewMenu();
void DrawTestMenu();
void DrawProjectMenu();
void DrawHelpMenu(); void DrawHelpMenu();
void LoadRom();
void SaveRom(); void SaveRom();
absl::Status OpenProject();
bool quit_ = false;
bool about_ = false; bool about_ = false;
bool rom_info_ = false; bool rom_info_ = false;
bool backup_rom_ = false; bool backup_rom_ = false;
@@ -91,6 +115,8 @@ class MasterEditor : public SharedRom,
emu::Emulator emulator_; emu::Emulator emulator_;
Project current_project_;
AssemblyEditor assembly_editor_; AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_; DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_; GraphicsEditor graphics_editor_;
@@ -99,8 +125,13 @@ class MasterEditor : public SharedRom,
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
ScreenEditor screen_editor_; ScreenEditor screen_editor_;
SpriteEditor sprite_editor_; SpriteEditor sprite_editor_;
SettingsEditor settings_editor_;
MessageEditor message_editor_;
MemoryEditorWithDiffChecker memory_editor_;
Editor *current_editor_ = nullptr; ImVector<int> active_tabs_;
std::vector<Editor*> active_editors_;
Editor* current_editor_ = nullptr;
}; };
} // namespace editor } // namespace editor

View File

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

View File

@@ -0,0 +1,177 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
namespace yaze {
namespace app {
namespace editor {
const uint8_t MESSAGETERMINATOR = 0x7F;
static std::string AddNewLinesToCommands(std::string str);
static std::string ReplaceAllDictionaryWords(std::string str);
static std::vector<uint8_t> ParseMessageToData(std::string str);
const std::string CHEESE = "\uBEBE"; // Inserted into commands to protect
// them from dictionary replacements.
struct MessageData {
int ID;
int Address;
std::string RawString;
std::string ContentsParsed;
std::vector<uint8_t> Data;
std::vector<uint8_t> DataParsed;
MessageData() = default;
MessageData(int id, int address, const std::string& rawString,
const std::vector<uint8_t>& rawData,
const std::string& parsedString,
const std::vector<uint8_t>& parsedData)
: ID(id),
Address(address),
RawString(rawString),
Data(rawData),
DataParsed(parsedData),
ContentsParsed(parsedString) {}
// Copy constructor
MessageData(const MessageData& other) {
ID = other.ID;
Address = other.Address;
RawString = other.RawString;
Data = other.Data;
DataParsed = other.DataParsed;
ContentsParsed = other.ContentsParsed;
}
void SetMessage(std::string messageString) {
ContentsParsed = messageString;
RawString = OptimizeMessageForDictionary(messageString);
RecalculateData();
}
std::string ToString() {
return absl::StrFormat("%0X - %s", ID, ContentsParsed);
}
std::string GetReadableDumpedContents() {
std::stringstream stringBuilder;
for (const auto& b : Data) {
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;
bool command = false;
for (const auto& c : messageString) {
if (c == '[') {
command = true;
} else if (c == ']') {
command = false;
}
protons << c;
if (command) {
protons << CHEESE;
}
}
std::string protonsString = protons.str();
std::string replacedString = ReplaceAllDictionaryWords(protonsString);
std::string finalString =
absl::StrReplaceAll(replacedString, {{CHEESE, ""}});
return finalString;
}
void RecalculateData() {
Data = ParseMessageToData(RawString);
DataParsed = ParseMessageToData(ContentsParsed);
}
};
struct TextElement {
uint8_t ID;
std::string Token;
std::string GenericToken;
std::string Pattern;
std::string StrictPattern;
std::string Description;
bool HasArgument;
TextElement() = default;
TextElement(uint8_t id, std::string token, bool arg,
std::string description) {
ID = id;
Token = token;
if (arg) {
GenericToken = absl::StrFormat("[%s:##]", Token);
} else {
GenericToken = absl::StrFormat("[%s]", Token);
}
HasArgument = arg;
Description = description;
Pattern =
arg ? "\\[" + Token + ":?([0-9A-F]{1,2})\\]" : "\\[" + Token + "\\]";
Pattern = absl::StrReplaceAll(Pattern, {{"[", "\\["}, {"]", "\\]"}});
StrictPattern = absl::StrCat("^", Pattern, "$");
StrictPattern = "^" + Pattern + "$";
}
std::string GetParameterizedToken(uint8_t value = 0) {
if (HasArgument) {
return absl::StrFormat("[%s:%02X]", Token, value);
} else {
return absl::StrFormat("[%s]", Token);
}
}
std::string ToString() {
return absl::StrFormat("%s %s", GenericToken, Description);
}
std::smatch MatchMe(std::string dfrag) const {
std::regex pattern(StrictPattern);
std::smatch match;
std::regex_match(dfrag, match, pattern);
return match;
}
bool Empty() { return ID == 0; }
};
struct ParsedElement {
TextElement Parent;
uint8_t Value;
bool Active = false;
ParsedElement() = default;
ParsedElement(TextElement textElement, uint8_t value) {
Parent = textElement;
Value = value;
Active = true;
}
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H

View File

@@ -0,0 +1,770 @@
#include "message_editor.h"
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#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/core/common.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.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/style.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::Begin;
using ImGui::BeginChild;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::End;
using ImGui::EndChild;
using ImGui::EndTable;
using ImGui::InputText;
using ImGui::InputTextMultiline;
using ImGui::SameLine;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
using ImGui::TextWrapped;
using ImGui::TreeNode;
static ParsedElement FindMatchingElement(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);
}
}
}
match = DictionaryElement.MatchMe(str);
if (match.size() > 0) {
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() {
for (int i = 0; i < 100; i++) {
width_array[i] = rom()->data()[kCharactersWidth + i];
}
BuildDictionaryEntries();
ReadAllTextData();
font_preview_colors_.AddColor(0x7FFF); // White
font_preview_colors_.AddColor(0x7C00); // Red
font_preview_colors_.AddColor(0x03E0); // Green
font_preview_colors_.AddColor(0x001F); // Blue
std::vector<uint8_t> data(0x4000, 0);
for (int i = 0; i < 0x4000; i++) {
data[i] = rom()->data()[kGfxFont + i];
}
font_gfx16_data = gfx::SnesTo8bppSheet(data, /*bpp=*/2);
// 4bpp
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
128, 128, 8, font_gfx16_data, font_gfx_bitmap_, font_preview_colors_))
current_font_gfx16_data_.reserve(172 * 4096);
for (int i = 0; i < 172 * 4096; i++) {
current_font_gfx16_data_.push_back(0);
}
// 8bpp
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
172, 4096, 64, current_font_gfx16_data_, current_font_gfx16_bitmap_,
font_preview_colors_))
gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
for (int i = 0; i < font_preview_colors_.size(); i++) {
*color_palette.mutable_color(i) = font_preview_colors_[i];
}
*font_gfx_bitmap_.mutable_palette() = color_palette;
for (const auto& message : ListOfTexts) {
DisplayedMessages.push_back(message);
}
for (const auto& each_message : ListOfTexts) {
// Each string has a [:XX] char encoded
// The corresponding character is found in CharEncoder unordered_map
std::string parsed_message = "";
for (const auto& byte : each_message.Data) {
// Find the char byte in the CharEncoder map
if (CharEncoder.contains(byte)) {
parsed_message.push_back(CharEncoder.at(byte));
} else {
// If the byte is not found in the CharEncoder map, it is a command
// or a dictionary entry
if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
// Dictionary entry
auto dictionaryEntry = GetDictionaryFromID(byte - DICTOFF);
parsed_message.append(dictionaryEntry.Contents);
} else {
// Command
TextElement textElement = FindMatchingCommand(byte);
if (!textElement.Empty()) {
// If the element is line 2, 3 or V we add a newline
if (textElement.ID == kScrollVertical || textElement.ID == kLine2 ||
textElement.ID == kLine3)
parsed_message.append("\n");
parsed_message.append(textElement.GenericToken);
}
}
}
}
ParsedMessages.push_back(parsed_message);
}
DrawMessagePreview();
return absl::OkStatus();
}
void MessageEditor::BuildDictionaryEntries() {
for (int i = 0; i < 97; i++) {
std::vector<uint8_t> bytes;
std::stringstream stringBuilder;
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);
AllDictionaries[0].Length = 0;
}
void MessageEditor::ReadAllTextData() {
int messageID = 0;
uint8_t current_byte;
int pos = kTextData;
std::vector<uint8_t> temp_bytes_raw;
std::vector<uint8_t> temp_bytes_parsed;
std::string current_message_raw;
std::string current_message_parsed;
TextElement text_element;
while (true) {
current_byte = rom()->data()[pos++];
if (current_byte == MESSAGETERMINATOR) {
auto message =
MessageData(messageID++, pos, current_message_raw, temp_bytes_raw,
current_message_parsed, temp_bytes_parsed);
ListOfTexts.push_back(message);
temp_bytes_raw.clear();
temp_bytes_parsed.clear();
current_message_raw.clear();
current_message_parsed.clear();
continue;
} else if (current_byte == 0xFF) {
break;
}
temp_bytes_raw.push_back(current_byte);
// Check for command.
text_element = FindMatchingCommand(current_byte);
if (!text_element.Empty()) {
temp_bytes_parsed.push_back(current_byte);
if (text_element.HasArgument) {
current_byte = rom()->data()[pos++];
temp_bytes_raw.push_back(current_byte);
temp_bytes_parsed.push_back(current_byte);
}
current_message_raw.append(
text_element.GetParameterizedToken(current_byte));
current_message_parsed.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_message_raw.append(text_element.GetParameterizedToken());
current_message_parsed.append(text_element.GetParameterizedToken());
temp_bytes_parsed.push_back(current_byte);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(current_byte);
if (dictionary >= 0) {
current_message_raw.append("[");
current_message_raw.append(DICTIONARYTOKEN);
current_message_raw.append(":");
current_message_raw.append(core::UppercaseHexWord(dictionary));
current_message_raw.append("]");
uint32_t address = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + (dictionary * 2));
uint32_t address_end = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) {
temp_bytes_parsed.push_back(rom()->data()[i]);
current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(current_byte)) {
std::string str = "";
str.push_back(CharEncoder.at(current_byte));
current_message_raw.append(str);
current_message_parsed.append(str);
temp_bytes_parsed.push_back(current_byte);
}
}
}
TextElement MessageEditor::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 MessageEditor::FindMatchingSpecial(uint8_t value) {
TextElement empty_element;
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 AllDictionaries[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,
int sizex, int sizey) {
int drawid = srcx + (srcy * 32);
for (int yl = 0; yl < sizey * 8; yl++) {
for (int xl = 0; xl < 4; xl++) {
int mx = xl;
int my = yl;
// Formula information to get tile index position in the array.
// ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
int tx = ((drawid / 16) * 512) + ((drawid - ((drawid / 16) * 16)) * 4);
uint8_t pixel = font_gfx16_data[tx + (yl * 64) + xl];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position
int index = x + (y * 172) + (mx * 2) + (my * 172);
if ((pixel & 0x0F) != 0) {
current_font_gfx16_data_[index + 1] =
(uint8_t)((pixel & 0x0F) + (0 * 4));
}
if (((pixel >> 4) & 0x0F) != 0) {
current_font_gfx16_data_[index + 0] =
(uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
}
}
}
}
void MessageEditor::DrawStringToPreview(string str) {
for (const auto c : str) {
DrawCharacterToPreview(c);
}
}
void MessageEditor::DrawCharacterToPreview(char c) {
DrawCharacterToPreview(FindMatchingCharacter(c));
}
void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
for (const uint8_t& value : text) {
if (skip_next) {
skip_next = false;
continue;
}
if (value < 100) {
int srcy = value / 16;
int srcx = value - (value & (~0xF));
if (text_pos >= 170) {
text_pos = 0;
text_line++;
}
DrawTileToPreview(text_pos, text_line * 16, srcx, srcy, 0, 1, 2);
text_pos += width_array[value];
} else if (value == kLine1) {
text_pos = 0;
text_line = 0;
} else if (value == kScrollVertical) {
text_pos = 0;
text_line += 1;
} else if (value == kLine2) {
text_pos = 0;
text_line = 1;
} else if (value == kLine3) {
text_pos = 0;
text_line = 2;
} else if (value == 0x6B || value == 0x6D || value == 0x6E ||
value == 0x77 || value == 0x78 || value == 0x79 ||
value == 0x7A) {
skip_next = true;
continue;
} else if (value == 0x6C) // BCD numbers.
{
DrawCharacterToPreview('0');
skip_next = true;
continue;
} else if (value == 0x6A) {
// Includes parentheses to be longer, since player names can be up to 6
// characters.
DrawStringToPreview("(NAME)");
} else if (value >= DICTOFF && value < (DICTOFF + 97)) {
auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
DrawCharacterToPreview(dictionaryEntry.Data);
}
}
}
void MessageEditor::DrawMessagePreview() // From Parsing.
{
text_line = 0;
for (int i = 0; i < (172 * 4096); i++) {
current_font_gfx16_data_[i] = 0;
}
text_pos = 0;
DrawCharacterToPreview(CurrentMessage.Data);
shown_lines = 0;
}
absl::Status MessageEditor::Cut() {
// Ensure that text is currently selected in the text box.
if (!message_text_box_.text.empty()) {
// Cut the selected text in the control and paste it into the Clipboard.
message_text_box_.Cut();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Paste() {
// Determine if there is any text in the Clipboard to paste into the
if (ImGui::GetClipboardText() != nullptr) {
// Paste the text from the Clipboard into the text box.
message_text_box_.Paste();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Copy() {
// Ensure that text is selected in the text box.
if (message_text_box_.selection_length > 0) {
// Copy the selected text to the Clipboard.
message_text_box_.Copy();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Undo() {
// Determine if last operation can be undone in text box.
if (message_text_box_.can_undo) {
// Undo the last operation.
message_text_box_.Undo();
// clear the undo buffer to prevent last action from being redone.
message_text_box_.clearUndo();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Save() {
std::vector<uint8_t> backup = rom()->vector();
for (int i = 0; i < 100; i++) {
RETURN_IF_ERROR(rom()->Write(kCharactersWidth + i, width_array[i]));
}
int pos = kTextData;
bool in_second_bank = false;
for (const auto& message : ListOfTexts) {
for (const auto value : message.Data) {
RETURN_IF_ERROR(rom()->Write(pos, value));
if (value == kBlockTerminator) {
// Make sure we didn't go over the space available in the first block.
// 0x7FFF available.
if ((!in_second_bank & pos) > kTextDataEnd) {
return absl::InternalError(DisplayTextOverflowError(pos, true));
}
// Switch to the second block.
pos = kTextData2 - 1;
in_second_bank = true;
}
pos++;
}
RETURN_IF_ERROR(
rom()->Write(pos++, MESSAGETERMINATOR)); // , true, "Terminator text"
}
// Verify that we didn't go over the space available for the second block.
// 0x14BF available.
if ((in_second_bank & pos) > kTextData2End) {
// rom()->data() = backup;
return absl::InternalError(DisplayTextOverflowError(pos, false));
}
RETURN_IF_ERROR(rom()->Write(pos, 0xFF)); // , true, "End of text"
return absl::OkStatus();
}
std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
string bankSTR = bank ? "1st" : "2nd";
string posSTR = bank ? absl::StrFormat("%X4", pos & 0xFFFF)
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
std::string message = absl::StrFormat(
"There is too much text data in the %s block to save.\n"
"Available: %X4 | Used: %s",
bankSTR, space, posSTR);
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() {
// Determine if any text is selected in the TextBox control.
if (message_text_box_.selection_length == 0) {
// clear all of the text in the textbox.
message_text_box_.clear();
}
}
void MessageEditor::SelectAll() {
// Determine if any text is selected in the TextBox control.
if (message_text_box_.selection_length == 0) {
// Select all text in the text box.
message_text_box_.SelectAll();
// Move the cursor to the text box.
message_text_box_.Focus();
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,366 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#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/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
using std::string;
// TEXT EDITOR RELATED CONSTANTS
const int kGfxFont = 0x70000; // 2bpp format
const int kTextData = 0xE0000;
const int kTextDataEnd = 0xE7FFF;
const int kTextData2 = 0x75F40;
const int kTextData2End = 0x773FF;
const int kPointersDictionaries = 0x74703;
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 kBlockTerminator = 0x80;
static std::vector<uint8_t> ParseMessageToData(string str);
static ParsedElement FindMatchingElement(string str);
constexpr uint8_t kScrollVertical = 0x73;
constexpr uint8_t kLine1 = 0x74;
constexpr uint8_t kLine2 = 0x75;
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 =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
class MessageEditor : public Editor, public SharedRom {
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; }
absl::Status Update() override;
void DrawMessageList();
void DrawCurrentMessage();
void DrawTextCommands();
absl::Status Initialize();
void ReadAllTextData();
void BuildDictionaryEntries();
absl::Status Cut() override;
absl::Status Copy() override;
absl::Status Paste() override;
absl::Status Undo() override;
absl::Status Redo() override {
return absl::UnimplementedError("Redo not implemented");
}
absl::Status Find() override {
return absl::UnimplementedError("Find not implemented");
}
absl::Status Save();
void Delete();
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);
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,
int sizex = 1, int sizey = 1);
void DrawCharacterToPreview(char c);
void DrawCharacterToPreview(const std::vector<uint8_t>& text);
void DrawStringToPreview(string str);
void DrawMessagePreview();
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:
static const TextElement DictionaryElement;
bool data_loaded_ = false;
int current_message_id_ = 0;
std::string search_text_ = "";
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)};
gui::Canvas current_font_gfx16_canvas_{"##CurrentMessageGfx",
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 {
std::string text;
std::string buffer;
int cursor_pos = 0;
int selection_start = 0;
int selection_end = 0;
int selection_length = 0;
bool has_selection = false;
bool has_focus = false;
bool changed = false;
bool can_undo = false;
void Undo() {
text = buffer;
cursor_pos = selection_start;
has_selection = false;
}
void clearUndo() { can_undo = false; }
void Copy() { ImGui::SetClipboardText(text.c_str()); }
void Cut() {
Copy();
text.erase(selection_start, selection_end - selection_start);
cursor_pos = selection_start;
has_selection = false;
changed = true;
}
void Paste() {
text.erase(selection_start, selection_end - selection_start);
text.insert(selection_start, ImGui::GetClipboardText());
std::string str = ImGui::GetClipboardText();
cursor_pos = selection_start + str.size();
has_selection = false;
changed = true;
}
void clear() {
text.clear();
buffer.clear();
cursor_pos = 0;
selection_start = 0;
selection_end = 0;
selection_length = 0;
has_selection = false;
has_focus = false;
changed = false;
can_undo = false;
}
void SelectAll() {
selection_start = 0;
selection_end = text.size();
selection_length = text.size();
has_selection = true;
}
void Focus() { has_focus = true; }
};
TextBox message_text_box_;
};
static std::vector<MessageEditor::DictionaryEntry> AllDictionaries;
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_MESSAGE_EDITOR_H

View File

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

View File

@@ -1,152 +0,0 @@
#include "assembly_editor.h"
#include "app/gui/widgets.h"
#include "core/constants.h"
namespace yaze {
namespace app {
namespace editor {
AssemblyEditor::AssemblyEditor() {
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
text_editor_.SetPalette(TextEditor::GetDarkPalette());
}
void AssemblyEditor::Update(bool& is_loaded) {
ImGui::Begin("Assembly Editor", &is_loaded);
MENU_BAR()
DrawFileMenu();
DrawEditMenu();
END_MENU_BAR()
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor");
ImGui::End();
}
void AssemblyEditor::InlineUpdate() {
ChangeActiveFile("assets/asm/template_song.asm");
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor", ImVec2(0, 0));
}
void AssemblyEditor::ChangeActiveFile(const std::string_view& filename) {
current_file_ = filename;
}
void AssemblyEditor::DrawFileView() {
ImGui::BeginTable("##table_view", 4,
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg);
// Table headers
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 150.0f);
ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed, 60.0f);
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100.0f);
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
// Table data
ImGui::TableNextRow();
ImGui::TableNextColumn();
// TODO: Add tree view of files
ImGui::TableNextColumn();
// TODO: Add line number
ImGui::TableNextColumn();
// TODO: Add address per line
ImGui::TableNextColumn();
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor");
ImGui::EndTable();
}
void AssemblyEditor::DrawFileMenu() {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) {
ImGuiFileDialog::Instance()->OpenDialog(
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
}
if (ImGui::MenuItem("Save", "Ctrl+S")) {
// TODO: Implement this
}
ImGui::EndMenu();
}
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
if (ImGuiFileDialog::Instance()->IsOk()) {
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
}
ImGuiFileDialog::Instance()->Close();
}
}
void AssemblyEditor::DrawEditMenu() {
if (ImGui::BeginMenu("Edit")) {
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
text_editor_.Undo();
}
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
text_editor_.Redo();
}
ImGui::Separator();
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
text_editor_.Cut();
}
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
text_editor_.Copy();
}
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
text_editor_.Paste();
}
ImGui::Separator();
if (ImGui::MenuItem("Find", "Ctrl+F")) {
// TODO: Implement this.
}
ImGui::EndMenu();
}
}
void AssemblyEditor::SetEditorText() {
if (!file_is_loaded_) {
std::ifstream t(current_file_);
if (t.good()) {
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
text_editor_.SetText(str);
file_is_loaded_ = true;
}
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,45 +0,0 @@
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <fstream>
#include <istream>
#include <string>
namespace yaze {
namespace app {
namespace editor {
/**
* @class AssemblyEditor
* @brief Text editor for modifying assembly code.
*/
class AssemblyEditor {
public:
AssemblyEditor();
void Update(bool &is_loaded);
void InlineUpdate();
void ChangeActiveFile(const std::string_view &);
private:
void DrawFileMenu();
void DrawEditMenu();
void DrawFileView();
void SetEditorText();
bool file_is_loaded_ = false;
std::string current_file_;
TextEditor text_editor_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif

View File

@@ -1,246 +0,0 @@
#include "gfx_group_editor.h"
#include <imgui/imgui.h>
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/modules/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/gui/widgets.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::SameLine;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
absl::Status GfxGroupEditor::Update() {
if (ImGui::BeginTabBar("GfxGroupEditor")) {
if (ImGui::BeginTabItem("Main")) {
gui::InputHexByte("Selected Blockset", &selected_blockset_);
DrawBlocksetViewer();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Rooms")) {
gui::InputHexByte("Selected Blockset", &selected_roomset_);
DrawRoomsetViewer();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Sprites")) {
gui::InputHexByte("Selected Spriteset", &selected_spriteset_);
ImGui::Text("Values");
DrawSpritesetViewer();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Palettes")) {
DrawPaletteViewer();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::Separator();
ImGui::Text("Palette: ");
ImGui::InputInt("##PreviewPaletteID", &preview_palette_id_);
return absl::OkStatus();
}
void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
if (ImGui::BeginTable("##BlocksetTable", sheet_only ? 1 : 2,
ImGuiTableFlags_Borders, ImVec2(0, 0))) {
if (!sheet_only) {
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
}
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
if (!sheet_only) {
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 8; i++) {
ImGui::SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->main_blockset_ids[selected_blockset_][i]);
}
ImGui::EndGroup();
}
}
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 8; i++) {
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
auto &sheet = *rom()->bitmap_manager()[sheet_id];
// if (sheet_id != last_sheet_id_) {
// last_sheet_id_ = sheet_id;
// auto palette_group = rom()->palette_group("ow_main");
// auto palette = palette_group[preview_palette_id_];
// sheet.ApplyPalette(palette);
// rom()->UpdateBitmap(&sheet);
// }
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 22);
}
ImGui::EndGroup();
}
ImGui::EndTable();
}
}
void GfxGroupEditor::DrawRoomsetViewer() {
ImGui::Text("Values - Overwrites 4 of main blockset");
if (ImGui::BeginTable("##Roomstable", 2, ImGuiTableFlags_Borders,
ImVec2(0, 0))) {
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 4; i++) {
ImGui::SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->room_blockset_ids[selected_roomset_][i]);
}
ImGui::EndGroup();
}
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 4; i++) {
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
auto &sheet = *rom()->bitmap_manager()[sheet_id];
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 23);
}
ImGui::EndGroup();
}
ImGui::EndTable();
}
}
void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
if (ImGui::BeginTable("##SpritesTable", sheet_only ? 1 : 2,
ImGuiTableFlags_Borders, ImVec2(0, 0))) {
if (!sheet_only) {
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
}
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
if (!sheet_only) {
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 4; i++) {
ImGui::SetNextItemWidth(100.f);
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
&rom()->spriteset_ids[selected_spriteset_][i]);
}
ImGui::EndGroup();
}
}
TableNextColumn();
{
ImGui::BeginGroup();
for (int i = 0; i < 4; i++) {
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
auto sheet = *rom()->bitmap_manager()[115 + sheet_id];
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 24);
}
ImGui::EndGroup();
}
ImGui::EndTable();
}
}
namespace {
void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
for (int n = 0; n < palette.size(); n++) {
ImGui::PushID(n);
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
auto popup_id = absl::StrCat("Palette", n);
// Small icon of the color in the palette
if (gui::SnesColorButton(popup_id, palette[n],
ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip)) {
}
ImGui::PopID();
}
}
} // namespace
void GfxGroupEditor::DrawPaletteViewer() {
static uint8_t selected_paletteset = 0;
gui::InputHexByte("Selected Paletteset", &selected_paletteset);
auto dungeon_main_palette_val = rom()->paletteset_ids[selected_paletteset][0];
auto dungeon_spr_pal_1_val = rom()->paletteset_ids[selected_paletteset][1];
auto dungeon_spr_pal_2_val = rom()->paletteset_ids[selected_paletteset][2];
auto dungeon_spr_pal_3_val = rom()->paletteset_ids[selected_paletteset][3];
gui::InputHexByte("Dungeon Main", &dungeon_main_palette_val);
gui::InputHexByte("Dungeon Spr Pal 1", &dungeon_spr_pal_1_val);
gui::InputHexByte("Dungeon Spr Pal 2", &dungeon_spr_pal_2_val);
gui::InputHexByte("Dungeon Spr Pal 3", &dungeon_spr_pal_3_val);
auto &palette = *rom()->mutable_palette_group()->dungeon_main.mutable_palette(
rom()->paletteset_ids[selected_paletteset][0]);
DrawPaletteFromPaletteGroup(palette);
auto &spr_aux_pal1 =
*rom()->mutable_palette_group()->sprites_aux1.mutable_palette(
rom()->paletteset_ids[selected_paletteset][1]);
DrawPaletteFromPaletteGroup(spr_aux_pal1);
auto &spr_aux_pal2 =
*rom()->mutable_palette_group()->sprites_aux2.mutable_palette(
rom()->paletteset_ids[selected_paletteset][2]);
DrawPaletteFromPaletteGroup(spr_aux_pal2);
auto &spr_aux_pal3 =
*rom()->mutable_palette_group()->sprites_aux3.mutable_palette(
rom()->paletteset_ids[selected_paletteset][3]);
DrawPaletteFromPaletteGroup(spr_aux_pal3);
}
void GfxGroupEditor::InitBlockset(gfx::Bitmap tile16_blockset) {
tile16_blockset_bmp_ = tile16_blockset;
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,300 +0,0 @@
#include "palette_editor.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"
static inline float ImSaturate(float f) {
return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
}
#define IM_F32_TO_INT8_SAT(_VAL) \
((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
#ifdef IMGUI_USE_STB_SPRINTF
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
#else
int w = vsnprintf(buf, buf_size, fmt, args);
#endif
va_end(args);
if (buf == nullptr) return w;
if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
buf[w] = 0;
return w;
}
namespace yaze {
namespace app {
namespace editor {
absl::Status PaletteEditor::Update() {
if (rom()->is_loaded()) {
// Initialize the labels
for (int i = 0; i < kNumPalettes; i++) {
rom()->resource_label()->CreateOrGetLabel(
"Palette Group Name", std::to_string(i),
std::string(kPaletteGroupNames[i]));
}
}
if (ImGui::BeginTable("paletteEditorTable", 2,
ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame,
ImVec2(0, 0))) {
ImGui::TableSetupColumn("Palette Groups",
ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
for (int category = 0; category < kNumPalettes; ++category) {
if (ImGui::TreeNode(kPaletteCategoryNames[category].data())) {
status_ = DrawPaletteGroup(category);
ImGui::TreePop();
}
}
ImGui::TableNextColumn();
if (gui::SnesColorEdit4("Color Picker", current_color_,
ImGuiColorEditFlags_NoAlpha)) {
}
ImGui::EndTable();
}
CLEAR_AND_RETURN_STATUS(status_)
return absl::OkStatus();
}
absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette,
int index) {
if (index >= palette.size()) {
return absl::InvalidArgumentError("Index out of bounds");
}
// Get the current color
ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
auto currentColor = color.rgb();
if (ImGui::ColorPicker4("Color Picker", (float*)&palette[index])) {
// The color was modified, update it in the palette
palette(index, currentColor);
}
return absl::OkStatus();
}
absl::Status PaletteEditor::ResetColorToOriginal(
gfx::SnesPalette& palette, int index,
const gfx::SnesPalette& originalPalette) {
if (index >= palette.size() || index >= originalPalette.size()) {
return absl::InvalidArgumentError("Index out of bounds");
}
ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
auto originalColor = color.rgb();
palette(index, originalColor);
return absl::OkStatus();
}
absl::Status PaletteEditor::DrawPaletteGroup(int category) {
if (!rom()->is_loaded()) {
return absl::NotFoundError("ROM not open, no palettes to display");
}
std::string group_name = kPaletteGroupNames[category].data();
auto palette_group = *rom()->palette_group().get_group(group_name);
const auto size = palette_group.size();
static bool edit_color = false;
for (int j = 0; j < size; j++) {
// ImGui::Text("%d", j);
rom()->resource_label()->SelectableLabelWithNameEdit(
false, "Palette Group Name", std::to_string(j),
std::string(kPaletteGroupNames[category]));
auto palette = palette_group.mutable_palette(j);
auto pal_size = palette->size();
for (int n = 0; n < pal_size; n++) {
ImGui::PushID(n);
if ((n % 7) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
auto popup_id =
absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
// Small icon of the color in the palette
if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
palette_button_flags)) {
ASSIGN_OR_RETURN(current_color_, palette->GetColor(n));
// EditColorInPalette(*palette, n);
}
if (ImGui::BeginPopupContextItem(popup_id.c_str())) {
RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
}
// if (gui::SnesColorEdit4(popup_id.c_str(), (*palette)[n],
// palette_button_flags)) {
// EditColorInPalette(*palette, n);
// }
ImGui::PopID();
}
}
return absl::OkStatus();
}
absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
int j, int n) {
auto col = gfx::ToFloatArray(palette[n]);
if (gui::SnesColorEdit4("Edit Color", palette[n], color_popup_flags)) {
// TODO: Implement new update color function
}
if (ImGui::Button("Copy as..", ImVec2(-1, 0))) ImGui::OpenPopup("Copy");
if (ImGui::BeginPopup("Copy")) {
int cr = IM_F32_TO_INT8_SAT(col[0]);
int cg = IM_F32_TO_INT8_SAT(col[1]);
int cb = IM_F32_TO_INT8_SAT(col[2]);
char buf[64];
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
col[1], col[2]);
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
ImGui::EndPopup();
}
ImGui::EndPopup();
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) {
InitializeSavedPalette(palette);
init = true;
}
static ImVec4 backup_color;
bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags);
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
open_popup |= ImGui::Button("Palette");
if (open_popup) {
ImGui::OpenPopup("mypicker");
backup_color = color;
}
if (ImGui::BeginPopup("mypicker")) {
TEXT_WITH_SEPARATOR("Current Overworld Palette");
ImGui::ColorPicker4("##picker", (float*)&color,
misc_flags | ImGuiColorEditFlags_NoSidePreview |
ImGuiColorEditFlags_NoSmallPreview);
ImGui::SameLine();
ImGui::BeginGroup(); // Lock X position
ImGui::Text("Current ==>");
ImGui::SameLine();
ImGui::Text("Previous");
if (ImGui::Button("Update Map Palette")) {
}
ImGui::ColorButton(
"##current", color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40));
ImGui::SameLine();
if (ImGui::ColorButton(
"##previous", backup_color,
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
ImVec2(60, 40)))
color = backup_color;
// List of Colors in Overworld Palette
ImGui::Separator();
ImGui::Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
ImGui::PushID(n);
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
if (ImGui::ColorButton("##palette", saved_palette_[n],
palette_button_flags_2, ImVec2(20, 20)))
color = ImVec4(saved_palette_[n].x, saved_palette_[n].y,
saved_palette_[n].z, color.w); // Preserve alpha!
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
ImGui::EndGroup();
ImGui::EndPopup();
}
}
void PaletteEditor::DrawPortablePalette(gfx::SnesPalette& palette) {
static bool init = false;
if (!init) {
InitializeSavedPalette(palette);
init = true;
}
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100);
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
ImGui::BeginGroup(); // Lock X position
ImGui::Text("Palette");
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
ImGui::PushID(n);
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
if (ImGui::ColorButton("##palette", saved_palette_[n],
palette_button_flags_2, ImVec2(20, 20)))
ImVec4(saved_palette_[n].x, saved_palette_[n].y, saved_palette_[n].z,
1.0f); // Preserve alpha!
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
if (const ImGuiPayload* payload =
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
ImGui::EndGroup();
}
ImGui::EndChild();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,71 +1,18 @@
#include "music_editor.h" #include "music_editor.h"
#include <SDL_mixer.h> #include "imgui/imgui.h"
#include <imgui/imgui.h>
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/editor/modules/assembly_editor.h" #include "app/editor/code/assembly_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 "snes_spc/demo/demo_util.h"
// #include "snes_spc/demo/wave_writer.h"
// #include "snes_spc/snes_spc/spc.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace editor { namespace editor {
namespace { absl::Status MusicEditor::Update() {
#define BUF_SIZE 2048
void PlaySPC() {
// /* Create emulator and filter */
// SNES_SPC* snes_spc = spc_new();
// SPC_Filter* filter = spc_filter_new();
// if (!snes_spc || !filter) error("Out of memory");
// /* Load SPC */
// {
// /* Load file into memory */
// long spc_size;
// void* spc = load_file("assets/music/hyrule_field.spc", &spc_size);
// /* Load SPC data into emulator */
// error(spc_load_spc(snes_spc, spc, spc_size));
// free(spc); /* emulator makes copy of data */
// /* Most SPC files have garbage data in the echo buffer, so clear that */
// spc_clear_echo(snes_spc);
// /* Clear filter before playing */
// spc_filter_clear(filter);
// }
// /* Record 20 seconds to wave file */
// wave_open(spc_sample_rate, "out.wav");
// wave_enable_stereo();
// while (wave_sample_count() < 30 * spc_sample_rate * 2) {
// /* Play into buffer */
// short buf[BUF_SIZE];
// error(spc_play(snes_spc, BUF_SIZE, buf));
// /* Filter samples */
// spc_filter_run(filter, buf, BUF_SIZE);
// wave_write(buf, BUF_SIZE);
// }
// /* Cleanup */
// spc_filter_delete(filter);
// spc_delete(snes_spc);
// wave_close();
}
} // namespace
void MusicEditor::Update() {
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_, if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
ImVec2(0, 0))) { ImVec2(0, 0))) {
ImGui::TableSetupColumn("Assembly"); ImGui::TableSetupColumn("Assembly");
@@ -83,6 +30,8 @@ void MusicEditor::Update() {
ImGui::EndTable(); ImGui::EndTable();
} }
return absl::OkStatus();
} }
void MusicEditor::DrawChannels() { void MusicEditor::DrawChannels() {
@@ -221,31 +170,8 @@ void MusicEditor::DrawToolset() {
static bool has_loaded_song = false; static bool has_loaded_song = false;
const int MAX_VOLUME = 100; const int MAX_VOLUME = 100;
if (is_playing) { if (is_playing && !has_loaded_song) {
if (!has_loaded_song) { has_loaded_song = true;
// PlaySPC();
// current_song_ = Mix_LoadMUS("out.wav");
// Mix_PlayMusic(current_song_, -1);
// has_loaded_song = true;
}
// // If there is no music playing
// if (Mix_PlayingMusic() == 0) {
// Mix_PlayMusic(current_song_, -1);
// }
// // If music is being played
// else {
// // If the music is paused
// if (Mix_PausedMusic() == 1) {
// // Resume the music
// Mix_ResumeMusic();
// }
// // If the music is playing
// else {
// // Pause the music
// Mix_PauseMusic();
// }
// }
} }
gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left); gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
@@ -264,7 +190,6 @@ void MusicEditor::DrawToolset() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) { if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
if (is_playing) { if (is_playing) {
Mix_HaltMusic();
has_loaded_song = false; has_loaded_song = false;
} }
is_playing = !is_playing; is_playing = !is_playing;

View File

@@ -1,11 +1,11 @@
#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 <SDL_mixer.h> #include "imgui/imgui.h"
#include <imgui/imgui.h>
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/editor/modules/assembly_editor.h" #include "app/editor/code/assembly_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/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
@@ -59,9 +59,18 @@ static constexpr absl::string_view kSongNotes[] = {
* @class MusicEditor * @class MusicEditor
* @brief A class for editing music data in a Rom. * @brief A class for editing music data in a Rom.
*/ */
class MusicEditor : public SharedRom { class MusicEditor : public SharedRom, public Editor {
public: public:
void Update(); MusicEditor() { type_ = EditorType::kMusic; }
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
private: private:
void DrawChannels(); void DrawChannels();

View File

@@ -0,0 +1,490 @@
#include "app/editor/overworld/entity.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::BeginChild;
using ImGui::BeginGroup;
using ImGui::Button;
using ImGui::Checkbox;
using ImGui::EndChild;
using ImGui::SameLine;
using ImGui::Selectable;
using ImGui::Text;
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
ImVec2 canvas_p0, ImVec2 scrolling) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
// Check if the mouse is hovering over the entity
if (mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 &&
mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16) {
return true;
}
return false;
}
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool free_movement) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
// Calculate the new position on the 16x16 grid
int new_x = static_cast<int>(mouse_pos.x) / 16 * 16;
int new_y = static_cast<int>(mouse_pos.y) / 16 * 16;
if (free_movement) {
new_x = static_cast<int>(mouse_pos.x) / 8 * 8;
new_y = static_cast<int>(mouse_pos.y) / 8 * 8;
}
// Update the entity position
entity->set_x(new_x);
entity->set_y(new_y);
}
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool &is_dragging_entity,
zelda3::OverworldEntity *&dragged_entity,
zelda3::OverworldEntity *&current_entity,
bool free_movement) {
std::string entity_type = "Entity";
if (entity->type_ == zelda3::OverworldEntity::EntityType::kExit) {
entity_type = "Exit";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kEntrance) {
entity_type = "Entrance";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kSprite) {
entity_type = "Sprite";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kItem) {
entity_type = "Item";
}
const auto is_hovering =
IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling);
const auto drag_or_clicked = ImGui::IsMouseDragging(ImGuiMouseButton_Left) ||
ImGui::IsMouseClicked(ImGuiMouseButton_Left);
if (is_hovering && drag_or_clicked && !is_dragging_entity) {
dragged_entity = entity;
is_dragging_entity = true;
} else if (is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
current_entity = entity;
ImGui::OpenPopup(absl::StrFormat("%s editor", entity_type.c_str()).c_str());
} else if (is_dragging_entity && dragged_entity == entity &&
ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
entity->UpdateMapProperties(entity->map_id_);
is_dragging_entity = false;
dragged_entity = nullptr;
} else if (is_dragging_entity && dragged_entity == entity) {
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
sizeof(zelda3::OverworldEntity));
Text("Moving %s ID: %s", entity_type.c_str(),
core::UppercaseHexByte(entity->entity_id_).c_str());
ImGui::EndDragDropSource();
}
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
entity->x_ = dragged_entity->x_;
entity->y_ = dragged_entity->y_;
entity->UpdateMapProperties(entity->map_id_);
}
}
bool DrawEntranceInserterPopup() {
bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopup("Entrance Inserter")) {
static int entrance_id = 0;
gui::InputHex("Entrance ID", &entrance_id);
if (Button(ICON_MD_DONE)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
// TODO: Implement deleting OverworldEntrance objects, currently only hides them
bool DrawOverworldEntrancePopup(
zelda3::overworld::OverworldEntrance &entrance) {
static bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopupModal("Entrance editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
gui::InputHex("Map ID", &entrance.map_id_);
gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
kInputFieldSize + 20);
gui::InputHex("X", &entrance.x_);
gui::InputHex("Y", &entrance.y_);
if (Button(ICON_MD_DONE)) {
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_DELETE)) {
entrance.deleted = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
// TODO: Implement deleting OverworldExit objects
void DrawExitInserterPopup() {
if (ImGui::BeginPopup("Exit Inserter")) {
static int exit_id = 0;
gui::InputHex("Exit ID", &exit_id);
if (Button(ICON_MD_DONE)) {
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) {
static bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopupModal("Exit editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
// Normal door: None = 0, Wooden = 1, Bombable = 2
static int doorType = exit.door_type_1_;
// Fancy door: None = 0, Sanctuary = 1, Palace = 2
static int fancyDoorType = exit.door_type_2_;
static int xPos = 0;
static int yPos = 0;
// Special overworld exit properties
static int centerY = 0;
static int centerX = 0;
static int unk1 = 0;
static int unk2 = 0;
static int linkPosture = 0;
static int spriteGFX = 0;
static int bgGFX = 0;
static int palette = 0;
static int sprPal = 0;
static int top = 0;
static int bottom = 0;
static int left = 0;
static int right = 0;
static int leftEdgeOfMap = 0;
gui::InputHexWord("Room", &exit.room_id_);
SameLine();
gui::InputHex("Entity ID", &exit.entity_id_, 4);
gui::InputHex("Map", &exit.map_id_);
SameLine();
Checkbox("Automatic", &exit.is_automatic_);
gui::InputHex("X Positon", &exit.x_);
SameLine();
gui::InputHex("Y Position", &exit.y_);
gui::InputHexByte("X Camera", &exit.x_camera_);
SameLine();
gui::InputHexByte("Y Camera", &exit.y_camera_);
gui::InputHexWord("X Scroll", &exit.x_scroll_);
SameLine();
gui::InputHexWord("Y Scroll", &exit.y_scroll_);
ImGui::Separator();
static bool show_properties = false;
Checkbox("Show properties", &show_properties);
if (show_properties) {
Text("Deleted? %s", exit.deleted_ ? "true" : "false");
Text("Hole? %s", exit.is_hole_ ? "true" : "false");
Text("Large Map? %s", exit.large_map_ ? "true" : "false");
}
gui::TextWithSeparators("Unimplemented below");
ImGui::RadioButton("None", &doorType, 0);
SameLine();
ImGui::RadioButton("Wooden", &doorType, 1);
SameLine();
ImGui::RadioButton("Bombable", &doorType, 2);
// If door type is not None, input positions
if (doorType != 0) {
gui::InputHex("Door X pos", &xPos);
gui::InputHex("Door Y pos", &yPos);
}
ImGui::RadioButton("None##Fancy", &fancyDoorType, 0);
SameLine();
ImGui::RadioButton("Sanctuary", &fancyDoorType, 1);
SameLine();
ImGui::RadioButton("Palace", &fancyDoorType, 2);
// If fancy door type is not None, input positions
if (fancyDoorType != 0) {
// Placeholder for fancy door's X position
gui::InputHex("Fancy Door X pos", &xPos);
// Placeholder for fancy door's Y position
gui::InputHex("Fancy Door Y pos", &yPos);
}
static bool special_exit = false;
Checkbox("Special exit", &special_exit);
if (special_exit) {
gui::InputHex("Center X", &centerX);
gui::InputHex("Center Y", &centerY);
gui::InputHex("Unk1", &unk1);
gui::InputHex("Unk2", &unk2);
gui::InputHex("Link's posture", &linkPosture);
gui::InputHex("Sprite GFX", &spriteGFX);
gui::InputHex("BG GFX", &bgGFX);
gui::InputHex("Palette", &palette);
gui::InputHex("Spr Pal", &sprPal);
gui::InputHex("Top", &top);
gui::InputHex("Bottom", &bottom);
gui::InputHex("Left", &left);
gui::InputHex("Right", &right);
gui::InputHex("Left edge of map", &leftEdgeOfMap);
}
if (Button(ICON_MD_DONE)) {
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_DELETE)) {
exit.deleted_ = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
void DrawItemInsertPopup() {
// Contents of the Context Menu
if (ImGui::BeginPopup("Item Inserter")) {
static int new_item_id = 0;
Text("Add Item");
BeginChild("ScrollRegion", ImVec2(150, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
i == new_item_id)) {
new_item_id = i;
}
}
EndChild();
if (Button(ICON_MD_DONE)) {
// Add the new item to the overworld
new_item_id = 0;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// TODO: Implement deleting OverworldItem objects, currently only hides them
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item) {
static bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopupModal("Item editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
BeginChild("ScrollRegion", ImVec2(150, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::BeginGroup();
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
item.id == i)) {
item.id = i;
}
}
ImGui::EndGroup();
EndChild();
if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
SameLine();
if (Button(ICON_MD_CLOSE)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_DELETE)) {
item.deleted = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
const ImGuiTableSortSpecs *SpriteItem::s_current_sort_specs = nullptr;
void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
static ImGuiTextFilter filter;
static int selected_id = 0;
static std::vector<SpriteItem> items;
// Initialize items if empty
if (items.empty()) {
for (int i = 0; i < 256; ++i) {
items.push_back(SpriteItem{i, core::kSpriteDefaultNames[i].data()});
}
}
filter.Draw("Filter", 180);
if (ImGui::BeginTable("##sprites", 2,
ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
MyItemColumnID_ID);
ImGui::TableSetupColumn("Name", 0, 0.0f, MyItemColumnID_Name);
ImGui::TableHeadersRow();
// Handle sorting
if (ImGuiTableSortSpecs *sort_specs = ImGui::TableGetSortSpecs()) {
if (sort_specs->SpecsDirty) {
SpriteItem::SortWithSortSpecs(sort_specs, items);
sort_specs->SpecsDirty = false;
}
}
// Display filtered and sorted items
for (const auto &item : items) {
if (filter.PassFilter(item.name)) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
Text("%d", item.id);
ImGui::TableSetColumnIndex(1);
if (Selectable(item.name, selected_id == item.id,
ImGuiSelectableFlags_SpanAllColumns)) {
selected_id = item.id;
onSpriteSelect(item.id);
}
}
}
ImGui::EndTable();
}
}
// TODO: Implement deleting OverworldSprite objects
void DrawSpriteInserterPopup() {
if (ImGui::BeginPopup("Sprite Inserter")) {
static int new_sprite_id = 0;
Text("Add Sprite");
BeginChild("ScrollRegion", ImVec2(250, 250), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
DrawSpriteTable([](int selected_id) { new_sprite_id = selected_id; });
EndChild();
if (Button(ICON_MD_DONE)) {
// Add the new item to the overworld
new_sprite_id = 0;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite) {
static bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopupModal("Sprite editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
BeginChild("ScrollRegion", ImVec2(350, 350), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::BeginGroup();
Text("%s", sprite.name().c_str());
DrawSpriteTable([&sprite](int selected_id) {
sprite.set_id(selected_id);
sprite.UpdateMapProperties(sprite.map_id());
});
ImGui::EndGroup();
EndChild();
if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
SameLine();
if (Button(ICON_MD_CLOSE)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_DELETE)) {
sprite.set_deleted(true);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,88 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
#define YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
#include "imgui/imgui.h"
#include "app/editor/overworld_editor.h"
#include "app/zelda3/common.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
namespace editor {
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
ImVec2 canvas_p0, ImVec2 scrolling);
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool free_movement = false);
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool &is_dragging_entity,
zelda3::OverworldEntity *&dragged_entity,
zelda3::OverworldEntity *&current_entity,
bool free_movement = false);
bool DrawEntranceInserterPopup();
bool DrawOverworldEntrancePopup(zelda3::overworld::OverworldEntrance &entrance);
void DrawExitInserterPopup();
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit);
void DrawItemInsertPopup();
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item);
enum MyItemColumnID {
MyItemColumnID_ID,
MyItemColumnID_Name,
MyItemColumnID_Action,
MyItemColumnID_Quantity,
MyItemColumnID_Description
};
struct SpriteItem {
int id;
const char *name;
static const ImGuiTableSortSpecs *s_current_sort_specs;
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs,
std::vector<SpriteItem> &items) {
s_current_sort_specs =
sort_specs; // Store for access by the compare function.
if (items.size() > 1)
std::sort(items.begin(), items.end(), SpriteItem::CompareWithSortSpecs);
s_current_sort_specs = nullptr;
}
static bool CompareWithSortSpecs(const SpriteItem &a, const SpriteItem &b) {
for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) {
const ImGuiTableColumnSortSpecs *sort_spec =
&s_current_sort_specs->Specs[n];
int delta = 0;
switch (sort_spec->ColumnUserID) {
case MyItemColumnID_ID:
delta = (a.id - b.id);
break;
case MyItemColumnID_Name:
delta = strcmp(a.name + 2, b.name + 2);
break;
}
if (delta != 0)
return (sort_spec->SortDirection == ImGuiSortDirection_Ascending)
? delta < 0
: delta > 0;
}
return a.id < b.id; // Fallback
}
};
void DrawSpriteTable(std::function<void(int)> onSpriteSelect);
void DrawSpriteInserterPopup();
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite);
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H

View File

@@ -0,0 +1,157 @@
#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

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
#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 "imgui/imgui.h"
#include <imgui/imgui_internal.h>
#include <cmath> #include <cmath>
#include <unordered_map> #include <unordered_map>
@@ -12,18 +11,18 @@
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/core/common.h" #include "app/core/common.h"
#include "app/editor/context/entrance_context.h" #include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/context/gfx_context.h" #include "app/editor/graphics/palette_editor.h"
#include "app/editor/modules/gfx_group_editor.h" #include "app/editor/graphics/tile16_editor.h"
#include "app/editor/modules/palette_editor.h" #include "app/editor/overworld/entity.h"
#include "app/editor/modules/tile16_editor.h"
#include "app/editor/utils/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/gui/canvas.h" #include "app/gui/canvas.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/pipeline.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"
@@ -62,6 +61,28 @@ 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.
@@ -81,15 +102,20 @@ constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
class OverworldEditor : public Editor, class OverworldEditor : public Editor,
public SharedRom, public SharedRom,
public context::GfxContext, public context::GfxContext,
public context::EntranceContext, public EntranceContext,
public core::ExperimentFlags { public core::ExperimentFlags {
public: public:
OverworldEditor() { type_ = EditorType::kOverworld; }
void InitializeZeml();
absl::Status Update() final; absl::Status Update() final;
absl::Status Undo() { return absl::UnimplementedError("Undo"); } absl::Status Undo() { return absl::UnimplementedError("Undo"); }
absl::Status Redo() { return absl::UnimplementedError("Redo"); } absl::Status Redo() { return absl::UnimplementedError("Redo"); }
absl::Status Cut() { return absl::UnimplementedError("Cut"); } absl::Status Cut() { return absl::UnimplementedError("Cut"); }
absl::Status Copy() { return absl::UnimplementedError("Copy"); } absl::Status Copy() { return absl::UnimplementedError("Copy"); }
absl::Status Paste() { return absl::UnimplementedError("Paste"); } absl::Status Paste() { return absl::UnimplementedError("Paste"); }
absl::Status Find() { return absl::UnimplementedError("Find Unused Tiles"); }
auto overworld() { return &overworld_; } auto overworld() { return &overworld_; }
@@ -99,25 +125,6 @@ class OverworldEditor : public Editor,
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;
void Shutdown() {
for (auto& bmp : tile16_individual_) {
bmp.Cleanup();
}
for (auto& [i, bmp] : maps_bmp_) {
bmp.Cleanup();
}
for (auto& [i, bmp] : graphics_bin_) {
bmp.Cleanup();
}
for (auto& [i, bmp] : current_graphics_set_) {
bmp.Cleanup();
}
maps_bmp_.clear();
overworld_.Destroy();
all_gfx_loaded_ = false;
map_blockset_loaded_ = false;
}
/** /**
* @brief Load the Bitmap objects for each OverworldMap. * @brief Load the Bitmap objects for each OverworldMap.
* *
@@ -128,7 +135,6 @@ class OverworldEditor : public Editor,
absl::Status LoadGraphics(); absl::Status LoadGraphics();
private: private:
absl::Status UpdateOverworldEdit();
absl::Status UpdateFullscreenCanvas(); absl::Status UpdateFullscreenCanvas();
absl::Status DrawToolset(); absl::Status DrawToolset();
@@ -259,14 +265,15 @@ class OverworldEditor : public Editor,
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
zelda3::overworld::Overworld overworld_; zelda3::overworld::Overworld overworld_;
gui::Canvas ow_map_canvas_{ImVec2(0x200 * 8, 0x200 * 8), gui::Canvas ow_map_canvas_{"owMapCanvas", ImVec2(0x200 * 8, 0x200 * 8),
gui::CanvasGridSize::k64x64}; gui::CanvasGridSize::k64x64};
gui::Canvas current_gfx_canvas_{ImVec2(0x100 + 1, 0x10 * 0x40 + 1), gui::Canvas current_gfx_canvas_{"customGfxCanvas",
ImVec2(0x100 + 1, 0x10 * 0x40 + 1),
gui::CanvasGridSize::k32x32}; gui::CanvasGridSize::k32x32};
gui::Canvas blockset_canvas_{ImVec2(0x100 + 1, 0x2000 + 1), gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100 + 1, 0x2000 + 1),
gui::CanvasGridSize::k32x32}; gui::CanvasGridSize::k32x32};
gui::Canvas graphics_bin_canvas_{ gui::Canvas graphics_bin_canvas_{
ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1), "graphicsBinCanvas", ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
gui::CanvasGridSize::k16x16}; gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_; gui::Canvas properties_canvas_;
@@ -277,12 +284,13 @@ class OverworldEditor : public Editor,
gfx::Bitmap all_gfx_bmp; gfx::Bitmap all_gfx_bmp;
gfx::BitmapTable maps_bmp_; gfx::BitmapTable maps_bmp_;
gfx::BitmapTable graphics_bin_;
gfx::BitmapTable current_graphics_set_; gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_; gfx::BitmapTable sprite_previews_;
gfx::BitmapTable animated_maps_; gfx::BitmapTable animated_maps_;
OWBlockset refresh_blockset_;
gui::zeml::Node layout_node_;
absl::Status status_; absl::Status status_;
}; };
} // namespace editor } // namespace editor

View File

@@ -0,0 +1,85 @@
#include "imgui/imgui.h"
#include "absl/status/status.h"
#include "app/editor/utils/flags.h"
#include "app/editor/utils/keyboard_shortcuts.h"
#include "app/editor/settings_editor.h"
#include "app/editor/utils/flags.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::BeginChild;
using ImGui::BeginMenu;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
using ImGui::BeginTable;
using ImGui::Checkbox;
using ImGui::EndChild;
using ImGui::EndMenu;
using ImGui::EndTabBar;
using ImGui::EndTabItem;
using ImGui::EndTable;
using ImGui::TableHeader;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetBgColor;
using ImGui::TableSetColumnIndex;
using ImGui::TableSetupColumn;
using ImGui::Text;
absl::Status SettingsEditor::Update() {
if (BeginTabBar("Settings", ImGuiTabBarFlags_None)) {
if (BeginTabItem("General")) {
DrawGeneralSettings();
EndTabItem();
}
if (BeginTabItem("Keyboard Shortcuts")) {
EndTabItem();
}
EndTabBar();
}
return absl::OkStatus();
}
void SettingsEditor::DrawGeneralSettings() {
if (BeginTable("##SettingsTable", 2,
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
TableSetupColumn("Experiment Flags", ImGuiTableColumnFlags_WidthFixed,
250.0f);
TableSetupColumn("General Setting", ImGuiTableColumnFlags_WidthStretch,
0.0f);
TableHeadersRow();
TableNextColumn();
if (BeginChild("##GeneralSettingsStyleWrapper", ImVec2(0, 0),
ImGuiChildFlags_FrameStyle)) {
static FlagsMenu flags;
flags.Draw();
EndChild();
}
TableNextColumn();
if (BeginChild("##GeneralSettingsWrapper", ImVec2(0, 0),
ImGuiChildFlags_FrameStyle)) {
Text("TODO: Add some settings here");
EndChild();
}
EndTable();
}
}
absl::Status SettingsEditor::DrawKeyboardShortcuts() {
return absl::OkStatus();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,232 @@
#ifndef YAZE_APP_EDITOR_SETTINGS_EDITOR_H
#define YAZE_APP_EDITOR_SETTINGS_EDITOR_H
#include "imgui/imgui.h"
#include "absl/status/status.h"
#include "app/editor/utils/editor.h"
namespace yaze {
namespace app {
namespace editor {
// Simple representation for a tree
// (this is designed to be simple to understand for our demos, not to be
// efficient etc.)
struct ExampleTreeNode {
char Name[28];
ImGuiID UID = 0;
ExampleTreeNode* Parent = NULL;
ImVector<ExampleTreeNode*> Childs;
// Data
bool HasData = false; // All leaves have data
bool DataIsEnabled = false;
int DataInt = 128;
ImVec2 DataVec2 = ImVec2(0.0f, 3.141592f);
};
// Simple representation of struct metadata/serialization data.
// (this is a minimal version of what a typical advanced application may
// provide)
struct ExampleMemberInfo {
const char* Name;
ImGuiDataType DataType;
int DataCount;
int Offset;
};
// Metadata description of ExampleTreeNode struct.
static const ExampleMemberInfo ExampleTreeNodeMemberInfos[]{
{"Enabled", ImGuiDataType_Bool, 1,
offsetof(ExampleTreeNode, DataIsEnabled)},
{"MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataInt)},
{"MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataVec2)},
};
static ExampleTreeNode* ExampleTree_CreateNode(const char* name,
const ImGuiID uid,
ExampleTreeNode* parent) {
ExampleTreeNode* node = IM_NEW(ExampleTreeNode);
snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name);
node->UID = uid;
node->Parent = parent;
if (parent) parent->Childs.push_back(node);
return node;
}
// Create example tree data
static ExampleTreeNode* ExampleTree_CreateDemoTree() {
static const char* root_names[] = {"Apple", "Banana", "Cherry",
"Kiwi", "Mango", "Orange",
"Pineapple", "Strawberry", "Watermelon"};
char name_buf[32];
ImGuiID uid = 0;
ExampleTreeNode* node_L0 = ExampleTree_CreateNode("<ROOT>", ++uid, NULL);
for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * 2; idx_L0++) {
snprintf(name_buf, 32, "%s %d", root_names[idx_L0 / 2], idx_L0 % 2);
ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0);
const int number_of_childs = (int)strlen(node_L1->Name);
for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) {
snprintf(name_buf, 32, "Child %d", idx_L1);
ExampleTreeNode* node_L2 =
ExampleTree_CreateNode(name_buf, ++uid, node_L1);
node_L2->HasData = true;
if (idx_L1 == 0) {
snprintf(name_buf, 32, "Sub-child %d", 0);
ExampleTreeNode* node_L3 =
ExampleTree_CreateNode(name_buf, ++uid, node_L2);
node_L3->HasData = true;
}
}
}
return node_L0;
}
struct ExampleAppPropertyEditor {
ImGuiTextFilter Filter;
void Draw(ExampleTreeNode* root_node) {
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F,
ImGuiInputFlags_Tooltip);
ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf,
IM_ARRAYSIZE(Filter.InputBuf),
ImGuiInputTextFlags_EscapeClearsAll))
Filter.Build();
ImGui::PopItemFlag();
ImGuiTableFlags table_flags = ImGuiTableFlags_Resizable |
ImGuiTableFlags_ScrollY |
ImGuiTableFlags_RowBg;
if (ImGui::BeginTable("##split", 2, table_flags)) {
ImGui::TableSetupColumn("Object", ImGuiTableColumnFlags_WidthStretch,
1.0f);
ImGui::TableSetupColumn("Contents", ImGuiTableColumnFlags_WidthStretch,
2.0f); // Default twice larger
// ImGui::TableSetupScrollFreeze(0, 1);
// ImGui::TableHeadersRow();
for (ExampleTreeNode* node : root_node->Childs)
if (Filter.PassFilter(node->Name)) // Filter root node
DrawTreeNode(node);
ImGui::EndTable();
}
}
void DrawTreeNode(ExampleTreeNode* node) {
// Object tree node
ImGui::PushID((int)node->UID);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None;
tree_flags |=
ImGuiTreeNodeFlags_SpanAllColumns |
ImGuiTreeNodeFlags_AllowOverlap; // Highlight whole row for visibility
tree_flags |=
ImGuiTreeNodeFlags_OpenOnArrow |
ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we
// are likely to want to add
// selection afterwards
tree_flags |=
ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Left arrow support
bool node_open =
ImGui::TreeNodeEx("##Object", tree_flags, "%s", node->Name);
ImGui::TableSetColumnIndex(1);
ImGui::TextDisabled("UID: 0x%08X", node->UID);
// Display child and data
if (node_open)
for (ExampleTreeNode* child : node->Childs) DrawTreeNode(child);
if (node_open && node->HasData) {
// In a typical application, the structure description would be derived
// from a data-driven system.
// - We try to mimic this with our ExampleMemberInfo structure and the
// ExampleTreeNodeMemberInfos[] array.
// - Limits and some details are hard-coded to simplify the demo.
// - Text and Selectable are less high than framed widgets, using
// AlignTextToFramePadding() we add vertical spacing to make the
// selectable lines equal high.
for (const ExampleMemberInfo& field_desc : ExampleTreeNodeMemberInfos) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop | ImGuiItemFlags_NoNav,
true);
ImGui::Selectable(field_desc.Name, false,
ImGuiSelectableFlags_SpanAllColumns |
ImGuiSelectableFlags_AllowOverlap);
ImGui::PopItemFlag();
ImGui::TableSetColumnIndex(1);
ImGui::PushID(field_desc.Name);
void* field_ptr = (void*)(((unsigned char*)node) + field_desc.Offset);
switch (field_desc.DataType) {
case ImGuiDataType_Bool: {
IM_ASSERT(field_desc.DataCount == 1);
ImGui::Checkbox("##Editor", (bool*)field_ptr);
break;
}
case ImGuiDataType_S32: {
int v_min = INT_MIN, v_max = INT_MAX;
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::DragScalarN("##Editor", field_desc.DataType, field_ptr,
field_desc.DataCount, 1.0f, &v_min, &v_max);
break;
}
case ImGuiDataType_Float: {
float v_min = 0.0f, v_max = 1.0f;
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::SliderScalarN("##Editor", field_desc.DataType, field_ptr,
field_desc.DataCount, &v_min, &v_max);
break;
}
}
ImGui::PopID();
}
}
if (node_open) ImGui::TreePop();
ImGui::PopID();
}
};
// Demonstrate creating a simple property editor.
static void ShowExampleAppPropertyEditor(bool* p_open) {
ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Example: Property editor", p_open)) {
ImGui::End();
return;
}
static ExampleAppPropertyEditor property_editor;
static ExampleTreeNode* tree_data = ExampleTree_CreateDemoTree();
property_editor.Draw(tree_data);
ImGui::End();
}
class SettingsEditor : public Editor {
public:
SettingsEditor() : Editor() { type_ = EditorType::kSettings; }
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
private:
void DrawGeneralSettings();
absl::Status DrawKeyboardShortcuts();
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SETTINGS_EDITOR_H_

View File

@@ -0,0 +1,278 @@
#include "sprite_editor.h"
#include "app/core/platform/file_dialog.h"
#include "app/editor/sprite/zsprite.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::EndTable;
using ImGui::Selectable;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
absl::Status SpriteEditor::Update() {
if (rom()->is_loaded() && !sheets_loaded_) {
// Load the values for current_sheets_ array
sheets_loaded_ = true;
}
if (ImGui::BeginTabBar("##SpriteEditorTabs")) {
if (ImGui::BeginTabItem("Vanilla")) {
DrawVanillaSpriteEditor();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Custom")) {
DrawCustomSprites();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
return status_.ok() ? absl::OkStatus() : status_;
}
void SpriteEditor::DrawVanillaSpriteEditor() {
if (ImGui::BeginTable("##SpriteCanvasTable", 3, ImGuiTableFlags_Resizable,
ImVec2(0, 0))) {
TableSetupColumn("Sprites List", ImGuiTableColumnFlags_WidthFixed, 256);
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
TableNextColumn();
DrawSpritesList();
TableNextColumn();
static int next_tab_id = 0;
if (ImGui::BeginTabBar("SpriteTabBar", kSpriteTabBarFlags)) {
if (ImGui::TabItemButton(ICON_MD_ADD, kSpriteTabBarFlags)) {
if (std::find(active_sprites_.begin(), active_sprites_.end(),
current_sprite_id_) != active_sprites_.end()) {
// Room is already open
next_tab_id++;
}
active_sprites_.push_back(next_tab_id++); // Add new tab
}
// Submit our regular tabs
for (int n = 0; n < active_sprites_.Size;) {
bool open = true;
if (active_sprites_[n] > sizeof(core::kSpriteDefaultNames) / 4) {
active_sprites_.erase(active_sprites_.Data + n);
continue;
}
if (ImGui::BeginTabItem(
core::kSpriteDefaultNames[active_sprites_[n]].data(), &open,
ImGuiTabItemFlags_None)) {
DrawSpriteCanvas();
ImGui::EndTabItem();
}
if (!open)
active_sprites_.erase(active_sprites_.Data + n);
else
n++;
}
ImGui::EndTabBar();
}
TableNextColumn();
if (sheets_loaded_) {
DrawCurrentSheets();
}
ImGui::EndTable();
}
}
void SpriteEditor::DrawSpriteCanvas() {
static bool flip_x = false;
static bool flip_y = false;
if (ImGui::BeginChild(gui::GetID("##SpriteCanvas"),
ImGui::GetContentRegionAvail(), true)) {
sprite_canvas_.DrawBackground();
sprite_canvas_.DrawContextMenu();
sprite_canvas_.DrawGrid();
sprite_canvas_.DrawOverlay();
// Draw a table with OAM configuration
// X, Y, Tile, Palette, Priority, Flip X, Flip Y
if (ImGui::BeginTable("##OAMTable", 7, ImGuiTableFlags_Resizable,
ImVec2(0, 0))) {
TableSetupColumn("X", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Y", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Tile", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Palette", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Priority", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Flip X", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Flip Y", ImGuiTableColumnFlags_WidthStretch);
TableHeadersRow();
TableNextRow();
TableNextColumn();
gui::InputHexWord("", &oam_config_.x);
TableNextColumn();
gui::InputHexWord("", &oam_config_.y);
TableNextColumn();
gui::InputHexByte("", &oam_config_.tile);
TableNextColumn();
gui::InputHexByte("", &oam_config_.palette);
TableNextColumn();
gui::InputHexByte("", &oam_config_.priority);
TableNextColumn();
if (ImGui::Checkbox("##XFlip", &flip_x)) {
oam_config_.flip_x = flip_x;
}
TableNextColumn();
if (ImGui::Checkbox("##YFlip", &flip_y)) {
oam_config_.flip_y = flip_y;
}
ImGui::EndTable();
}
DrawAnimationFrames();
DrawCustomSpritesMetadata();
ImGui::EndChild();
}
}
void SpriteEditor::DrawCurrentSheets() {
if (ImGui::BeginChild(gui::GetID("sheet_label"),
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
ImGuiWindowFlags_NoDecoration)) {
for (int i = 0; i < 8; i++) {
std::string sheet_label = absl::StrFormat("Sheet %d", i);
gui::InputHexByte(sheet_label.c_str(), &current_sheets_[i]);
if (i % 2 == 0) ImGui::SameLine();
}
graphics_sheet_canvas_.DrawBackground();
graphics_sheet_canvas_.DrawContextMenu();
graphics_sheet_canvas_.DrawTileSelector(32);
for (int i = 0; i < 8; i++) {
graphics_sheet_canvas_.DrawBitmap(
rom()->bitmap_manager()[current_sheets_[i]], 1, (i * 0x40) + 1, 2);
}
graphics_sheet_canvas_.DrawGrid();
graphics_sheet_canvas_.DrawOverlay();
ImGui::EndChild();
}
}
void SpriteEditor::DrawSpritesList() {
if (ImGui::BeginChild(gui::GetID("##SpritesList"),
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
ImGuiWindowFlags_NoDecoration)) {
int i = 0;
for (const auto each_sprite_name : core::kSpriteDefaultNames) {
rom()->resource_label()->SelectableLabelWithNameEdit(
current_sprite_id_ == i, "Sprite Names", core::UppercaseHexByte(i),
core::kSpriteDefaultNames[i].data());
if (ImGui::IsItemClicked()) {
current_sprite_id_ = i;
if (!active_sprites_.contains(i)) {
active_sprites_.push_back(i);
}
}
i++;
}
ImGui::EndChild();
}
}
void SpriteEditor::DrawAnimationFrames() {
if (ImGui::Button("Add Frame")) {
// Add a new frame
}
if (ImGui::Button("Remove Frame")) {
// Remove the current frame
}
}
void SpriteEditor::DrawCustomSprites() {
if (BeginTable("##CustomSpritesTable", 3,
ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders |
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable,
ImVec2(0, 0))) {
TableSetupColumn("Metadata", ImGuiTableColumnFlags_WidthFixed, 256);
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthFixed, 256);
TableSetupColumn("TIlesheets", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
TableNextColumn();
Separator();
DrawCustomSpritesMetadata();
TableNextColumn();
DrawSpriteCanvas();
TableNextColumn();
DrawCurrentSheets();
EndTable();
}
}
void SpriteEditor::DrawCustomSpritesMetadata() {
// ZSprite Maker format open file dialog
if (ImGui::Button("Open ZSprite")) {
// Open ZSprite file
std::string file_path = FileDialogWrapper::ShowOpenFileDialog();
if (!file_path.empty()) {
zsprite::ZSprite zsprite;
status_ = zsprite.Load(file_path);
if (status_.ok()) {
custom_sprites_.push_back(zsprite);
}
}
}
for (const auto custom_sprite : custom_sprites_) {
Selectable("%s", custom_sprite.sprName.c_str());
if (ImGui::IsItemClicked()) {
current_sprite_id_ = 256 + stoi(custom_sprite.property_sprid.Text);
if (!active_sprites_.contains(current_sprite_id_)) {
active_sprites_.push_back(current_sprite_id_);
}
}
Separator();
}
for (const auto custom_sprite : custom_sprites_) {
// Draw the custom sprite metadata
Text("Sprite ID: %s", custom_sprite.property_sprid.Text.c_str());
Text("Sprite Name: %s", custom_sprite.property_sprname.Text.c_str());
Text("Sprite Palette: %s", custom_sprite.property_palette.Text.c_str());
Separator();
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,117 @@
#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H
#include "absl/status/status.h"
#include "app/editor/sprite/zsprite.h"
#include "app/editor/utils/editor.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
constexpr ImGuiTabItemFlags kSpriteTabFlags =
ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip;
constexpr ImGuiTabBarFlags kSpriteTabBarFlags =
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton;
constexpr ImGuiTableFlags kSpriteTableFlags =
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV;
/**
* @class SpriteEditor
* @brief Allows the user to edit sprites.
*
* This class provides functionality for updating the sprite editor, drawing the
* editor table, drawing the sprite canvas, and drawing the current sheets.
*/
class SpriteEditor : public SharedRom, public Editor {
public:
SpriteEditor() { type_ = EditorType::kSprite; }
/**
* @brief Updates the sprite editor.
*
* @return An absl::Status indicating the success or failure of the update.
*/
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
private:
void DrawVanillaSpriteEditor();
/**
* @brief Draws the sprites list.
*/
void DrawSpritesList();
/**
* @brief Draws the sprite canvas.
*/
void DrawSpriteCanvas();
/**
* @brief Draws the current sheets.
*/
void DrawCurrentSheets();
void DrawCustomSprites();
void DrawCustomSpritesMetadata();
/**
* @brief Draws the animation frames manager.
*/
void DrawAnimationFrames();
ImVector<int> active_sprites_; /**< Active sprites. */
int current_sprite_id_; /**< Current sprite ID. */
uint8_t current_sheets_[8] = {0x00, 0x0A, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00};
bool sheets_loaded_ =
false; /**< Flag indicating whether the sheets are loaded or not. */
// OAM Configuration
struct OAMConfig {
uint16_t x; /**< X offset. */
uint16_t y; /**< Y offset. */
uint8_t tile; /**< Tile number. */
uint8_t palette; /**< Palette number. */
uint8_t priority; /**< Priority. */
bool flip_x; /**< Flip X. */
bool flip_y; /**< Flip Y. */
};
OAMConfig oam_config_; /**< OAM configuration. */
gui::Bitmap oam_bitmap_; /**< OAM bitmap. */
gui::Canvas sprite_canvas_{
"SpriteCanvas", ImVec2(0x200, 0x200),
gui::CanvasGridSize::k32x32}; /**< Sprite canvas. */
gui::Canvas graphics_sheet_canvas_{
"GraphicsSheetCanvas", ImVec2(0x80 * 2 + 2, 0x40 * 8 + 2),
gui::CanvasGridSize::k16x16}; /**< Graphics sheet canvas. */
std::vector<zsprite::ZSprite> custom_sprites_; /**< Sprites. */
absl::Status status_; /**< Status. */
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H

View File

@@ -0,0 +1,396 @@
#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#include "imgui/imgui.h"
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "app/gfx/snes_tile.h"
namespace yaze {
namespace app {
namespace editor {
/**
* @brief Namespace for the ZSprite format from Zarby's ZSpriteMaker.
*/
namespace zsprite {
struct OamTile {
OamTile(uint8_t x, uint8_t y, bool mx, bool my, uint16_t id, uint8_t pal,
bool s, uint8_t p)
: x(x),
y(y),
mirror_x(mx),
mirror_y(my),
id(id),
palette(pal),
size(s),
priority(p) {}
uint8_t x;
uint8_t y;
bool mirror_x;
bool mirror_y;
uint16_t id;
uint8_t palette;
bool size;
uint8_t priority;
uint8_t z;
};
struct AnimationGroup {
AnimationGroup() = default;
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) {}
std::string frame_name;
uint8_t frame_start;
uint8_t frame_end;
uint8_t frame_speed;
std::vector<OamTile> Tiles;
};
struct UserRoutine {
UserRoutine(std::string n, std::string c) : name(n), code(c) {}
std::string name;
std::string code;
int Count;
};
struct SubEditor {
std::vector<AnimationGroup> Frames;
std::vector<UserRoutine> user_routines;
};
struct SpriteProperty {
bool IsChecked;
std::string Text;
};
struct ZSprite {
public:
absl::Status Load(const std::string& filename) {
std::ifstream fs(filename, std::ios::binary);
if (!fs.is_open()) {
return absl::NotFoundError("File not found");
}
std::vector<char> buffer(std::istreambuf_iterator<char>(fs), {});
int animation_count = *reinterpret_cast<int32_t*>(&buffer[0]);
int offset = sizeof(int);
for (int i = 0; i < animation_count; i++) {
std::string aname = std::string(&buffer[offset]);
offset += aname.size() + 1;
uint8_t afs = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
uint8_t afe = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
uint8_t afspeed = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
animations.push_back(AnimationGroup(afs, afe, afspeed, aname));
}
// RefreshAnimations();
int frame_count = *reinterpret_cast<int32_t*>(&buffer[offset]);
offset += sizeof(int);
for (int i = 0; i < frame_count; i++) {
// editor.Frames[i] = new Frame();
editor.Frames.emplace_back();
// editor.AddUndo(i);
int tCount = *reinterpret_cast<int*>(&buffer[offset]);
offset += sizeof(int);
for (int j = 0; j < tCount; j++) {
ushort tid = *reinterpret_cast<ushort*>(&buffer[offset]);
offset += sizeof(ushort);
uint8_t tpal = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
bool tmx = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
bool tmy = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
uint8_t tprior = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
bool tsize = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
uint8_t tx = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
uint8_t ty = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
uint8_t tz = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
OamTile to(tx, ty, tmx, tmy, tid, tpal, tsize, tprior);
to.z = tz;
editor.Frames[i].Tiles.push_back(to);
}
}
// all sprites properties
property_blockable.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_canfall.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_collisionlayer.IsChecked =
*reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_customdeath.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_damagesound.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_deflectarrows.IsChecked =
*reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_deflectprojectiles.IsChecked =
*reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_fast.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_harmless.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_impervious.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_imperviousarrow.IsChecked =
*reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_imperviousmelee.IsChecked =
*reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_interaction.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_isboss.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_persist.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_shadow.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_smallshadow.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_statis.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_statue.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_watersprite.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
offset += sizeof(bool);
property_prize.Text =
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
offset += sizeof(uint8_t);
property_palette.Text =
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
offset += sizeof(uint8_t);
property_oamnbr.Text =
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
offset += sizeof(uint8_t);
property_hitbox.Text =
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
offset += sizeof(uint8_t);
property_health.Text =
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
offset += sizeof(uint8_t);
property_damage.Text =
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
offset += sizeof(uint8_t);
if (offset != buffer.size()) {
property_sprname.Text = std::string(&buffer[offset]);
offset += property_sprname.Text.size() + 1;
int actionL = buffer[offset];
offset += sizeof(int);
for (int i = 0; i < actionL; i++) {
std::string a = std::string(&buffer[offset]);
offset += a.size() + 1;
std::string b = std::string(&buffer[offset]);
offset += b.size() + 1;
userRoutines.push_back(UserRoutine(a, b));
}
}
if (offset != buffer.size()) {
property_sprid.Text = std::string(&buffer[offset]);
fs.close();
}
// UpdateUserRoutines();
// userroutinesListbox.SelectedIndex = 0;
// RefreshScreen();
return absl::OkStatus();
}
absl::Status Save(const std::string& filename) {
std::ofstream fs(filename, std::ios::binary);
if (fs.is_open()) {
// Write data to the file
fs.write(reinterpret_cast<const char*>(animations.size()), sizeof(int));
for (const AnimationGroup& anim : animations) {
fs.write(anim.frame_name.c_str(), anim.frame_name.size() + 1);
fs.write(reinterpret_cast<const char*>(&anim.frame_start),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&anim.frame_end),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&anim.frame_speed),
sizeof(uint8_t));
}
fs.write(reinterpret_cast<const char*>(editor.Frames.size()),
sizeof(int));
for (int i = 0; i < editor.Frames.size(); i++) {
fs.write(reinterpret_cast<const char*>(editor.Frames[i].Tiles.size()),
sizeof(int));
for (int j = 0; j < editor.Frames[i].Tiles.size(); j++) {
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].id),
sizeof(ushort));
fs.write(
reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].palette),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(
&editor.Frames[i].Tiles[j].mirror_x),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(
&editor.Frames[i].Tiles[j].mirror_y),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(
&editor.Frames[i].Tiles[j].priority),
sizeof(uint8_t));
fs.write(
reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].size),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].x),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].y),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].z),
sizeof(uint8_t));
}
}
// Write other properties
fs.write(reinterpret_cast<const char*>(&property_blockable.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_canfall.IsChecked),
sizeof(bool));
fs.write(
reinterpret_cast<const char*>(&property_collisionlayer.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_customdeath.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_damagesound.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_deflectarrows.IsChecked),
sizeof(bool));
fs.write(
reinterpret_cast<const char*>(&property_deflectprojectiles.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_fast.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_harmless.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_impervious.IsChecked),
sizeof(bool));
fs.write(
reinterpret_cast<const char*>(&property_imperviousarrow.IsChecked),
sizeof(bool));
fs.write(
reinterpret_cast<const char*>(&property_imperviousmelee.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_interaction.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_isboss.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_persist.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_shadow.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_smallshadow.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_statis.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_statue.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_watersprite.IsChecked),
sizeof(bool));
fs.write(reinterpret_cast<const char*>(&property_prize.Text),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&property_palette.Text),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&property_oamnbr.Text),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&property_hitbox.Text),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&property_health.Text),
sizeof(uint8_t));
fs.write(reinterpret_cast<const char*>(&property_damage.Text),
sizeof(uint8_t));
fs.write(sprName.c_str(), sprName.size() + 1);
fs.write(reinterpret_cast<const char*>(userRoutines.size()), sizeof(int));
for (const UserRoutine& userR : userRoutines) {
fs.write(userR.name.c_str(), userR.name.size() + 1);
fs.write(userR.code.c_str(), userR.code.size() + 1);
}
fs.write(reinterpret_cast<const char*>(&property_sprid.Text),
sizeof(property_sprid.Text));
fs.close();
}
return absl::OkStatus();
}
std::string sprName;
std::vector<AnimationGroup> animations;
std::vector<UserRoutine> userRoutines;
SubEditor editor;
SpriteProperty property_blockable;
SpriteProperty property_canfall;
SpriteProperty property_collisionlayer;
SpriteProperty property_customdeath;
SpriteProperty property_damagesound;
SpriteProperty property_deflectarrows;
SpriteProperty property_deflectprojectiles;
SpriteProperty property_fast;
SpriteProperty property_harmless;
SpriteProperty property_impervious;
SpriteProperty property_imperviousarrow;
SpriteProperty property_imperviousmelee;
SpriteProperty property_interaction;
SpriteProperty property_isboss;
SpriteProperty property_persist;
SpriteProperty property_shadow;
SpriteProperty property_smallshadow;
SpriteProperty property_statis;
SpriteProperty property_statue;
SpriteProperty property_watersprite;
SpriteProperty property_sprname;
SpriteProperty property_prize;
SpriteProperty property_palette;
SpriteProperty property_oamnbr;
SpriteProperty property_hitbox;
SpriteProperty property_health;
SpriteProperty property_damage;
SpriteProperty property_sprid;
};
} // namespace zsprite
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SPRITE_ZSPRITE_H

View File

@@ -1,69 +0,0 @@
#include "sprite_editor.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::Button;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
absl::Status SpriteEditor::Update() {
if (rom()->is_loaded() && !sheets_loaded_) {
// Load the values for current_sheets_ array
sheets_loaded_ = true;
}
// if (ImGui::BeginTable({"Canvas", "Graphics"}, 2, nullptr, ImVec2(0, 0))) {
// TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
// ImGui::GetContentRegionAvail().x);
// TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
// TableHeadersRow();
// TableNextRow();
// TableNextColumn();
// DrawSpriteCanvas();
// TableNextColumn();
// if (sheets_loaded_) {
// DrawCurrentSheets();
// }
// ImGui::EndTable();
// }
return absl::OkStatus();
}
void SpriteEditor::DrawEditorTable() {}
void SpriteEditor::DrawSpriteCanvas() {}
void SpriteEditor::DrawCurrentSheets() {
static gui::Canvas graphics_sheet_canvas;
for (int i = 0; i < 8; i++) {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)7);
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar)) {
graphics_sheet_canvas.DrawBackground(ImVec2(0x200 * 8, 0x200 * 8));
ImGui::PopStyleVar(2);
graphics_sheet_canvas.DrawContextMenu();
graphics_sheet_canvas.DrawBitmap(
*rom()->bitmap_manager()[current_sheets_[i]], 2, 2);
graphics_sheet_canvas.DrawGrid(64.0f);
graphics_sheet_canvas.DrawOverlay();
}
ImGui::EndChild();
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,53 +0,0 @@
#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H
#include "absl/status/status.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
/**
* @class SpriteEditor
* @brief Allows the user to edit sprites.
*
* This class provides functionality for updating the sprite editor, drawing the
* editor table, drawing the sprite canvas, and drawing the current sheets.
*/
class SpriteEditor : public SharedRom {
public:
/**
* @brief Updates the sprite editor.
*
* @return An absl::Status indicating the success or failure of the update.
*/
absl::Status Update();
private:
/**
* @brief Draws the editor table.
*/
void DrawEditorTable();
/**
* @brief Draws the sprite canvas.
*/
void DrawSpriteCanvas();
/**
* @brief Draws the current sheets.
*/
void DrawCurrentSheets();
uint8_t current_sheets_[8]; /**< Array to store the current sheets. */
bool sheets_loaded_ =
false; /**< Flag indicating whether the sheets are loaded or not. */
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H

View File

@@ -12,6 +12,24 @@ namespace app {
*/ */
namespace editor { namespace editor {
enum class EditorType {
kAssembly,
kDungeon,
kGraphics,
kMusic,
kOverworld,
kPalette,
kScreen,
kSprite,
kMessage,
kSettings,
};
constexpr std::array<const char*, 10> kEditorNames = {
"Assembly", "Dungeon", "Graphics", "Music", "Overworld",
"Palette", "Screen", "Sprite", "Message", "Settings",
};
/** /**
* @class Editor * @class Editor
* @brief Interface for editor classes. * @brief Interface for editor classes.
@@ -31,6 +49,13 @@ class Editor {
virtual absl::Status Redo() = 0; virtual absl::Status Redo() = 0;
virtual absl::Status Update() = 0; virtual absl::Status Update() = 0;
virtual absl::Status Find() = 0;
EditorType type() const { return type_; }
protected:
EditorType type_;
}; };
} // namespace editor } // namespace editor

View File

@@ -0,0 +1,68 @@
#ifndef YAZE_APP_EDITOR_UTILS_FLAGS_H
#define YAZE_APP_EDITOR_UTILS_FLAGS_H
#include "imgui/imgui.h"
#include "core/common.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::BeginMenu;
using ImGui::Checkbox;
using ImGui::EndMenu;
using ImGui::MenuItem;
using ImGui::Separator;
struct FlagsMenu : public core::ExperimentFlags {
void Draw() {
if (BeginMenu("Overworld Flags")) {
Checkbox("Enable Overworld Sprites",
&mutable_flags()->overworld.kDrawOverworldSprites);
Separator();
Checkbox("Save Overworld Maps",
&mutable_flags()->overworld.kSaveOverworldMaps);
Checkbox("Save Overworld Entrances",
&mutable_flags()->overworld.kSaveOverworldEntrances);
Checkbox("Save Overworld Exits",
&mutable_flags()->overworld.kSaveOverworldExits);
Checkbox("Save Overworld Items",
&mutable_flags()->overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&mutable_flags()->overworld.kSaveOverworldProperties);
ImGui::EndMenu();
}
if (BeginMenu("Dungeon Flags")) {
Checkbox("Draw Dungeon Room Graphics",
&mutable_flags()->kDrawDungeonRoomGraphics);
Separator();
Checkbox("Save Dungeon Maps", &mutable_flags()->kSaveDungeonMaps);
ImGui::EndMenu();
}
if (BeginMenu("Emulator Flags")) {
Checkbox("Load Audio Device", &mutable_flags()->kLoadAudioDevice);
ImGui::EndMenu();
}
Checkbox("Use built-in file dialog",
&mutable_flags()->kNewFileDialogWrapper);
Checkbox("Enable Console Logging", &mutable_flags()->kLogToConsole);
Checkbox("Enable Texture Streaming",
&mutable_flags()->kLoadTexturesAsStreaming);
Checkbox("Use Bitmap Manager", &mutable_flags()->kUseBitmapManager);
Checkbox("Log Instructions to Debugger",
&mutable_flags()->kLogInstructions);
Checkbox("Save All Palettes", &mutable_flags()->kSaveAllPalettes);
Checkbox("Save Gfx Groups", &mutable_flags()->kSaveGfxGroups);
Checkbox("Use New ImGui Input", &mutable_flags()->kUseNewImGuiInput);
}
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_UTILS_FLAGS_H_

View File

@@ -1,17 +1,16 @@
#include "app/editor/context/gfx_context.h" #include "app/editor/utils/gfx_context.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <cmath> #include <cmath>
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h" #include "app/editor/utils/editor.h"
#include "app/editor/modules/palette_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/icons.h"
#include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {

View File

@@ -1,19 +1,17 @@
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H #ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H #define YAZE_APP_EDITOR_VRAM_CONTEXT_H
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include "app/editor/utils/editor.h" #include "app/editor/utils/editor.h"
#include "app/editor/modules/palette_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/icons.h"
#include "app/gui/pipeline.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {
@@ -25,9 +23,6 @@ namespace context {
* @brief Shared graphical context across editors. * @brief Shared graphical context across editors.
*/ */
class GfxContext { class GfxContext {
public:
absl::Status Update();
protected: protected:
// Palettesets for the tile16 individual tiles // Palettesets for the tile16 individual tiles
static std::unordered_map<uint8_t, gfx::Paletteset> palettesets_; static std::unordered_map<uint8_t, gfx::Paletteset> palettesets_;

View File

@@ -0,0 +1,25 @@
#ifndef YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
#define YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
#include "imgui/imgui.h"
namespace yaze {
namespace app {
namespace editor {
struct KeyboardShortcuts {
enum class ShortcutType {
kCut,
kCopy,
kPaste,
kUndo,
kRedo,
kFind,
};
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H_

View File

@@ -0,0 +1,64 @@
#ifndef YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
#define YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
namespace yaze {
namespace app {
namespace editor {
class RecentFilesManager {
public:
RecentFilesManager(const std::string& filename) : filename_(filename) {}
void AddFile(const std::string& filePath) {
// Add a file to the list, avoiding duplicates
auto it = std::find(recentFiles_.begin(), recentFiles_.end(), filePath);
if (it == recentFiles_.end()) {
recentFiles_.push_back(filePath);
}
}
void Save() {
std::ofstream file(filename_);
if (!file.is_open()) {
return; // Handle the error appropriately
}
for (const auto& filePath : recentFiles_) {
file << filePath << std::endl;
}
}
void Load() {
std::ifstream file(filename_);
if (!file.is_open()) {
return; // Handle the error appropriately
}
recentFiles_.clear();
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
recentFiles_.push_back(line);
}
}
}
const std::vector<std::string>& GetRecentFiles() const {
return recentFiles_;
}
private:
std::string filename_;
std::vector<std::string> recentFiles_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_UTILS_RECENT_FILES_H

View File

@@ -0,0 +1,23 @@
add_executable(
yaze_emu
app/rom.cc
app/emu/debug/emu.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}
)
target_include_directories(
yaze_emu PUBLIC
lib/
app/
${CMAKE_SOURCE_DIR}/src/
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
target_link_libraries(yaze_emu PUBLIC ${ABSL_TARGETS} ${SDL_TARGETS} ${PNG_LIBRARIES} ${CMAKE_DL_LIBS} ImGui)

View File

@@ -1,5 +1,7 @@
#include "app/emu/audio/apu.h" #include "app/emu/audio/apu.h"
#include <SDL.h>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
@@ -15,127 +17,191 @@ namespace app {
namespace emu { namespace emu {
namespace audio { namespace audio {
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312 * 50.0);
static const uint8_t bootRom[0x40] = {
0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa,
0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19,
0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb,
0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e,
0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4,
0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff};
void Apu::Init() { void Apu::Init() {
// Set the clock frequency ram.resize(0x10000);
clock_.SetFrequency(kApuClockSpeed); for (int i = 0; i < 0x10000; i++) {
ram[i] = 0;
// Initialize Digital Signal Processor Callbacks }
dsp_.SetSampleFetcher([this](uint16_t address) -> uint8_t { // Copy the boot rom into the ram at ffc0
return this->FetchSampleFromRam(address); for (int i = 0; i < 0x40; i++) {
}); ram[0xffc0 + i] = bootRom[i];
}
dsp_.SetSamplePusher(
[this](int16_t sample) { this->PushToAudioBuffer(sample); });
} }
void Apu::Reset() { void Apu::Reset() {
clock_.ResetAccumulatedTime(); spc700_.Reset(true);
spc700_.Reset();
dsp_.Reset(); dsp_.Reset();
for (int i = 0; i < 0x10000; i++) {
ram[i] = 0;
}
// Copy the boot rom into the ram at ffc0
for (int i = 0; i < 0x40; i++) {
ram[0xffc0 + i] = bootRom[i];
}
rom_readable_ = true;
dsp_adr_ = 0;
cycles_ = 0;
std::fill(in_ports_.begin(), in_ports_.end(), 0);
std::fill(out_ports_.begin(), out_ports_.end(), 0);
for (int i = 0; i < 3; i++) {
timer_[i].cycles = 0;
timer_[i].divider = 0;
timer_[i].target = 0;
timer_[i].counter = 0;
timer_[i].enabled = false;
}
} }
void Apu::Update() { void Apu::RunCycles(uint64_t cycles) {
auto cycles_to_run = clock_.GetCycleCount(); uint64_t sync_to =
(uint64_t)cycles *
(memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster);
for (auto i = 0; i < cycles_to_run; ++i) { while (cycles_ < sync_to) {
// Update the Apu spc700_.RunOpcode();
UpdateChannelSettings();
// Update the SPC700
uint8_t opcode = spc700_.read(spc700_.PC);
spc700_.ExecuteInstructions(opcode);
spc700_.PC++;
} }
ProcessSamples();
} }
void Apu::Notify(uint32_t address, uint8_t data) { void Apu::Cycle() {
if (address < 0x2140 || address > 0x2143) { if ((cycles_ & 0x1f) == 0) {
return; // every 32 cycles
dsp_.Cycle();
} }
auto offset = address - 0x2140;
spc700_.write(offset, data);
// HACK - This is a temporary solution to get the Apu to play audio // handle timers
ports_[address - 0x2140] = data; for (int i = 0; i < 3; i++) {
switch (address) { if (timer_[i].cycles == 0) {
case 0x2140: timer_[i].cycles = i == 2 ? 16 : 128;
if (data == BEGIN_SIGNAL) { if (timer_[i].enabled) {
SignalReady(); timer_[i].divider++;
if (timer_[i].divider == timer_[i].target) {
timer_[i].divider = 0;
timer_[i].counter++;
timer_[i].counter &= 0xf;
}
} }
break; }
case 0x2141: timer_[i].cycles--;
// TODO: Handle data byte transfer here
break;
case 0x2142:
// TODO: Handle the setup of destination address
break;
case 0x2143:
// TODO: Handle additional communication/commands
break;
} }
cycles_++;
} }
void Apu::ProcessSamples() { uint8_t Apu::Read(uint16_t adr) {
// Fetch sample data from AudioRam switch (adr) {
// Iterate over all voices case 0xf0:
for (uint8_t voice_num = 0; voice_num < 8; voice_num++) { case 0xf1:
// Fetch the sample data for the current voice from AudioRam case 0xfa:
uint8_t sample = FetchSampleForVoice(voice_num); case 0xfb:
case 0xfc: {
// Process the sample through DSP return 0;
int16_t processed_sample = dsp_.ProcessSample(voice_num, sample); }
case 0xf2: {
// Add the processed sample to the audio buffer return dsp_adr_;
audio_samples_.push_back(processed_sample); }
case 0xf3: {
return dsp_.Read(dsp_adr_ & 0x7f);
}
case 0xf4:
case 0xf5:
case 0xf6:
case 0xf7:
case 0xf8:
case 0xf9: {
return in_ports_[adr - 0xf4];
}
case 0xfd:
case 0xfe:
case 0xff: {
uint8_t ret = timer_[adr - 0xfd].counter;
timer_[adr - 0xfd].counter = 0;
return ret;
}
} }
} if (rom_readable_ && adr >= 0xffc0) {
return bootRom[adr - 0xffc0];
uint8_t Apu::FetchSampleForVoice(uint8_t voice_num) {
uint16_t address = CalculateAddressForVoice(voice_num);
return aram_.read(address);
}
uint16_t Apu::CalculateAddressForVoice(uint8_t voice_num) {
// TODO: Calculate the address for the specified voice
return voice_num;
}
int16_t Apu::GetNextSample() {
if (!audio_samples_.empty()) {
int16_t sample = audio_samples_.front();
audio_samples_.erase(audio_samples_.begin());
return sample;
} }
return 0; // TODO: Return the last sample instead of 0. return ram[adr];
} }
const std::vector<int16_t>& Apu::GetAudioSamples() const { void Apu::Write(uint16_t adr, uint8_t val) {
return audio_samples_; switch (adr) {
case 0xf0: {
break; // test register
}
case 0xf1: {
for (int i = 0; i < 3; i++) {
if (!timer_[i].enabled && (val & (1 << i))) {
timer_[i].divider = 0;
timer_[i].counter = 0;
}
timer_[i].enabled = val & (1 << i);
}
if (val & 0x10) {
in_ports_[0] = 0;
in_ports_[1] = 0;
}
if (val & 0x20) {
in_ports_[2] = 0;
in_ports_[3] = 0;
}
rom_readable_ = val & 0x80;
break;
}
case 0xf2: {
dsp_adr_ = val;
break;
}
case 0xf3: {
if (dsp_adr_ < 0x80) dsp_.Write(dsp_adr_, val);
break;
}
case 0xf4:
case 0xf5:
case 0xf6:
case 0xf7: {
out_ports_[adr - 0xf4] = val;
break;
}
case 0xf8:
case 0xf9: {
in_ports_[adr - 0xf4] = val;
break;
}
case 0xfa:
case 0xfb:
case 0xfc: {
timer_[adr - 0xfa].target = val;
break;
}
}
ram[adr] = val;
} }
void Apu::UpdateChannelSettings() { uint8_t Apu::SpcRead(uint16_t adr) {
// TODO: Implement this method to update the channel settings. Cycle();
return Read(adr);
} }
int16_t Apu::GenerateSample(int channel) { void Apu::SpcWrite(uint16_t adr, uint8_t val) {
// TODO: Implement this method to generate a sample for the specified channel. Cycle();
Write(adr, val);
} }
void Apu::ApplyEnvelope(int channel) { void Apu::SpcIdle(bool waiting) { Cycle(); }
// TODO: Implement this method to apply an envelope to the specified channel.
}
uint8_t Apu::ReadDspMemory(uint16_t address) { } // namespace audio
return dsp_.ReadGlobalReg(address);
}
void Apu::WriteDspMemory(uint16_t address, uint8_t value) {
dsp_.WriteGlobalReg(address, value);
}
} // namespace audio
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -7,7 +7,6 @@
#include "app/emu/audio/dsp.h" #include "app/emu/audio/dsp.h"
#include "app/emu/audio/spc700.h" #include "app/emu/audio/spc700.h"
#include "app/emu/cpu/clock.h"
#include "app/emu/memory/memory.h" #include "app/emu/memory/memory.h"
namespace yaze { namespace yaze {
@@ -17,15 +16,13 @@ namespace audio {
using namespace memory; using namespace memory;
/** typedef struct Timer {
* uint8_t cycles;
uint8_t divider;
* uint8_t target;
*/ uint8_t counter;
bool enabled;
const int kApuClockSpeed = 1024000; // 1.024 MHz } Timer;
const int apuSampleRate = 32000; // 32 KHz
const int apuClocksPerSample = 64; // 64 clocks per sample
/** /**
* @class Apu * @class Apu
@@ -52,97 +49,47 @@ const int apuClocksPerSample = 64; // 64 clocks per sample
* register $F1 can be cleared to unmap the IPL ROM and allow read access to * register $F1 can be cleared to unmap the IPL ROM and allow read access to
* this RAM. * this RAM.
*/ */
class Apu : public Observer { class Apu {
public: public:
Apu(MemoryImpl &memory, AudioRam &aram, Clock &clock) Apu(MemoryImpl &memory) : memory_(memory) {}
: aram_(aram), clock_(clock), memory_(memory) {}
void Init(); void Init();
void Reset(); void Reset();
void Update();
void Notify(uint32_t address, uint8_t data) override;
void ProcessSamples(); void RunCycles(uint64_t cycles);
uint8_t FetchSampleForVoice(uint8_t voice_num); uint8_t SpcRead(uint16_t address);
uint16_t CalculateAddressForVoice(uint8_t voice_num); void SpcWrite(uint16_t address, uint8_t data);
int16_t GetNextSample(); void SpcIdle(bool waiting);
// Called upon a reset void Cycle();
void Initialize() {
spc700_.Reset();
dsp_.Reset();
SignalReady();
}
// Set Port 0 = $AA and Port 1 = $BB uint8_t Read(uint16_t address);
void SignalReady() { void Write(uint16_t address, uint8_t data);
memory_.WriteByte(0x2140, READY_SIGNAL_0);
memory_.WriteByte(0x2141, READY_SIGNAL_1);
}
void WriteToPort(uint8_t portNum, uint8_t value) { auto dsp() -> Dsp & { return dsp_; }
ports_[portNum] = value; auto spc700() -> Spc700 & { return spc700_; }
switch (portNum) {
case 0:
memory_.WriteByte(0x2140, value);
break;
case 1:
memory_.WriteByte(0x2141, value);
break;
case 2:
memory_.WriteByte(0x2142, value);
break;
case 3:
memory_.WriteByte(0x2143, value);
break;
}
}
void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); }
// Method to fetch a sample from AudioRam
uint8_t FetchSampleFromRam(uint16_t address) const {
return aram_.read(address);
}
// Method to push a processed sample to the audio buffer
void PushToAudioBuffer(int16_t sample) { audio_samples_.push_back(sample); }
// Returns the audio samples for the current frame
const std::vector<int16_t> &GetAudioSamples() const;
private:
// Constants for communication
static const uint8_t READY_SIGNAL_0 = 0xAA;
static const uint8_t READY_SIGNAL_1 = 0xBB;
static const uint8_t BEGIN_SIGNAL = 0xCC;
// Port buffers (equivalent to $2140 to $2143 for the main CPU) // Port buffers (equivalent to $2140 to $2143 for the main CPU)
uint8_t ports_[4] = {0}; std::array<uint8_t, 6> in_ports_; // includes 2 bytes of ram
std::array<uint8_t, 4> out_ports_;
std::vector<uint8_t> ram = std::vector<uint8_t>(0x10000, 0);
// Updates internal state based on APU register settings private:
void UpdateChannelSettings(); bool rom_readable_ = false;
// Generates a sample for an audio channel uint8_t dsp_adr_ = 0;
int16_t GenerateSample(int channel); uint32_t cycles_ = 0;
// Applies an envelope to an audio channel
void ApplyEnvelope(int channel);
// Handles DSP (Digital Signal Processor) memory reads and writes
uint8_t ReadDspMemory(uint16_t address);
void WriteDspMemory(uint16_t address, uint8_t value);
// Member variables to store internal APU state and resources
AudioRam &aram_;
Clock &clock_;
MemoryImpl &memory_; MemoryImpl &memory_;
std::array<Timer, 3> timer_;
DigitalSignalProcessor dsp_; ApuCallbacks callbacks_ = {
Spc700 spc700_{aram_}; [&](uint16_t adr, uint8_t val) { SpcWrite(adr, val); },
std::vector<int16_t> audio_samples_; [&](uint16_t adr) { return SpcRead(adr); },
[&](bool waiting) { SpcIdle(waiting); },
std::function<void()> ready_callback_; };
Dsp dsp_{ram};
Spc700 spc700_{callbacks_};
}; };
} // namespace audio } // namespace audio

View File

@@ -1,5 +1,7 @@
#include "app/emu/audio/dsp.h" #include "app/emu/audio/dsp.h"
#include <cstring>
#include "app/emu/memory/memory.h" #include "app/emu/memory/memory.h"
namespace yaze { namespace yaze {
@@ -7,280 +9,626 @@ namespace app {
namespace emu { namespace emu {
namespace audio { namespace audio {
void DigitalSignalProcessor::Reset() {} static const int rateValues[32] = {0, 2048, 1536, 1280, 1024, 768, 640, 512,
384, 320, 256, 192, 160, 128, 96, 80,
64, 48, 40, 32, 24, 20, 16, 12,
10, 8, 6, 5, 4, 3, 2, 1};
uint8_t DigitalSignalProcessor::ReadVoiceReg(uint8_t voice, uint8_t reg) const { static const int rateOffsets[32] = {0, 0, 1040, 536, 0, 1040, 536, 0, 1040,
voice %= kNumVoices; 536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
switch (reg % kNumVoiceRegs) { 536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
case 0: 536, 0, 1040, 536, 0};
return voices_[voice].vol_left;
case 1: static const int gaussValues[512] = {
return voices_[voice].vol_right; 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
case 2: 0x000, 0x000, 0x000, 0x000, 0x000, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
return voices_[voice].pitch_low; 0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002,
case 3: 0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004,
return voices_[voice].pitch_high; 0x005, 0x005, 0x005, 0x005, 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007,
case 4: 0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00a, 0x00a, 0x00a, 0x00b, 0x00b,
return voices_[voice].source_number; 0x00b, 0x00c, 0x00c, 0x00d, 0x00d, 0x00e, 0x00e, 0x00f, 0x00f, 0x00f, 0x010,
case 5: 0x010, 0x011, 0x011, 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016,
return voices_[voice].adsr1; 0x017, 0x017, 0x018, 0x018, 0x019, 0x01a, 0x01b, 0x01b, 0x01c, 0x01d, 0x01d,
case 6: 0x01e, 0x01f, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026,
return voices_[voice].adsr2; 0x027, 0x028, 0x029, 0x02a, 0x02b, 0x02c, 0x02d, 0x02e, 0x02f, 0x030, 0x031,
case 7: 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x03a, 0x03b, 0x03c, 0x03d,
return voices_[voice].gain; 0x03e, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04a, 0x04c,
case 8: 0x04d, 0x04e, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05a, 0x05c,
return voices_[voice].envx; 0x05e, 0x05f, 0x061, 0x063, 0x064, 0x066, 0x068, 0x06a, 0x06b, 0x06d, 0x06f,
case 9: 0x071, 0x073, 0x075, 0x076, 0x078, 0x07a, 0x07c, 0x07e, 0x080, 0x082, 0x084,
return voices_[voice].outx; 0x086, 0x089, 0x08b, 0x08d, 0x08f, 0x091, 0x093, 0x096, 0x098, 0x09a, 0x09c,
default: 0x09f, 0x0a1, 0x0a3, 0x0a6, 0x0a8, 0x0ab, 0x0ad, 0x0af, 0x0b2, 0x0b4, 0x0b7,
return 0; // This shouldn't happen, but it's good to have a default 0x0ba, 0x0bc, 0x0bf, 0x0c1, 0x0c4, 0x0c7, 0x0c9, 0x0cc, 0x0cf, 0x0d2, 0x0d4,
// case 0x0d7, 0x0da, 0x0dd, 0x0e0, 0x0e3, 0x0e6, 0x0e9, 0x0ec, 0x0ef, 0x0f2, 0x0f5,
0x0f8, 0x0fb, 0x0fe, 0x101, 0x104, 0x107, 0x10b, 0x10e, 0x111, 0x114, 0x118,
0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, 0x130, 0x133, 0x137, 0x13a, 0x13e,
0x141, 0x145, 0x148, 0x14c, 0x150, 0x153, 0x157, 0x15b, 0x15f, 0x162, 0x166,
0x16a, 0x16e, 0x172, 0x176, 0x17a, 0x17d, 0x181, 0x185, 0x189, 0x18d, 0x191,
0x195, 0x19a, 0x19e, 0x1a2, 0x1a6, 0x1aa, 0x1ae, 0x1b2, 0x1b7, 0x1bb, 0x1bf,
0x1c3, 0x1c8, 0x1cc, 0x1d0, 0x1d5, 0x1d9, 0x1dd, 0x1e2, 0x1e6, 0x1eb, 0x1ef,
0x1f3, 0x1f8, 0x1fc, 0x201, 0x205, 0x20a, 0x20f, 0x213, 0x218, 0x21c, 0x221,
0x226, 0x22a, 0x22f, 0x233, 0x238, 0x23d, 0x241, 0x246, 0x24b, 0x250, 0x254,
0x259, 0x25e, 0x263, 0x267, 0x26c, 0x271, 0x276, 0x27b, 0x280, 0x284, 0x289,
0x28e, 0x293, 0x298, 0x29d, 0x2a2, 0x2a6, 0x2ab, 0x2b0, 0x2b5, 0x2ba, 0x2bf,
0x2c4, 0x2c9, 0x2ce, 0x2d3, 0x2d8, 0x2dc, 0x2e1, 0x2e6, 0x2eb, 0x2f0, 0x2f5,
0x2fa, 0x2ff, 0x304, 0x309, 0x30e, 0x313, 0x318, 0x31d, 0x322, 0x326, 0x32b,
0x330, 0x335, 0x33a, 0x33f, 0x344, 0x349, 0x34e, 0x353, 0x357, 0x35c, 0x361,
0x366, 0x36b, 0x370, 0x374, 0x379, 0x37e, 0x383, 0x388, 0x38c, 0x391, 0x396,
0x39b, 0x39f, 0x3a4, 0x3a9, 0x3ad, 0x3b2, 0x3b7, 0x3bb, 0x3c0, 0x3c5, 0x3c9,
0x3ce, 0x3d2, 0x3d7, 0x3dc, 0x3e0, 0x3e5, 0x3e9, 0x3ed, 0x3f2, 0x3f6, 0x3fb,
0x3ff, 0x403, 0x408, 0x40c, 0x410, 0x415, 0x419, 0x41d, 0x421, 0x425, 0x42a,
0x42e, 0x432, 0x436, 0x43a, 0x43e, 0x442, 0x446, 0x44a, 0x44e, 0x452, 0x455,
0x459, 0x45d, 0x461, 0x465, 0x468, 0x46c, 0x470, 0x473, 0x477, 0x47a, 0x47e,
0x481, 0x485, 0x488, 0x48c, 0x48f, 0x492, 0x496, 0x499, 0x49c, 0x49f, 0x4a2,
0x4a6, 0x4a9, 0x4ac, 0x4af, 0x4b2, 0x4b5, 0x4b7, 0x4ba, 0x4bd, 0x4c0, 0x4c3,
0x4c5, 0x4c8, 0x4cb, 0x4cd, 0x4d0, 0x4d2, 0x4d5, 0x4d7, 0x4d9, 0x4dc, 0x4de,
0x4e0, 0x4e3, 0x4e5, 0x4e7, 0x4e9, 0x4eb, 0x4ed, 0x4ef, 0x4f1, 0x4f3, 0x4f5,
0x4f6, 0x4f8, 0x4fa, 0x4fb, 0x4fd, 0x4ff, 0x500, 0x502, 0x503, 0x504, 0x506,
0x507, 0x508, 0x50a, 0x50b, 0x50c, 0x50d, 0x50e, 0x50f, 0x510, 0x511, 0x511,
0x512, 0x513, 0x514, 0x514, 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518,
0x518, 0x518, 0x518, 0x518, 0x519, 0x519};
void Dsp::Reset() {
memset(ram, 0, sizeof(ram));
ram[0x7c] = 0xff; // set ENDx
for (int i = 0; i < 8; i++) {
channel[i].pitch = 0;
channel[i].pitchCounter = 0;
channel[i].pitchModulation = false;
memset(channel[i].decodeBuffer, 0, sizeof(channel[i].decodeBuffer));
channel[i].bufferOffset = 0;
channel[i].srcn = 0;
channel[i].decodeOffset = 0;
channel[i].blockOffset = 0;
channel[i].brrHeader = 0;
channel[i].useNoise = false;
channel[i].startDelay = 0;
memset(channel[i].adsrRates, 0, sizeof(channel[i].adsrRates));
channel[i].adsrState = 0;
channel[i].sustainLevel = 0;
channel[i].gainSustainLevel = 0;
channel[i].useGain = false;
channel[i].gainMode = 0;
channel[i].directGain = false;
channel[i].gainValue = 0;
channel[i].preclampGain = 0;
channel[i].gain = 0;
channel[i].keyOn = false;
channel[i].keyOff = false;
channel[i].sampleOut = 0;
channel[i].volumeL = 0;
channel[i].volumeR = 0;
channel[i].echoEnable = false;
} }
counter = 0;
dirPage = 0;
evenCycle = true;
mute = true;
reset = true;
masterVolumeL = 0;
masterVolumeR = 0;
sampleOutL = 0;
sampleOutR = 0;
echoOutL = 0;
echoOutR = 0;
noiseSample = 0x4000;
noiseRate = 0;
echoWrites = false;
echoVolumeL = 0;
echoVolumeR = 0;
feedbackVolume = 0;
echoBufferAdr = 0;
echoDelay = 0;
echoLength = 0;
echoBufferIndex = 0;
firBufferIndex = 0;
memset(firValues, 0, sizeof(firValues));
memset(firBufferL, 0, sizeof(firBufferL));
memset(firBufferR, 0, sizeof(firBufferR));
memset(sampleBuffer, 0, sizeof(sampleBuffer));
sampleOffset = 0;
} }
void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, void Dsp::NewFrame() {
uint8_t value) { lastFrameBoundary = sampleOffset;
voice %= kNumVoices; }
switch (reg % kNumVoiceRegs) {
case 0: void Dsp::Cycle() {
voices_[voice].vol_left = static_cast<int8_t>(value); sampleOutL = 0;
break; sampleOutR = 0;
case 1: echoOutL = 0;
voices_[voice].vol_right = static_cast<int8_t>(value); echoOutR = 0;
break; for (int i = 0; i < 8; i++) {
case 2: CycleChannel(i);
voices_[voice].pitch_low = value;
break;
case 3:
voices_[voice].pitch_high = value;
break;
case 4:
voices_[voice].source_number = value;
break;
case 5:
voices_[voice].adsr1 = value;
break;
case 6:
voices_[voice].adsr2 = value;
break;
case 7:
voices_[voice].gain = value;
break;
// Note: envx and outx are read-only, so they don't have cases here
} }
} HandleEcho(); // also applies master volume
counter = counter == 0 ? 30720 : counter - 1;
// Set the callbacks HandleNoise();
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) { evenCycle = !evenCycle;
sample_fetcher_ = fetcher; // handle mute flag
} if (mute) {
sampleOutL = 0;
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) { sampleOutR = 0;
sample_pusher_ = pusher;
}
int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) {
Voice const& voice = voices_[voice_num];
uint16_t sample_address = voice.source_number;
// Use the callback to fetch the sample
int16_t sample = static_cast<int16_t>(sample_fetcher_(sample_address) << 8);
return sample;
}
int16_t DigitalSignalProcessor::ProcessSample(uint8_t voice_num,
int16_t sample) {
Voice const& voice = voices_[voice_num];
// Adjust the pitch (for simplicity, we're just adjusting the sample value)
sample += voice.pitch_low + (voice.pitch_high << 8);
// Apply volume (separate for left and right for stereo sound)
int16_t left_sample = (sample * voice.vol_left) / 255;
int16_t right_sample = (sample * voice.vol_right) / 255;
// Combine stereo samples into a single 16-bit value
return (left_sample + right_sample) / 2;
}
void DigitalSignalProcessor::MixSamples() {
int16_t mixed_sample = 0;
for (uint8_t i = 0; i < kNumVoices; i++) {
int16_t decoded_sample = DecodeSample(i);
int16_t processed_sample = ProcessSample(i, decoded_sample);
mixed_sample += processed_sample;
} }
// put final sample in the samplebuffer
// Clamp the mixed sample to 16-bit range sampleBuffer[(sampleOffset & 0x3ff) * 2] = sampleOutL;
if (mixed_sample > 32767) { sampleBuffer[(sampleOffset++ & 0x3ff) * 2 + 1] = sampleOutR;
mixed_sample = 32767;
} else if (mixed_sample < -32768) {
mixed_sample = -32768;
}
// Use the callback to push the mixed sample
sample_pusher_(mixed_sample);
} }
void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) { static int clamp16(int val) {
uint8_t adsr1 = ReadVoiceReg(voice, 0x05); return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val);
uint8_t adsr2 = ReadVoiceReg(voice, 0x06); }
uint8_t gain = ReadVoiceReg(voice, 0x07);
uint8_t enableADSR = (adsr1 & 0x80) >> 7; static int clip16(int val) { return (int16_t)(val & 0xffff); }
if (enableADSR) { bool Dsp::CheckCounter(int rate) {
// Handle ADSR envelope if (rate == 0) return false;
Voice& voice_obj = voices_[voice]; return ((counter + rateOffsets[rate]) % rateValues[rate]) == 0;
switch (voice_obj.state) { }
case VoiceState::ATTACK:
// Update amplitude based on attack rate void Dsp::HandleEcho() {
voice_obj.current_amplitude += AttackRate(adsr1); // increment fir buffer index
if (voice_obj.current_amplitude >= ENVELOPE_MAX) { firBufferIndex++;
voice_obj.current_amplitude = ENVELOPE_MAX; firBufferIndex &= 0x7;
voice_obj.state = VoiceState::DECAY; // get value out of ram
} uint16_t adr = echoBufferAdr + echoBufferIndex;
break; int16_t ramSample = aram_[adr] | (aram_[(adr + 1) & 0xffff] << 8);
case VoiceState::DECAY: firBufferL[firBufferIndex] = ramSample >> 1;
// Update amplitude based on decay rate ramSample = aram_[(adr + 2) & 0xffff] | (aram_[(adr + 3) & 0xffff] << 8);
voice_obj.current_amplitude -= DecayRate(adsr2); firBufferR[firBufferIndex] = ramSample >> 1;
if (voice_obj.current_amplitude <= voice_obj.decay_level) { // calculate FIR-sum
voice_obj.current_amplitude = voice_obj.decay_level; int sumL = 0, sumR = 0;
voice_obj.state = VoiceState::SUSTAIN; for (int i = 0; i < 8; i++) {
} sumL += (firBufferL[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
break; sumR += (firBufferR[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
case VoiceState::SUSTAIN: if (i == 6) {
// Keep amplitude at the calculated decay level // clip to 16-bit before last addition
voice_obj.current_amplitude = voice_obj.decay_level; sumL = clip16(sumL);
break; sumR = clip16(sumR);
case VoiceState::RELEASE:
// Update amplitude based on release rate
voice_obj.current_amplitude -= ReleaseRate(adsr2);
if (voice_obj.current_amplitude <= 0) {
voice_obj.current_amplitude = 0;
voice_obj.state = VoiceState::OFF;
}
break;
default:
break;
} }
}
sumL = clamp16(sumL) & ~1;
sumR = clamp16(sumR) & ~1;
// apply master volume and modify output with sum
sampleOutL = clamp16(((sampleOutL * masterVolumeL) >> 7) +
((sumL * echoVolumeL) >> 7));
sampleOutR = clamp16(((sampleOutR * masterVolumeR) >> 7) +
((sumR * echoVolumeR) >> 7));
// get echo value
int echoL = clamp16(echoOutL + clip16((sumL * feedbackVolume) >> 7)) & ~1;
int echoR = clamp16(echoOutR + clip16((sumR * feedbackVolume) >> 7)) & ~1;
// write it to ram
if (echoWrites) {
aram_[adr] = echoL & 0xff;
aram_[(adr + 1) & 0xffff] = echoL >> 8;
aram_[(adr + 2) & 0xffff] = echoR & 0xff;
aram_[(adr + 3) & 0xffff] = echoR >> 8;
}
// handle indexes
if (echoBufferIndex == 0) {
echoLength = echoDelay * 4;
}
echoBufferIndex += 4;
if (echoBufferIndex >= echoLength) {
echoBufferIndex = 0;
}
}
void Dsp::CycleChannel(int ch) {
// handle pitch counter
int pitch = channel[ch].pitch;
if (ch > 0 && channel[ch].pitchModulation) {
pitch += ((channel[ch - 1].sampleOut >> 5) * pitch) >> 10;
}
// get current brr header and get sample address
channel[ch].brrHeader = aram_[channel[ch].decodeOffset];
uint16_t samplePointer = dirPage + 4 * channel[ch].srcn;
if (channel[ch].startDelay == 0) samplePointer += 2;
uint16_t sampleAdr =
aram_[samplePointer] | (aram_[(samplePointer + 1) & 0xffff] << 8);
// handle starting of sample
if (channel[ch].startDelay > 0) {
if (channel[ch].startDelay == 5) {
// first keyed on
channel[ch].decodeOffset = sampleAdr;
channel[ch].blockOffset = 1;
channel[ch].bufferOffset = 0;
channel[ch].brrHeader = 0;
ram[0x7c] &= ~(1 << ch); // clear ENDx
}
channel[ch].gain = 0;
channel[ch].startDelay--;
channel[ch].pitchCounter = 0;
if (channel[ch].startDelay > 0 && channel[ch].startDelay < 4) {
channel[ch].pitchCounter = 0x4000;
}
pitch = 0;
}
// get sample
int sample = 0;
if (channel[ch].useNoise) {
sample = clip16(noiseSample * 2);
} else { } else {
// Handle Gain envelope sample = GetSample(ch);
// Extract mode from the gain byte }
uint8_t mode = (gain & 0xE0) >> 5; sample = ((sample * channel[ch].gain) >> 11) & ~1;
uint8_t rate = gain & 0x1F; // handle reset and release
if (reset || (channel[ch].brrHeader & 0x03) == 1) {
Voice& voice_obj = voices_[voice]; channel[ch].adsrState = 3; // go to release
channel[ch].gain = 0;
switch (mode) { }
case 0: // Direct Designation // handle keyon/keyoff
case 1: if (evenCycle) {
case 2: if (channel[ch].keyOff) {
case 3: channel[ch].adsrState = 3; // go to release
voice_obj.current_amplitude = }
rate << 3; // Multiplying by 8 to scale to 0-255 if (channel[ch].keyOn) {
break; channel[ch].startDelay = 5;
channel[ch].adsrState = 0; // go to attack
case 6: // Increase Mode (Linear) channel[ch].keyOn = false;
voice_obj.current_amplitude += gainTimings[0][rate];
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
voice_obj.current_amplitude = ENVELOPE_MAX;
}
break;
case 7: // Increase Mode (Bent Line)
// Hypothetical behavior: Increase linearly at first, then increase
// more slowly You'll likely need to adjust this based on your
// specific requirements
if (voice_obj.current_amplitude < (ENVELOPE_MAX / 2)) {
voice_obj.current_amplitude += gainTimings[1][rate];
} else {
voice_obj.current_amplitude += gainTimings[1][rate] / 2;
}
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
voice_obj.current_amplitude = ENVELOPE_MAX;
}
break;
case 4: // Decrease Mode (Linear)
if (voice_obj.current_amplitude < gainTimings[2][rate]) {
voice_obj.current_amplitude = 0;
} else {
voice_obj.current_amplitude -= gainTimings[2][rate];
}
break;
case 5: // Decrease Mode (Exponential)
voice_obj.current_amplitude -=
(voice_obj.current_amplitude * gainTimings[3][rate]) / ENVELOPE_MAX;
break;
default:
// Default behavior can be handled here if necessary
break;
} }
} }
} // handle envelope
if (channel[ch].startDelay == 0) {
void DigitalSignalProcessor::update_voice_state(uint8_t voice_num) { HandleGain(ch);
if (voice_num >= kNumVoices) return; }
// decode new brr samples if needed and update offsets
Voice& voice = voices_[voice_num]; if (channel[ch].pitchCounter >= 0x4000) {
switch (voice.state) { DecodeBrr(ch);
case VoiceState::OFF: if (channel[ch].blockOffset >= 7) {
// Reset current amplitude if (channel[ch].brrHeader & 0x1) {
voice.current_amplitude = 0; channel[ch].decodeOffset = sampleAdr;
break; ram[0x7c] |= 1 << ch; // set ENDx
} else {
case VoiceState::ATTACK: channel[ch].decodeOffset += 9;
// Increase the current amplitude at a rate defined by the ATTACK
// setting
voice.current_amplitude += AttackRate(voice.adsr1);
if (voice.current_amplitude >= ENVELOPE_MAX) {
voice.current_amplitude = ENVELOPE_MAX;
voice.state = VoiceState::DECAY;
voice.decay_level = CalculateDecayLevel(voice.adsr2);
} }
break; channel[ch].blockOffset = 1;
} else {
case VoiceState::DECAY: channel[ch].blockOffset += 2;
// Decrease the current amplitude at a rate defined by the DECAY setting }
voice.current_amplitude -= DecayRate(voice.adsr2); }
if (voice.current_amplitude <= voice.decay_level) { // update pitch counter
voice.current_amplitude = voice.decay_level; channel[ch].pitchCounter &= 0x3fff;
voice.state = VoiceState::SUSTAIN; channel[ch].pitchCounter += pitch;
} if (channel[ch].pitchCounter > 0x7fff) channel[ch].pitchCounter = 0x7fff;
break; // set outputs
ram[(ch << 4) | 8] = channel[ch].gain >> 4;
case VoiceState::SUSTAIN: ram[(ch << 4) | 9] = sample >> 8;
// Keep the current amplitude at the decay level channel[ch].sampleOut = sample;
break; sampleOutL = clamp16(sampleOutL + ((sample * channel[ch].volumeL) >> 7));
sampleOutR = clamp16(sampleOutR + ((sample * channel[ch].volumeR) >> 7));
case VoiceState::RELEASE: if (channel[ch].echoEnable) {
// Decrease the current amplitude at a rate defined by the RELEASE echoOutL = clamp16(echoOutL + ((sample * channel[ch].volumeL) >> 7));
// setting echoOutR = clamp16(echoOutR + ((sample * channel[ch].volumeR) >> 7));
voice.current_amplitude -= ReleaseRate(voice.adsr2);
if (voice.current_amplitude == 0) {
voice.state = VoiceState::OFF;
}
break;
} }
} }
void DigitalSignalProcessor::process_envelope(uint8_t voice_num) { void Dsp::HandleGain(int ch) {
if (voice_num >= kNumVoices) return; int newGain = channel[ch].gain;
int rate = 0;
// handle gain mode
if (channel[ch].adsrState == 3) { // release
rate = 31;
newGain -= 8;
} else {
if (!channel[ch].useGain) {
rate = channel[ch].adsrRates[channel[ch].adsrState];
switch (channel[ch].adsrState) {
case 0:
newGain += rate == 31 ? 1024 : 32;
break; // attack
case 1:
newGain -= ((newGain - 1) >> 8) + 1;
break; // decay
case 2:
newGain -= ((newGain - 1) >> 8) + 1;
break; // sustain
}
} else {
if (!channel[ch].directGain) {
rate = channel[ch].adsrRates[3];
switch (channel[ch].gainMode) {
case 0:
newGain -= 32;
break; // linear decrease
case 1:
newGain -= ((newGain - 1) >> 8) + 1;
break; // exponential decrease
case 2:
newGain += 32;
break; // linear increase
case 3:
newGain += (channel[ch].preclampGain < 0x600) ? 32 : 8;
break; // bent increase
}
} else { // direct gain
rate = 31;
newGain = channel[ch].gainValue;
}
}
}
// use sustain level according to mode
int sustainLevel = channel[ch].useGain ? channel[ch].gainSustainLevel
: channel[ch].sustainLevel;
if (channel[ch].adsrState == 1 && (newGain >> 8) == sustainLevel) {
channel[ch].adsrState = 2; // go to sustain
}
// store pre-clamped gain (for bent increase)
channel[ch].preclampGain = newGain & 0xffff;
// clamp gain
if (newGain < 0 || newGain > 0x7ff) {
newGain = newGain < 0 ? 0 : 0x7ff;
if (channel[ch].adsrState == 0) {
channel[ch].adsrState = 1; // go to decay
}
}
// store new value
if (CheckCounter(rate)) channel[ch].gain = newGain;
}
Voice& voice = voices_[voice_num]; int16_t Dsp::GetSample(int ch) {
int pos = (channel[ch].pitchCounter >> 12) + channel[ch].bufferOffset;
int offset = (channel[ch].pitchCounter >> 4) & 0xff;
int16_t news = channel[ch].decodeBuffer[(pos + 3) % 12];
int16_t olds = channel[ch].decodeBuffer[(pos + 2) % 12];
int16_t olders = channel[ch].decodeBuffer[(pos + 1) % 12];
int16_t oldests = channel[ch].decodeBuffer[pos % 12];
int out = (gaussValues[0xff - offset] * oldests) >> 11;
out += (gaussValues[0x1ff - offset] * olders) >> 11;
out += (gaussValues[0x100 + offset] * olds) >> 11;
out = clip16(out) + ((gaussValues[offset] * news) >> 11);
return clamp16(out) & ~1;
}
// Update the voice state first (based on keys, etc.) void Dsp::DecodeBrr(int ch) {
update_voice_state(voice_num); int shift = channel[ch].brrHeader >> 4;
int filter = (channel[ch].brrHeader & 0xc) >> 2;
int bOff = channel[ch].bufferOffset;
int old = channel[ch].decodeBuffer[bOff == 0 ? 11 : bOff - 1] >> 1;
int older = channel[ch].decodeBuffer[bOff == 0 ? 10 : bOff - 2] >> 1;
uint8_t curByte = 0;
for (int i = 0; i < 4; i++) {
int s = 0;
if (i & 1) {
s = curByte & 0xf;
} else {
curByte = aram_[(channel[ch].decodeOffset + channel[ch].blockOffset +
(i >> 1)) &
0xffff];
s = curByte >> 4;
}
if (s > 7) s -= 16;
if (shift <= 0xc) {
s = (s << shift) >> 1;
} else {
s = (s >> 3) << 12;
}
switch (filter) {
case 1:
s += old + (-old >> 4);
break;
case 2:
s += 2 * old + ((3 * -old) >> 5) - older + (older >> 4);
break;
case 3:
s += 2 * old + ((13 * -old) >> 6) - older + ((3 * older) >> 4);
break;
}
channel[ch].decodeBuffer[bOff + i] = clamp16(s) * 2; // cuts off bit 15
older = old;
old = channel[ch].decodeBuffer[bOff + i] >> 1;
}
channel[ch].bufferOffset += 4;
if (channel[ch].bufferOffset >= 12) channel[ch].bufferOffset = 0;
}
// Calculate the envelope value based on the current amplitude void Dsp::HandleNoise() {
voice.envx = calculate_envelope_value(voice.current_amplitude); if (CheckCounter(noiseRate)) {
int bit = (noiseSample & 1) ^ ((noiseSample >> 1) & 1);
noiseSample = ((noiseSample >> 1) & 0x3fff) | (bit << 14);
}
}
// Apply the envelope value to the audio output uint8_t Dsp::Read(uint8_t adr) { return ram[adr]; }
apply_envelope_to_output(voice_num);
void Dsp::Write(uint8_t adr, uint8_t val) {
int ch = adr >> 4;
switch (adr) {
case 0x00:
case 0x10:
case 0x20:
case 0x30:
case 0x40:
case 0x50:
case 0x60:
case 0x70: {
channel[ch].volumeL = val;
break;
}
case 0x01:
case 0x11:
case 0x21:
case 0x31:
case 0x41:
case 0x51:
case 0x61:
case 0x71: {
channel[ch].volumeR = val;
break;
}
case 0x02:
case 0x12:
case 0x22:
case 0x32:
case 0x42:
case 0x52:
case 0x62:
case 0x72: {
channel[ch].pitch = (channel[ch].pitch & 0x3f00) | val;
break;
}
case 0x03:
case 0x13:
case 0x23:
case 0x33:
case 0x43:
case 0x53:
case 0x63:
case 0x73: {
channel[ch].pitch = ((channel[ch].pitch & 0x00ff) | (val << 8)) & 0x3fff;
break;
}
case 0x04:
case 0x14:
case 0x24:
case 0x34:
case 0x44:
case 0x54:
case 0x64:
case 0x74: {
channel[ch].srcn = val;
break;
}
case 0x05:
case 0x15:
case 0x25:
case 0x35:
case 0x45:
case 0x55:
case 0x65:
case 0x75: {
channel[ch].adsrRates[0] = (val & 0xf) * 2 + 1;
channel[ch].adsrRates[1] = ((val & 0x70) >> 4) * 2 + 16;
channel[ch].useGain = (val & 0x80) == 0;
break;
}
case 0x06:
case 0x16:
case 0x26:
case 0x36:
case 0x46:
case 0x56:
case 0x66:
case 0x76: {
channel[ch].adsrRates[2] = val & 0x1f;
channel[ch].sustainLevel = (val & 0xe0) >> 5;
break;
}
case 0x07:
case 0x17:
case 0x27:
case 0x37:
case 0x47:
case 0x57:
case 0x67:
case 0x77: {
channel[ch].directGain = (val & 0x80) == 0;
channel[ch].gainMode = (val & 0x60) >> 5;
channel[ch].adsrRates[3] = val & 0x1f;
channel[ch].gainValue = (val & 0x7f) * 16;
channel[ch].gainSustainLevel = (val & 0xe0) >> 5;
break;
}
case 0x0c: {
masterVolumeL = val;
break;
}
case 0x1c: {
masterVolumeR = val;
break;
}
case 0x2c: {
echoVolumeL = val;
break;
}
case 0x3c: {
echoVolumeR = val;
break;
}
case 0x4c: {
for (int i = 0; i < 8; i++) {
channel[i].keyOn = val & (1 << i);
}
break;
}
case 0x5c: {
for (int i = 0; i < 8; i++) {
channel[i].keyOff = val & (1 << i);
}
break;
}
case 0x6c: {
reset = val & 0x80;
mute = val & 0x40;
echoWrites = (val & 0x20) == 0;
noiseRate = val & 0x1f;
break;
}
case 0x7c: {
val = 0; // any write clears ENDx
break;
}
case 0x0d: {
feedbackVolume = val;
break;
}
case 0x2d: {
for (int i = 0; i < 8; i++) {
channel[i].pitchModulation = val & (1 << i);
}
break;
}
case 0x3d: {
for (int i = 0; i < 8; i++) {
channel[i].useNoise = val & (1 << i);
}
break;
}
case 0x4d: {
for (int i = 0; i < 8; i++) {
channel[i].echoEnable = val & (1 << i);
}
break;
}
case 0x5d: {
dirPage = val << 8;
break;
}
case 0x6d: {
echoBufferAdr = val << 8;
break;
}
case 0x7d: {
echoDelay =
(val & 0xf) * 512; // 2048-byte steps, stereo sample is 4 bytes
break;
}
case 0x0f:
case 0x1f:
case 0x2f:
case 0x3f:
case 0x4f:
case 0x5f:
case 0x6f:
case 0x7f: {
firValues[ch] = val;
break;
}
}
ram[adr] = val;
}
void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame,
bool pal_timing) {
// resample from 534 / 641 samples per frame to wanted value
float wantedSamples = (pal_timing ? 641.0 : 534.0);
double adder = wantedSamples / samples_per_frame;
double location = lastFrameBoundary - wantedSamples;
for (int i = 0; i < samples_per_frame; i++) {
sample_data[i * 2] = sample_buffer_[(((int)location) & 0x3ff) * 2];
sample_data[i * 2 + 1] = sample_buffer_[(((int)location) & 0x3ff) * 2 + 1];
location += adder;
}
} }
} // namespace audio } // namespace audio

View File

@@ -5,6 +5,7 @@
#include <functional> #include <functional>
#include <vector> #include <vector>
#include "app/emu/audio/spc700.h"
#include "app/emu/memory/memory.h" #include "app/emu/memory/memory.h"
namespace yaze { namespace yaze {
@@ -12,8 +13,40 @@ namespace app {
namespace emu { namespace emu {
namespace audio { namespace audio {
using SampleFetcher = std::function<uint8_t(uint16_t)>; typedef struct DspChannel {
using SamplePusher = std::function<void(int16_t)>; // pitch
uint16_t pitch;
uint16_t pitchCounter;
bool pitchModulation;
// brr decoding
int16_t decodeBuffer[12];
uint8_t bufferOffset;
uint8_t srcn;
uint16_t decodeOffset;
uint8_t blockOffset; // offset within brr block
uint8_t brrHeader;
bool useNoise;
uint8_t startDelay;
// adsr, envelope, gain
uint8_t adsrRates[4]; // attack, decay, sustain, gain
uint8_t adsrState; // 0: attack, 1: decay, 2: sustain, 3: release
uint8_t sustainLevel;
uint8_t gainSustainLevel;
bool useGain;
uint8_t gainMode;
bool directGain;
uint16_t gainValue; // for direct gain
uint16_t preclampGain; // for bent increase
uint16_t gain;
// keyon/off
bool keyOn;
bool keyOff;
// output
int16_t sampleOut; // final sample, to be multiplied by channel volume
int8_t volumeL;
int8_t volumeR;
bool echoEnable;
} DspChannel;
/** /**
* The S-DSP is a digital signal processor generating the sound data. * The S-DSP is a digital signal processor generating the sound data.
@@ -34,277 +67,92 @@ using SamplePusher = std::function<void(int16_t)>;
* There are 8 voices, numbered 0 to 7. * There are 8 voices, numbered 0 to 7.
* Each voice X has 10 registers in the range $X0-$X9. * Each voice X has 10 registers in the range $X0-$X9.
* *
* | Name | Address | Bits | Notes | * | Name | Address | Bits | Notes |
* |---------|---------|-----------|--------------------------------------------------------| * |---------|---------|-----------|--------------------------------------------------------|
* | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | * | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | | VOL (R) |
* | VOL (R) | $X1 | SVVV VVVV | Right channel volume, signed. | * $X1 | SVVV VVVV | Right channel volume, signed. | | P (L) | $X2 |
* | P (L) | $X2 | LLLL LLLL | Low 8 bits of sample pitch. | * LLLL LLLL | Low 8 bits of sample pitch. | | P (H)
* | P (H) | $X3 | --HH HHHH | High 6 bits of sample pitch. | * | $X3 | --HH HHHH | High 6 bits of sample pitch. | | SCRN | $X4 |
* | SCRN | $X4 | SSSS SSSS | Selects a sample source entry from the directory. | * SSSS SSSS | Selects a sample source entry from the directory. | | ADSR
* | ADSR (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A). | * (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A).
* | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | * | | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | |
* | GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | * GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | | ENVX |
* | ENVX | $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. | * $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. | |
* | OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample wave | * OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample
* | | | | multiplied by ENVX, before applying VOL. | * wave | | | | | multiplied by ENVX, before
* applying VOL. |
*/ */
class DigitalSignalProcessor { class Dsp {
private:
static const size_t kNumVoices = 8;
static const size_t kNumVoiceRegs = 10;
static const size_t kNumGlobalRegs = 15;
enum class VoiceState { OFF, ATTACK, DECAY, SUSTAIN, RELEASE };
struct Voice {
int8_t vol_left; // x0
int8_t vol_right; // x1
uint8_t pitch_low; // x2
uint8_t pitch_high; // x3
uint8_t source_number; // x4
uint8_t adsr1; // x5
uint8_t adsr2; // x6
uint8_t gain; // x7
uint8_t envx; // x8 (read-only)
int8_t outx; // x9 (read-only)
VoiceState state = VoiceState::OFF;
uint16_t current_amplitude = 0; // Current amplitude value used for ADSR
uint16_t decay_level; // Calculated decay level based on ADSR settings
};
Voice voices_[8];
// Global DSP registers
uint8_t mvol_left; // 0C
uint8_t mvol_right; // 0D
uint8_t evol_left; // 0E
uint8_t evol_right; // 0F
uint8_t kon; // 10
uint8_t koff; // 11
uint8_t flags; // 12
uint8_t endx; // 13 (read-only)
// Global registers
std::vector<uint8_t> globalRegs = std::vector<uint8_t>(kNumGlobalRegs, 0x00);
static const uint16_t ENVELOPE_MAX = 2047; // $7FF
// Attack times in ms
const std::vector<uint32_t> attackTimes = {
4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 0};
// Decay times in ms
const std::vector<uint32_t> decayTimes = {1200, 740, 440, 290,
180, 110, 74, 37};
// Release times in ms
const std::vector<uint32_t> releaseTimes = {
// "Infinite" is represented by a large value, e.g., UINT32_MAX
UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
1200, 880, 740, 590, 440, 370, 290, 220,
180, 150, 110, 92, 74, 55, 37, 18};
// Gain timings for decrease linear, decrease exponential, etc.
// Organized by mode: [Linear Increase, Bentline Increase, Linear Decrease,
// Exponential Decrease]
const std::vector<std::vector<uint32_t>> gainTimings = {
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
24, 20, 16, 12, 10, 8, 6, 4, 2},
{UINT32_MAX, 5400, 4600, 3500, 2600, 2300, 1800, 1300, 1100, 900,
670, 560, 450, 340, 280, 220, 170, 140, 110, 84,
70, 56, 42, 35, 28, 21, 18, 14, 11, 7,
/*3.5=*/3},
// Repeating the Linear Increase timings for Linear Decrease, since they
// are the same.
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
24, 20, 16, 12, 10, 8, 6, 4, 2},
{UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
1200, 880, 740, 590, 440, 370, 290, 220,
180, 150, 110, 92, 55, 37, 18}};
// DSP Period Table
const std::vector<std::vector<uint16_t>> DigitalSignalProcessorPeriodTable = {
// ... Your DSP period table here ...
};
// DSP Period Offset
const std::vector<uint16_t> DigitalSignalProcessorPeriodOffset = {
// ... Your DSP period offsets here ...
};
uint8_t calculate_envelope_value(uint16_t amplitude) const {
// Convert the 16-bit amplitude to an 8-bit envelope value
return amplitude >> 8;
}
void apply_envelope_to_output(uint8_t voice_num) {
Voice& voice = voices_[voice_num];
// Scale the OUTX by the envelope value
// This might be a linear scaling, or more complex operations can be used
voice.outx = (voice.outx * voice.envx) / 255;
}
SampleFetcher sample_fetcher_;
SamplePusher sample_pusher_;
public: public:
DigitalSignalProcessor() = default; Dsp(std::vector<uint8_t>& aram) : aram_(aram) {}
void NewFrame();
void Reset(); void Reset();
void SetSampleFetcher(std::function<uint8_t(uint16_t)> fetcher); void Cycle();
void SetSamplePusher(std::function<void(int16_t)> pusher);
// Read a byte from a voice register void HandleEcho();
uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const; void CycleChannel(int ch);
// Write a byte to a voice register void HandleNoise();
void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value); void HandleGain(int ch);
// Read a byte from a global register bool CheckCounter(int rate);
uint8_t ReadGlobalReg(uint8_t reg) const {
return globalRegs[reg % kNumGlobalRegs];
}
// Write a byte to a global register void DecodeBrr(int ch);
void WriteGlobalReg(uint8_t reg, uint8_t value) {
globalRegs[reg % kNumGlobalRegs] = value;
}
int16_t DecodeSample(uint8_t voice_num); uint8_t Read(uint8_t adr);
int16_t ProcessSample(uint8_t voice_num, int16_t sample); void Write(uint8_t adr, uint8_t val);
void MixSamples();
// Trigger a voice to start playing int16_t GetSample(int ch);
void trigger_voice(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
Voice& voice = voices_[voice_num]; void GetSamples(int16_t* sample_data, int samples_per_frame, bool pal_timing);
voice.state = VoiceState::ATTACK;
// Initialize other state management variables if needed
}
// Release a voice (e.g., note release in ADSR) private:
void release_voice(uint8_t voice_num) { int16_t sample_buffer_[0x400 * 2]; // (1024 samples, *2 for stereo)
if (voice_num >= kNumVoices) return; int16_t sample_offset_; // current offset in samplebuffer
Voice& voice = voices_[voice_num]; std::vector<uint8_t>& aram_;
if (voice.state != VoiceState::OFF) {
voice.state = VoiceState::RELEASE;
}
// Update other state management variables if needed
}
// Calculate envelope for a given voice // mirror ram
void UpdateEnvelope(uint8_t voice); uint8_t ram[0x80];
// 8 channels
// Voice-related functions (implementations) DspChannel channel[8];
void set_voice_volume(int voice_num, int8_t left, int8_t right) { // overarching
voices_[voice_num].vol_left = left; uint16_t counter;
voices_[voice_num].vol_right = right; uint16_t dirPage;
} bool evenCycle;
bool mute;
void set_voice_pitch(int voice_num, uint16_t pitch) { bool reset;
voices_[voice_num].pitch_low = pitch & 0xFF; int8_t masterVolumeL;
voices_[voice_num].pitch_high = (pitch >> 8) & 0xFF; int8_t masterVolumeR;
} // accumulation
int16_t sampleOutL;
void set_voice_source_number(int voice_num, uint8_t srcn) { int16_t sampleOutR;
voices_[voice_num].source_number = srcn; int16_t echoOutL;
} int16_t echoOutR;
// noise
void set_voice_adsr(int voice_num, uint8_t adsr1, uint8_t adsr2) { int16_t noiseSample;
voices_[voice_num].adsr1 = adsr1; uint8_t noiseRate;
voices_[voice_num].adsr2 = adsr2; // echo
} bool echoWrites;
int8_t echoVolumeL;
void set_voice_gain(int voice_num, uint8_t gain) { int8_t echoVolumeR;
voices_[voice_num].gain = gain; int8_t feedbackVolume;
} uint16_t echoBufferAdr;
uint16_t echoDelay;
uint8_t read_voice_envx(int voice_num) { return voices_[voice_num].envx; } uint16_t echoLength;
uint16_t echoBufferIndex;
int8_t read_voice_outx(int voice_num) { return voices_[voice_num].outx; } uint8_t firBufferIndex;
int8_t firValues[8];
// Global DSP functions int16_t firBufferL[8];
void set_master_volume(int8_t left, int8_t right) { int16_t firBufferR[8];
mvol_left = left; // sample ring buffer (1024 samples, *2 for stereo)
mvol_right = right; int16_t sampleBuffer[0x400 * 2];
} uint16_t sampleOffset; // current offset in samplebuffer
uint32_t lastFrameBoundary;
void set_echo_volume(int8_t left, int8_t right) {
evol_left = left;
evol_right = right;
}
void update_voice_state(uint8_t voice_num);
// Override the key_on and key_off methods to utilize the new state management
void key_on(uint8_t value) {
for (uint8_t i = 0; i < kNumVoices; i++) {
if (value & (1 << i)) {
trigger_voice(i);
}
}
}
void key_off(uint8_t value) {
for (uint8_t i = 0; i < kNumVoices; i++) {
if (value & (1 << i)) {
release_voice(i);
}
}
}
void set_flags(uint8_t value) {
flags = value;
// More logic may be needed here depending on flag behaviors
}
uint8_t read_endx() { return endx; }
uint16_t AttackRate(uint8_t adsr1) {
// Convert the ATTACK portion of adsr1 into a rate of amplitude change
// You might need to adjust this logic based on the exact ADSR
// implementation details
return (adsr1 & 0x0F) * 16; // Just a hypothetical conversion
}
uint16_t DecayRate(uint8_t adsr2) {
// Convert the DECAY portion of adsr2 into a rate of amplitude change
return ((adsr2 >> 4) & 0x07) * 8; // Hypothetical conversion
}
uint16_t ReleaseRate(uint8_t adsr2) {
// Convert the RELEASE portion of adsr2 into a rate of amplitude change
return (adsr2 & 0x0F) * 16; // Hypothetical conversion
}
uint16_t CalculateDecayLevel(uint8_t adsr2) {
// Calculate the decay level based on the SUSTAIN portion of adsr2
// This is the level the amplitude will decay to before entering the SUSTAIN
// phase Again, adjust based on your implementation details
return ((adsr2 >> 4) & 0x07) * 256; // Hypothetical conversion
}
// Envelope processing for all voices
// Goes through each voice and processes its envelope.
void process_envelopes() {
for (size_t i = 0; i < kNumVoices; ++i) {
process_envelope(i);
}
}
// Envelope processing for a specific voice
// For a given voice, update its state (ADSR), calculate the envelope value,
// and apply the envelope to the audio output.
void process_envelope(uint8_t voice_num);
}; };
} // namespace audio } // namespace audio

View File

@@ -5,29 +5,84 @@ namespace app {
namespace emu { namespace emu {
namespace audio { namespace audio {
// Immediate // adressing modes
uint8_t Spc700::imm() {
PC++; uint16_t Spc700::ind() {
return read(PC); read(PC);
return X | (PSW.P << 8);
} }
uint16_t Spc700::idx() {
uint8_t pointer = ReadOpcode();
callbacks_.idle(false);
return read_word(((pointer + X) & 0xff) | (PSW.P << 8));
}
uint16_t Spc700::dpx() {
uint16_t res = ((ReadOpcode() + X) & 0xff) | (PSW.P << 8);
callbacks_.idle(false);
return res;
}
uint16_t Spc700::dp_y() {
uint16_t res = ((ReadOpcode() + Y) & 0xff) | (PSW.P << 8);
callbacks_.idle(false);
return res;
}
uint16_t Spc700::abs_x() {
uint16_t res = (ReadOpcodeWord() + X) & 0xffff;
callbacks_.idle(false);
return res;
}
uint16_t Spc700::abs_y() {
uint16_t res = (ReadOpcodeWord() + Y) & 0xffff;
callbacks_.idle(false);
return res;
}
uint16_t Spc700::idy() {
uint8_t pointer = ReadOpcode();
uint16_t adr = read_word(pointer | (PSW.P << 8));
callbacks_.idle(false);
return (adr + Y) & 0xffff;
}
uint16_t Spc700::dp_imm(uint8_t* srcVal) {
*srcVal = ReadOpcode();
return ReadOpcode() | (PSW.P << 8);
}
uint16_t Spc700::ind_ind(uint8_t* srcVal) {
read(PC);
*srcVal = read(Y | (PSW.P << 8));
return X | (PSW.P << 8);
}
uint8_t Spc700::abs_bit(uint16_t* adr) {
uint16_t adrBit = ReadOpcodeWord();
*adr = adrBit & 0x1fff;
return adrBit >> 13;
}
uint16_t Spc700::dp_word(uint16_t* low) {
uint8_t adr = ReadOpcode();
*low = adr | (PSW.P << 8);
return ((adr + 1) & 0xff) | (PSW.P << 8);
}
uint16_t Spc700::ind_p() {
read(PC);
return X++ | (PSW.P << 8);
}
// Immediate
uint16_t Spc700::imm() { return PC++; }
// Direct page // Direct page
uint8_t Spc700::dp() { uint8_t Spc700::dp() {
PC++; return ReadOpcode() | (PSW.P << 8);
uint8_t offset = read(PC);
return read((PSW.P << 8) + offset);
}
uint8_t& Spc700::mutable_dp() {
PC++;
uint8_t offset = read(PC);
return mutable_read((PSW.P << 8) + offset);
}
uint8_t Spc700::get_dp_addr() {
PC++;
uint8_t offset = read(PC);
return (PSW.P << 8) + offset;
} }
// Direct page indexed by X // Direct page indexed by X
@@ -47,23 +102,24 @@ uint8_t Spc700::dp_plus_y() {
// Indexed indirect (add index before 16-bit lookup). // Indexed indirect (add index before 16-bit lookup).
uint16_t Spc700::dp_plus_x_indirect() { uint16_t Spc700::dp_plus_x_indirect() {
PC++; PC++;
uint16_t addr = read_16(PC + X); uint16_t addr = read_word(PC + X);
return addr; return addr;
} }
// Indirect indexed (add index after 16-bit lookup). // Indirect indexed (add index after 16-bit lookup).
uint16_t Spc700::dp_indirect_plus_y() { uint16_t Spc700::dp_indirect_plus_y() {
PC++; PC++;
uint16_t offset = read_16(PC); uint16_t offset = read_word(PC);
return offset + Y; return offset + Y;
} }
uint16_t Spc700::abs() { uint16_t Spc700::dp_dp(uint8_t* src) {
PC++; *src = read(ReadOpcode() | (PSW.P << 8));
uint16_t addr = read(PC) | (read(PC) << 8); return ReadOpcode() | (PSW.P << 8);
return addr;
} }
uint16_t Spc700::abs() { return ReadOpcodeWord(); }
int8_t Spc700::rel() { int8_t Spc700::rel() {
PC++; PC++;
return static_cast<int8_t>(read(PC)); return static_cast<int8_t>(read(PC));

View File

@@ -5,10 +5,45 @@ namespace app {
namespace emu { namespace emu {
namespace audio { namespace audio {
void Spc700::MOV(uint8_t& dest, uint8_t operand) { // opcode functions
dest = operand;
PSW.Z = (operand == 0); void Spc700::MOVX(uint16_t adr) {
PSW.N = (operand & 0x80); X = read(adr);
PSW.Z = (X == 0);
PSW.N = (X & 0x80);
}
void Spc700::MOVY(uint16_t adr) {
Y = read(adr);
PSW.Z = (Y == 0);
PSW.N = (Y & 0x80);
}
void Spc700::MOVS(uint16_t adr) {
switch (bstep) {
case 0: read(adr); break;
case 1: write(adr, A); bstep = 0; break;
}
}
void Spc700::MOVSX(uint16_t adr) {
switch (bstep) {
case 0: read(adr); break;
case 1: write(adr, X); bstep = 0; break;
}
}
void Spc700::MOVSY(uint16_t adr) {
switch (bstep) {
case 0: read(adr); break;
case 1: write(adr, Y); bstep = 0; break;
}
}
void Spc700::MOV(uint16_t adr) {
A = read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
} }
void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) { void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
@@ -17,75 +52,162 @@ void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
PSW.N = (operand & 0x80); PSW.N = (operand & 0x80);
} }
void Spc700::ADC(uint8_t& dest, uint8_t operand) { void Spc700::ADC(uint16_t adr) {
uint16_t result = dest + operand + PSW.C; uint8_t value = read(adr);
PSW.V = ((A ^ result) & (operand ^ result) & 0x80); uint16_t result = A + value + PSW.C;
PSW.V = ((A ^ result) & (adr ^ result) & 0x80);
PSW.C = (result > 0xFF); PSW.C = (result > 0xFF);
PSW.Z = ((result & 0xFF) == 0); PSW.H = ((A ^ adr ^ result) & 0x10);
PSW.N = (result & 0x80); A = result & 0xFF;
PSW.H = ((A ^ operand ^ result) & 0x10); PSW.Z = ((A & 0xFF) == 0);
dest = result & 0xFF; PSW.N = (A & 0x80);
} }
void Spc700::SBC(uint8_t& dest, uint8_t operand) { void Spc700::ADCM(uint16_t& dest, uint8_t operand) {
uint16_t result = dest - operand - (1 - PSW.C); uint8_t applyOn = read(dest);
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x80); int result = applyOn + operand + PSW.C;
PSW.C = (result < 0x100); PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
PSW.Z = ((result & 0xFF) == 0); (operand & 0x80) != (result & 0x80);
PSW.N = (result & 0x80); PSW.H = ((applyOn & 0xf) + (operand & 0xf) + PSW.C) > 0xf;
PSW.H = ((dest ^ operand ^ result) & 0x10); PSW.C = result > 0xff;
dest = result & 0xFF; write(dest, result);
}
void Spc700::CMP(uint8_t& dest, uint8_t operand) {
uint16_t result = dest - operand;
PSW.C = (result < 0x100);
PSW.Z = ((result & 0xFF) == 0); PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80); PSW.N = (result & 0x80);
} }
void Spc700::AND(uint8_t& dest, uint8_t operand) { void Spc700::SBC(uint16_t adr) {
dest &= operand; uint8_t value = read(adr) ^ 0xff;
PSW.Z = (dest == 0); int result = A + value + PSW.C;
PSW.N = (dest & 0x80); PSW.V = (A & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80);
PSW.H = ((A & 0xf) + (value & 0xf) + PSW.C) > 0xf;
PSW.C = result > 0xff;
A = result;
PSW.Z = ((A & 0xFF) == 0);
PSW.N = (A & 0x80);
} }
void Spc700::OR(uint8_t& dest, uint8_t operand) { void Spc700::SBCM(uint16_t& dest, uint8_t operand) {
dest |= operand; operand ^= 0xff;
PSW.Z = (dest == 0); uint8_t applyOn = read(dest);
PSW.N = (dest & 0x80); int result = applyOn + operand + PSW.C;
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
(operand & 0x80) != (operand & 0x80);
PSW.H = ((applyOn & 0xF) + (operand & 0xF) + PSW.C) > 0xF;
PSW.C = result > 0xFF;
write(dest, result);
PSW.Z = ((A & 0xFF) == 0);
PSW.N = (A & 0x80);
} }
void Spc700::EOR(uint8_t& dest, uint8_t operand) { void Spc700::CMPX(uint16_t adr) {
dest ^= operand; uint8_t value = read(adr) ^ 0xff;
PSW.Z = (dest == 0); int result = X + value + 1;
PSW.N = (dest & 0x80); PSW.C = result > 0xff;
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
} }
void Spc700::ASL(uint8_t operand) { void Spc700::CMPY(uint16_t adr) {
PSW.C = (operand & 0x80); uint8_t value = read(adr) ^ 0xff;
operand <<= 1; int result = Y + value + 1;
PSW.Z = (operand == 0); PSW.C = result > 0xff;
PSW.N = (operand & 0x80); PSW.Z = (result == 0);
// A = value; PSW.N = (result & 0x80);
} }
void Spc700::LSR(uint8_t& operand) { void Spc700::CMPM(uint16_t dst, uint8_t value) {
PSW.C = (operand & 0x01); value ^= 0xff;
operand >>= 1; int result = read(dst) + value + 1;
PSW.Z = (operand == 0); PSW.C = result > 0xff;
PSW.N = (operand & 0x80); callbacks_.idle(false);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
} }
void Spc700::ROL(uint8_t operand, bool isImmediate) { void Spc700::CMP(uint16_t adr) {
uint8_t value = isImmediate ? imm() : operand; uint8_t value = read(adr) ^ 0xff;
uint8_t carry = PSW.C; int result = A + value + 1;
PSW.C = (value & 0x80); PSW.C = result > 0xff;
value <<= 1; PSW.Z = ((result & 0xFF) == 0);
value |= carry; PSW.N = (result & 0x80);
PSW.Z = (value == 0); }
PSW.N = (value & 0x80);
// operand = value; void Spc700::AND(uint16_t adr) {
A &= read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
}
void Spc700::ANDM(uint16_t dest, uint8_t operand) {
uint8_t result = read(dest) & operand;
write(dest, result);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
}
void Spc700::OR(uint16_t adr) {
A |= read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
}
void Spc700::ORM(uint16_t dst, uint8_t value) {
uint8_t result = read(dst) | value;
write(dst, result);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
}
void Spc700::EOR(uint16_t adr) {
A ^= read(adr);
PSW.Z = (A == 0);
PSW.N = (A & 0x80);
}
void Spc700::EORM(uint16_t dest, uint8_t operand) {
uint8_t result = read(dest) ^ operand;
write(dest, result);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
}
void Spc700::ASL(uint16_t operand) {
uint8_t val = read(operand);
write(operand, val);
PSW.C = (val & 0x80);
val <<= 1;
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
}
void Spc700::LSR(uint16_t adr) {
uint8_t val = read(adr);
PSW.C = (val & 0x01);
val >>= 1;
write(adr, val);
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
}
void Spc700::ROR(uint16_t adr) {
uint8_t val = read(adr);
bool newC = val & 1;
val = (val >> 1) | (PSW.C << 7);
PSW.C = newC;
write(adr, val);
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
}
void Spc700::ROL(uint16_t adr) {
uint8_t val = read(adr);
bool newC = val & 0x80;
val = (val << 1) | PSW.C;
PSW.C = newC;
write(adr, val);
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
} }
void Spc700::XCN(uint8_t operand, bool isImmediate) { void Spc700::XCN(uint8_t operand, bool isImmediate) {
@@ -96,14 +218,16 @@ void Spc700::XCN(uint8_t operand, bool isImmediate) {
// operand = value; // operand = value;
} }
void Spc700::INC(uint8_t& operand) { void Spc700::INC(uint16_t adr) {
operand++; uint8_t val = read(adr) + 1;
PSW.Z = (operand == 0); write(adr, val);
PSW.N = (operand & 0x80); PSW.Z = (val == 0);
PSW.N = (val & 0x80);
} }
void Spc700::DEC(uint8_t& operand) { void Spc700::DEC(uint16_t operand) {
operand--; uint8_t val = read(operand) - 1;
write(operand, val);
PSW.Z = (operand == 0); PSW.Z = (operand == 0);
PSW.N = (operand & 0x80); PSW.N = (operand & 0x80);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
#define YAZE_APP_EMU_SPC700_H #define YAZE_APP_EMU_SPC700_H
#include <cstdint> #include <cstdint>
#include <functional>
#include <iostream> #include <iostream>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -21,6 +22,7 @@ class AudioRam {
virtual uint8_t read(uint16_t address) const = 0; virtual uint8_t read(uint16_t address) const = 0;
virtual uint8_t& mutable_read(uint16_t address) = 0; virtual uint8_t& mutable_read(uint16_t address) = 0;
virtual void write(uint16_t address, uint8_t value) = 0; virtual void write(uint16_t address, uint8_t value) = 0;
uint8_t operator[](uint16_t address) { return mutable_read(address); }
}; };
/** /**
@@ -45,8 +47,17 @@ class AudioRamImpl : public AudioRam {
void write(uint16_t address, uint8_t value) override { void write(uint16_t address, uint8_t value) override {
ram[address % ARAM_SIZE] = value; ram[address % ARAM_SIZE] = value;
} }
// add [] operators
uint8_t operator[](uint16_t address) const { return read(address); }
}; };
typedef struct ApuCallbacks {
std::function<void(uint16_t, uint8_t)> write;
std::function<uint8_t(uint16_t)> read;
std::function<void(bool)> idle;
} ApuCallbacks;
/** /**
* @class Spc700 * @class Spc700
* @brief The Spc700 class represents the SPC700 processor. * @brief The Spc700 class represents the SPC700 processor.
@@ -60,9 +71,21 @@ class AudioRamImpl : public AudioRam {
*/ */
class Spc700 { class Spc700 {
private: private:
AudioRam& aram_; ApuCallbacks callbacks_;
std::vector<std::string> log_; std::vector<std::string> log_;
bool stopped_;
bool reset_wanted_;
// single-cycle
uint8_t opcode;
uint32_t step = 0;
uint32_t bstep;
uint16_t adr;
uint16_t adr1;
uint8_t dat;
uint16_t dat16;
uint8_t param;
const uint8_t ipl_rom_[64]{ const uint8_t ipl_rom_[64]{
0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, 0xFC, 0x8F, 0xAA, 0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, 0xFC, 0x8F, 0xAA,
0xF4, 0x8F, 0xBB, 0xF5, 0x78, 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xF4, 0x8F, 0xBB, 0xF5, 0x78, 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19,
@@ -72,7 +95,7 @@ class Spc700 {
0xDD, 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF}; 0xDD, 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF};
public: public:
explicit Spc700(AudioRam& aram) : aram_(aram) {} explicit Spc700(ApuCallbacks& callbacks) : callbacks_(callbacks) {}
// Registers // Registers
uint8_t A = 0x00; // 8-bit accumulator uint8_t A = 0x00; // 8-bit accumulator
@@ -83,14 +106,14 @@ class Spc700 {
uint8_t SP = 0x00; // stack pointer uint8_t SP = 0x00; // stack pointer
struct Flags { struct Flags {
uint8_t N : 1; // Negative flag bool N : 1; // Negative flag
uint8_t V : 1; // Overflow flag bool V : 1; // Overflow flag
uint8_t P : 1; // Direct page flag bool P : 1; // Direct page flag
uint8_t B : 1; // Break flag bool B : 1; // Break flag
uint8_t H : 1; // Half-carry flag bool H : 1; // Half-carry flag
uint8_t I : 1; // Interrupt enable bool I : 1; // Interrupt enable
uint8_t Z : 1; // Zero flag bool Z : 1; // Zero flag
uint8_t C : 1; // Carry flag bool C : 1; // Carry flag
}; };
Flags PSW; // Processor status word Flags PSW; // Processor status word
@@ -112,80 +135,97 @@ class Spc700 {
return flags; return flags;
} }
void Reset(); void Reset(bool hard = false);
void BootIplRom(); void RunOpcode();
void ExecuteInstructions(uint8_t opcode); void ExecuteInstructions(uint8_t opcode);
void LogInstruction(uint16_t initial_pc, uint8_t opcode); void LogInstruction(uint16_t initial_pc, uint8_t opcode);
// Read a byte from the memory-mapped registers // Read a byte from the memory-mapped registers
uint8_t read(uint16_t address) { uint8_t read(uint16_t address) { return callbacks_.read(address); }
if (address < 0xFFC0) {
return aram_.read(address); uint16_t read_word(uint16_t address) {
} else { uint8_t adrl = address;
// Check if register is set to unmap the IPL ROM uint8_t adrh = address + 1;
if (read(0xF1) & 0x80) { uint8_t value = callbacks_.read(adrl);
return aram_.read(address); return value | (callbacks_.read(adrh) << 8);
}
return ipl_rom_[address - 0xFFC0];
}
} }
uint8_t& mutable_read(uint16_t address) { uint8_t ReadOpcode() {
if (address < 0xFFC0) { uint8_t opcode = callbacks_.read(PC++);
return aram_.mutable_read(address); return opcode;
} else {
// NOTE: Mutable access to IPL ROM is not allowed
return aram_.mutable_read(address);
}
} }
uint16_t& mutable_read_16(uint16_t address) { uint16_t ReadOpcodeWord() {
if (address < 0xFFC0) { uint8_t low = ReadOpcode();
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address)); uint8_t high = ReadOpcode();
} else { return low | (high << 8);
// NOTE: Mutable access to IPL ROM is not allowed
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address));
}
} }
uint16_t read_16(uint16_t address) { void DoBranch(uint8_t value, bool check) {
if (address < 0xFFC0) { if (check) {
return (aram_.read(address) | (aram_.read(address + 1) << 8)); // taken branch: 2 extra cycles
} else { callbacks_.idle(false);
// Check if register is set to unmap the IPL ROM callbacks_.idle(false);
if (read(0xF1) & 0x80) { PC += (int8_t)value;
return aram_.read(address);
}
return ipl_rom_[address - 0xFFC0];
} }
} }
// Write a byte to the memory-mapped registers // Write a byte to the memory-mapped registers
void write(uint16_t address, uint8_t value) { void write(uint16_t address, uint8_t value) {
if (address < 0xFFC0) { callbacks_.write(address, value);
aram_.write(address, value); }
} else {
// Check if register is set to unmap the IPL ROM void push_byte(uint8_t value) {
if (read(0xF1) & 0x80) { callbacks_.write(0x100 | SP, value);
aram_.write(address, value); SP--;
} }
}
void push_word(uint16_t value) {
push_byte(value >> 8);
push_byte(value & 0xFF);
}
uint8_t pull_byte() {
SP++;
return read(0x100 | SP);
}
uint16_t pull_word() {
uint16_t value = pull_byte();
value |= pull_byte() << 8;
return value;
} }
// ====================================================== // ======================================================
// Addressing modes // Addressing modes
// Immediate // Immediate
uint8_t imm(); uint16_t imm();
// Direct page // Direct page
uint8_t dp(); uint8_t dp();
uint8_t& mutable_dp(); uint16_t dpx();
uint8_t get_dp_addr(); uint8_t get_dp_addr();
uint8_t abs_bit(uint16_t* adr);
uint16_t dp_dp(uint8_t* src);
uint16_t ind();
uint16_t ind_ind(uint8_t* srcVal);
uint16_t dp_word(uint16_t* low);
uint16_t ind_p();
uint16_t abs_x();
uint16_t abs_y();
uint16_t idx();
uint16_t idy();
uint16_t dp_y();
uint16_t dp_imm(uint8_t* srcVal);
uint16_t abs();
int8_t rel();
// Direct page indexed by X // Direct page indexed by X
uint8_t dp_plus_x(); uint8_t dp_plus_x();
@@ -197,11 +237,6 @@ class Spc700 {
// Indirect indexed (add index after 16-bit lookup). // Indirect indexed (add index after 16-bit lookup).
uint16_t dp_indirect_plus_y(); uint16_t dp_indirect_plus_y();
uint16_t abs();
int8_t rel();
uint8_t i(); uint8_t i();
uint8_t i_postinc(); uint8_t i_postinc();
@@ -213,20 +248,43 @@ class Spc700 {
// ========================================================================== // ==========================================================================
// Instructions // Instructions
void MOV(uint8_t& dest, uint8_t operand); void MOV(uint16_t adr);
void MOV_ADDR(uint16_t address, uint8_t operand); void MOV_ADDR(uint16_t address, uint8_t operand);
void ADC(uint8_t& dest, uint8_t operand); void MOVY(uint16_t adr);
void SBC(uint8_t& dest, uint8_t operand); void MOVX(uint16_t adr);
void CMP(uint8_t& dest, uint8_t operand); void MOVS(uint16_t adr);
void AND(uint8_t& dest, uint8_t operand); void MOVSX(uint16_t adr);
void OR(uint8_t& dest, uint8_t operand); void MOVSY(uint16_t adr);
void EOR(uint8_t& dest, uint8_t operand);
void ASL(uint8_t operand); void ADC(uint16_t adr);
void LSR(uint8_t& operand); void ADCM(uint16_t& dest, uint8_t operand);
void ROL(uint8_t operand, bool isImmediate = false);
void SBC(uint16_t adr);
void SBCM(uint16_t& dest, uint8_t operand);
void CMP(uint16_t adr);
void CMPX(uint16_t adr);
void CMPM(uint16_t dst, uint8_t value);
void CMPY(uint16_t adr);
void AND(uint16_t adr);
void ANDM(uint16_t dest, uint8_t operand);
void OR(uint16_t adr);
void ORM(uint16_t dest, uint8_t operand);
void EOR(uint16_t adr);
void EORM(uint16_t dest, uint8_t operand);
void ASL(uint16_t operand);
void LSR(uint16_t adr);
void ROL(uint16_t operand);
void ROR(uint16_t adr);
void XCN(uint8_t operand, bool isImmediate = false); void XCN(uint8_t operand, bool isImmediate = false);
void INC(uint8_t& operand); void INC(uint16_t adr);
void DEC(uint8_t& operand); void DEC(uint16_t operand);
void MOVW(uint16_t& dest, uint16_t operand); void MOVW(uint16_t& dest, uint16_t operand);
void INCW(uint16_t& operand); void INCW(uint16_t& operand);
void DECW(uint16_t& operand); void DECW(uint16_t& operand);

File diff suppressed because it is too large Load Diff

View File

@@ -36,46 +36,26 @@ class InstructionEntry {
std::string instruction; // Human-readable instruction text std::string instruction; // Human-readable instruction text
}; };
const int kCpuClockSpeed = 21477272; // 21.477272 MHz class Cpu : public Loggable, public core::ExperimentFlags {
class Cpu : public memory::Memory,
public Loggable,
public core::ExperimentFlags {
public: public:
explicit Cpu(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {} explicit Cpu(memory::Memory& mem, Clock& vclock,
enum class UpdateMode { Run, Step, Pause }; memory::CpuCallbacks& callbacks)
: memory(mem), clock(vclock), callbacks_(callbacks) {}
void Reset(bool hard = false);
void Init(bool verbose = false) { clock.SetFrequency(kCpuClockSpeed); } void RunOpcode();
void Update(UpdateMode mode = UpdateMode::Run, int stepCount = 1);
void ExecuteInstruction(uint8_t opcode); void ExecuteInstruction(uint8_t opcode);
void LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, void LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand,
bool immediate, bool accumulator_mode); bool immediate, bool accumulator_mode);
void UpdatePC(uint8_t instruction_length) { PC += instruction_length; } void UpdatePC(uint8_t instruction_length) { PC += instruction_length; }
uint8_t GetInstructionLength(uint8_t opcode);
uint16_t SP() const override { return memory.SP(); }
void SetSP(uint16_t value) override { memory.SetSP(value); }
void set_next_pc(uint16_t value) { next_pc_ = value; }
void UpdateClock(int delta_time) { clock.UpdateClock(delta_time); } void UpdateClock(int delta_time) { clock.UpdateClock(delta_time); }
bool IsBreakpoint(uint32_t address) { void SetIrq(bool state) { irq_wanted_ = state; }
return std::find(breakpoints_.begin(), breakpoints_.end(), address) != void Nmi() { nmi_wanted_ = true; }
breakpoints_.end();
} uint8_t GetInstructionLength(uint8_t opcode);
void SetBreakpoint(uint32_t address) { breakpoints_.push_back(address); }
void ClearBreakpoint(uint32_t address) {
breakpoints_.erase(
std::remove(breakpoints_.begin(), breakpoints_.end(), address),
breakpoints_.end());
}
void ClearBreakpoints() {
breakpoints_.clear();
breakpoints_.shrink_to_fit();
}
auto GetBreakpoints() { return breakpoints_; }
std::vector<uint32_t> breakpoints_; std::vector<uint32_t> breakpoints_;
std::vector<InstructionEntry> instruction_log_; std::vector<InstructionEntry> instruction_log_;
@@ -90,7 +70,7 @@ class Cpu : public memory::Memory,
// 0xFFF8,F9 - ABORT 0xFFE8,E9 - ABORT // 0xFFF8,F9 - ABORT 0xFFE8,E9 - ABORT
// 0xFFE6,E7 - BRK // 0xFFE6,E7 - BRK
// 0xFFF4,F5 - COP 0xFFE4,E5 - COP // 0xFFF4,F5 - COP 0xFFE4,E5 - COP
void HandleInterrupts(); void DoInterrupt();
// ====================================================== // ======================================================
// Registers // Registers
@@ -117,18 +97,34 @@ class Cpu : public memory::Memory,
// E 6502 emulation mode // E 6502 emulation mode
// B #$10 00010000 Break (emulation mode only) // B #$10 00010000 Break (emulation mode only)
void SetFlags(uint8_t val) {
status = val;
if (E) {
SetAccumulatorSize(true);
SetIndexSize(true);
SetSP(SP() & 0xFF | 0x100);
}
if (GetIndexSize()) {
X &= 0xff;
Y &= 0xff;
}
}
void SetZN(uint16_t value, bool byte) {
if (byte) {
SetZeroFlag((value & 0xff) == 0);
SetNegativeFlag(value & 0x80);
} else {
SetZeroFlag(value == 0);
SetNegativeFlag(value & 0x8000);
}
}
// Setting flags in the status register // Setting flags in the status register
bool m() { return GetAccumulatorSize() ? 1 : 0; } bool m() { return GetAccumulatorSize() ? 1 : 0; }
bool xf() { return GetIndexSize() ? 1 : 0; }
int GetAccumulatorSize() const { return status & 0x20; } int GetAccumulatorSize() const { return status & 0x20; }
int GetIndexSize() const { return status & 0x10; } int GetIndexSize() const { return status & 0x10; }
void set_16_bit_mode() {
SetAccumulatorSize(true);
SetIndexSize(true);
}
void set_8_bit_mode() {
SetAccumulatorSize(false);
SetIndexSize(false);
}
void SetAccumulatorSize(bool set) { SetFlag(0x20, set); } void SetAccumulatorSize(bool set) { SetFlag(0x20, set); }
void SetIndexSize(bool set) { SetFlag(0x10, set); } void SetIndexSize(bool set) { SetFlag(0x10, set); }
@@ -152,7 +148,95 @@ class Cpu : public memory::Memory,
enum class AccessType { Control, Data }; enum class AccessType { Control, Data };
// ========================================================================== uint8_t ReadOpcode() { return ReadByte((PB << 16) | PC++); }
uint16_t ReadOpcodeWord(bool int_check = false) {
uint8_t value = ReadOpcode();
if (int_check) CheckInt();
return value | (ReadOpcode() << 8);
}
// Memory access routines
uint8_t ReadByte(uint32_t address) { return callbacks_.read_byte(address); }
uint16_t ReadWord(uint32_t address, uint32_t address_high,
bool int_check = false) {
uint8_t value = ReadByte(address);
if (int_check) CheckInt();
uint8_t value2 = ReadByte(address_high);
return value | (value2 << 8);
}
uint32_t ReadWordLong(uint32_t address) {
uint8_t value = ReadByte(address);
uint8_t value2 = ReadByte(address + 1);
uint8_t value3 = ReadByte(address + 2);
return value | (value2 << 8) | (value3 << 16);
}
void WriteByte(uint32_t address, uint8_t value) {
callbacks_.write_byte(address, value);
}
void WriteWord(uint32_t address, uint32_t address_high, uint16_t value,
bool reversed = false, bool int_check = false) {
if (reversed) {
callbacks_.write_byte(address_high, value >> 8);
if (int_check) CheckInt();
callbacks_.write_byte(address, value & 0xFF);
} else {
callbacks_.write_byte(address, value & 0xFF);
if (int_check) CheckInt();
callbacks_.write_byte(address_high, value >> 8);
}
}
void WriteLong(uint32_t address, uint32_t value) {
callbacks_.write_byte(address, value & 0xFF);
callbacks_.write_byte(address + 1, (value >> 8) & 0xFF);
callbacks_.write_byte(address + 2, value >> 16);
}
void PushByte(uint8_t value) {
callbacks_.write_byte(SP(), value);
SetSP(SP() - 1);
if (E) SetSP((SP() & 0xff) | 0x100);
}
void PushWord(uint16_t value, bool int_check = false) {
PushByte(value >> 8);
if (int_check) CheckInt();
PushByte(value & 0xFF);
}
void PushLong(uint32_t value) { // Push 24-bit value
PushByte(value >> 16);
PushWord(value & 0xFFFF);
}
uint8_t PopByte() {
SetSP(SP() + 1);
if (E) SetSP((SP() & 0xff) | 0x100);
return ReadByte(SP());
}
uint16_t PopWord(bool int_check = false) {
uint8_t low = PopByte();
if (int_check) CheckInt();
return low | (PopByte() << 8);
}
uint32_t PopLong() { // Pop 24-bit value
uint32_t low = PopWord();
uint32_t high = PopByte();
return (high << 16) | low;
}
void DoBranch(bool check) {
if (!check) CheckInt();
uint8_t value = ReadOpcode();
if (check) {
CheckInt();
callbacks_.idle(false); // taken branch: 1 extra cycle
PC += (int8_t)value;
}
}
void set_int_delay(bool delay) { int_delay_ = delay; }
// Addressing Modes // Addressing Modes
// Effective Address: // Effective Address:
@@ -162,7 +246,7 @@ class Cpu : public memory::Memory,
// Low: First operand byte // Low: First operand byte
// //
// LDA addr // LDA addr
uint32_t Absolute(AccessType access_type = AccessType::Data); uint32_t Absolute(uint32_t* low);
// Effective Address: // Effective Address:
// The Data Bank Register is concatened with the 16-bit operand // The Data Bank Register is concatened with the 16-bit operand
@@ -171,6 +255,7 @@ class Cpu : public memory::Memory,
// //
// LDA addr, X // LDA addr, X
uint32_t AbsoluteIndexedX(); uint32_t AbsoluteIndexedX();
uint32_t AdrAbx(uint32_t* low, bool write);
// Effective Address: // Effective Address:
// The Data Bank Register is concatened with the 16-bit operand // The Data Bank Register is concatened with the 16-bit operand
@@ -179,6 +264,17 @@ class Cpu : public memory::Memory,
// //
// LDA addr, Y // LDA addr, Y
uint32_t AbsoluteIndexedY(); uint32_t AbsoluteIndexedY();
uint32_t AdrAby(uint32_t* low, bool write);
void AdrImp();
uint32_t AdrIdx(uint32_t* low);
uint32_t AdrIdp(uint32_t* low);
uint32_t AdrIdy(uint32_t* low, bool write);
uint32_t AdrIdl(uint32_t* low);
uint32_t AdrIly(uint32_t* low);
uint32_t AdrIsy(uint32_t* low);
uint32_t Immediate(uint32_t* low, bool xFlag);
// Effective Address: // Effective Address:
// Bank: Program Bank Register (PBR) // Bank: Program Bank Register (PBR)
@@ -211,12 +307,14 @@ class Cpu : public memory::Memory,
// //
// LDA long // LDA long
uint32_t AbsoluteLong(); uint32_t AbsoluteLong();
uint32_t AdrAbl(uint32_t* low);
// Effective Address: // Effective Address:
// The 24-bit operand is added to X based on the emulation mode // The 24-bit operand is added to X based on the emulation mode
// //
// LDA long, X // LDA long, X
uint32_t AbsoluteLongIndexedX(); uint32_t AbsoluteLongIndexedX();
uint32_t AdrAlx(uint32_t* low);
// Source Effective Address: // Source Effective Address:
// Bank: Second operand byte // Bank: Second operand byte
@@ -238,6 +336,7 @@ class Cpu : public memory::Memory,
// //
// LDA dp // LDA dp
uint16_t DirectPage(); uint16_t DirectPage();
uint32_t AdrDp(uint32_t* low);
// Effective Address: // Effective Address:
// Bank: Zero // Bank: Zero
@@ -246,6 +345,7 @@ class Cpu : public memory::Memory,
// //
// LDA dp, X // LDA dp, X
uint16_t DirectPageIndexedX(); uint16_t DirectPageIndexedX();
uint32_t AdrDpx(uint32_t* low);
// Effective Address: // Effective Address:
// Bank: Zero // Bank: Zero
@@ -253,6 +353,7 @@ class Cpu : public memory::Memory,
// based on the emulation mode // based on the emulation mode
// LDA dp, Y // LDA dp, Y
uint16_t DirectPageIndexedY(); uint16_t DirectPageIndexedY();
uint32_t AdrDpy(uint32_t* low);
// Effective Address: // Effective Address:
// Bank: Data bank register // Bank: Data bank register
@@ -310,6 +411,7 @@ class Cpu : public memory::Memory,
uint16_t Immediate(bool index_size = false); uint16_t Immediate(bool index_size = false);
uint16_t StackRelative(); uint16_t StackRelative();
uint32_t AdrSr(uint32_t* low);
// Effective Address: // Effective Address:
// The Data Bank Register is concatenated to the Indirect Address; // The Data Bank Register is concatenated to the Indirect Address;
@@ -321,88 +423,6 @@ class Cpu : public memory::Memory,
// LDA (sr, S), Y // LDA (sr, S), Y
uint32_t StackRelativeIndirectIndexedY(); uint32_t StackRelativeIndirectIndexedY();
// Memory access routines
uint8_t ReadByte(uint32_t address) const override {
return memory.ReadByte(address);
}
uint16_t ReadWord(uint32_t address) const override {
return memory.ReadWord(address);
}
uint32_t ReadWordLong(uint32_t address) const override {
return memory.ReadWordLong(address);
}
std::vector<uint8_t> ReadByteVector(uint32_t address,
uint16_t size) const override {
return memory.ReadByteVector(address, size);
}
void WriteByte(uint32_t address, uint8_t value) override {
memory.WriteByte(address, value);
}
void WriteWord(uint32_t address, uint16_t value) override {
memory.WriteWord(address, value);
}
void WriteLong(uint32_t address, uint32_t value) override {
memory.WriteLong(address, value);
}
uint8_t FetchByte() {
uint32_t address = (PB << 16) | PC + 1;
uint8_t byte = memory.ReadByte(address);
return byte;
}
uint16_t FetchWord() {
uint32_t address = (PB << 16) | PC + 1;
uint16_t value = memory.ReadWord(address);
return value;
}
uint32_t FetchLong() {
uint32_t value = memory.ReadWordLong((PB << 16) | PC + 1);
return value;
}
int8_t FetchSignedByte() { return static_cast<int8_t>(FetchByte()); }
int16_t FetchSignedWord() {
auto offset = static_cast<int16_t>(FetchWord());
return offset;
}
uint8_t FetchByteDirectPage(uint8_t operand) {
uint16_t distance = D * 0x100;
// Calculate the effective address in the Direct Page
uint16_t effectiveAddress = operand + distance;
// Fetch the byte from memory
uint8_t fetchedByte = memory.ReadByte(effectiveAddress);
next_pc_ = PC + 1;
return fetchedByte;
}
uint16_t ReadByteOrWord(uint32_t address) {
if (GetAccumulatorSize()) {
// 8-bit mode
return memory.ReadByte(address) & 0xFF;
} else {
// 16-bit mode
return memory.ReadWord(address);
}
}
void PushByte(uint8_t value) override { memory.PushByte(value); }
void PushWord(uint16_t value) override { memory.PushWord(value); }
uint8_t PopByte() override { return memory.PopByte(); }
uint16_t PopWord() override { return memory.PopWord(); }
void PushLong(uint32_t value) override { memory.PushLong(value); }
uint32_t PopLong() override { return memory.PopLong(); }
// ====================================================== // ======================================================
// Instructions // Instructions
@@ -501,13 +521,13 @@ class Cpu : public memory::Memory,
void JMP(uint16_t address); void JMP(uint16_t address);
// JML: Jump long // JML: Jump long
void JML(uint32_t address); void JML(uint16_t address);
// JSR: Jump to subroutine // JSR: Jump to subroutine
void JSR(uint16_t address); void JSR(uint16_t address);
// JSL: Jump to subroutine long // JSL: Jump to subroutine long
void JSL(uint32_t address); void JSL(uint16_t address);
// LDA: Load accumulator // LDA: Load accumulator
void LDA(uint16_t address, bool immediate = false, bool direct_page = false, void LDA(uint16_t address, bool immediate = false, bool direct_page = false,
@@ -523,16 +543,16 @@ class Cpu : public memory::Memory,
void LSR(uint16_t address, bool accumulator = false); void LSR(uint16_t address, bool accumulator = false);
// MVN: Block move next // MVN: Block move next
void MVN(uint16_t source, uint16_t dest, uint16_t length); void MVN();
// MVP: Block move previous // MVP: Block move previous
void MVP(uint16_t source, uint16_t dest, uint16_t length); void MVP();
// NOP: No operation // NOP: No operation
void NOP(); void NOP();
// ORA: Logical inclusive OR // ORA: Logical inclusive OR
void ORA(uint16_t address, bool immediate = false); void ORA(uint32_t low, uint32_t high);
// PEA: Push effective absolute address // PEA: Push effective absolute address
void PEA(); void PEA();
@@ -684,6 +704,57 @@ class Cpu : public memory::Memory,
// XCE: Exchange carry and emulation bits // XCE: Exchange carry and emulation bits
void XCE(); void XCE();
void And(uint32_t low, uint32_t high);
void Eor(uint32_t low, uint32_t high);
void Adc(uint32_t low, uint32_t high);
void Sbc(uint32_t low, uint32_t high);
void Cmp(uint32_t low, uint32_t high);
void Cpx(uint32_t low, uint32_t high);
void Cpy(uint32_t low, uint32_t high);
void Bit(uint32_t low, uint32_t high);
void Lda(uint32_t low, uint32_t high);
void Ldx(uint32_t low, uint32_t high);
void Ldy(uint32_t low, uint32_t high);
void Sta(uint32_t low, uint32_t high);
void Stx(uint32_t low, uint32_t high);
void Sty(uint32_t low, uint32_t high);
void Stz(uint32_t low, uint32_t high);
void Ror(uint32_t low, uint32_t high);
void Rol(uint32_t low, uint32_t high);
void Lsr(uint32_t low, uint32_t high);
void Asl(uint32_t low, uint32_t high);
void Inc(uint32_t low, uint32_t high);
void Dec(uint32_t low, uint32_t high);
void Tsb(uint32_t low, uint32_t high);
void Trb(uint32_t low, uint32_t high);
uint16_t SP() const { return memory.SP(); }
void SetSP(uint16_t value) { memory.SetSP(value); }
bool IsBreakpoint(uint32_t address) {
return std::find(breakpoints_.begin(), breakpoints_.end(), address) !=
breakpoints_.end();
}
void SetBreakpoint(uint32_t address) { breakpoints_.push_back(address); }
void ClearBreakpoint(uint32_t address) {
breakpoints_.erase(
std::remove(breakpoints_.begin(), breakpoints_.end(), address),
breakpoints_.end());
}
void ClearBreakpoints() {
breakpoints_.clear();
breakpoints_.shrink_to_fit();
}
auto GetBreakpoints() { return breakpoints_; }
void CheckInt() {
int_wanted_ =
(nmi_wanted_ || (irq_wanted_ && !GetInterruptFlag())) && !int_delay_;
int_delay_ = false;
}
auto mutable_log_instructions() -> bool* { return &log_instructions_; }
private: private:
void compare(uint16_t register_value, uint16_t memory_value) { void compare(uint16_t register_value, uint16_t memory_value) {
uint16_t result; uint16_t result;
@@ -711,14 +782,20 @@ class Cpu : public memory::Memory,
} }
bool GetFlag(uint8_t mask) const { return (status & mask) != 0; } bool GetFlag(uint8_t mask) const { return (status & mask) != 0; }
void ClearMemory() override { memory.ClearMemory(); }
uint8_t operator[](int i) const override { return 0; }
uint8_t at(int i) const override { return 0; }
uint16_t last_call_frame_; bool log_instructions_ = false;
uint16_t next_pc_;
Memory& memory; bool waiting_ = false;
bool stopped_ = false;
bool irq_wanted_ = false;
bool nmi_wanted_ = false;
bool reset_wanted_ = false;
bool int_wanted_ = false;
bool int_delay_ = false;
memory::CpuCallbacks callbacks_;
memory::Memory& memory;
Clock& clock; Clock& clock;
}; };

View File

@@ -4,120 +4,187 @@ namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
uint32_t Cpu::Absolute(Cpu::AccessType access_type) { void Cpu::AdrImp() {
auto operand = FetchWord(); // only for 2-cycle implied opcodes
uint32_t bank = CheckInt();
(access_type == Cpu::AccessType::Data) ? (DB << 16) : (PB << 16); if (int_wanted_) {
return bank | (operand & 0xFFFF); // if interrupt detected in 2-cycle implied/accumulator opcode,
} // idle cycle turns into read from pc
ReadByte((PB << 16) | PC);
uint32_t Cpu::AbsoluteIndexedX() { } else {
uint16_t address = memory.ReadWord((PB << 16) | (PC + 1)); callbacks_.idle(false);
uint32_t effective_address = (DB << 16) | ((address + X) & 0xFFFF);
return effective_address;
}
uint32_t Cpu::AbsoluteIndexedY() {
uint16_t address = memory.ReadWord((PB << 16) | (PC + 1));
uint32_t effective_address = (DB << 16) | address + Y;
return effective_address;
}
uint16_t Cpu::AbsoluteIndexedIndirect() {
uint16_t address = FetchWord() + X;
return memory.ReadWord((DB << 16) | address & 0xFFFF);
}
uint16_t Cpu::AbsoluteIndirect() {
uint16_t address = FetchWord();
return memory.ReadWord((PB << 16) | address);
}
uint32_t Cpu::AbsoluteIndirectLong() {
uint16_t address = FetchWord();
return memory.ReadWordLong((PB << 16) | address);
}
uint32_t Cpu::AbsoluteLong() { return FetchLong(); }
uint32_t Cpu::AbsoluteLongIndexedX() { return FetchLong() + X; }
void Cpu::BlockMove(uint16_t source, uint16_t dest, uint16_t length) {
for (int i = 0; i < length; i++) {
memory.WriteByte(dest + i, memory.ReadByte(source + i));
} }
} }
uint32_t Cpu::Immediate(uint32_t* low, bool xFlag) {
if ((xFlag && GetIndexSize()) || (!xFlag && GetAccumulatorSize())) {
*low = (PB << 16) | PC++;
return 0;
} else {
*low = (PB << 16) | PC++;
return (PB << 16) | PC++;
}
}
uint32_t Cpu::AdrDpx(uint32_t* low) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
callbacks_.idle(false);
*low = (D + adr + X) & 0xffff;
return (D + adr + X + 1) & 0xffff;
}
uint32_t Cpu::AdrDpy(uint32_t* low) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
callbacks_.idle(false);
*low = (D + adr + Y) & 0xffff;
return (D + adr + Y + 1) & 0xffff;
}
uint32_t Cpu::AdrIdp(uint32_t* low) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
uint16_t pointer = ReadWord((D + adr) & 0xffff, false);
*low = (DB << 16) + pointer;
return ((DB << 16) + pointer + 1) & 0xffffff;
}
uint32_t Cpu::AdrIdy(uint32_t* low, bool write) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
uint16_t pointer = ReadWord((D + adr) & 0xffff, false);
// writing opcode or x = 0 or page crossed: 1 extra cycle
if (write || !GetIndexSize() || ((pointer >> 8) != ((pointer + Y) >> 8)))
callbacks_.idle(false);
*low = ((DB << 16) + pointer + Y) & 0xffffff;
return ((DB << 16) + pointer + Y + 1) & 0xffffff;
}
uint32_t Cpu::AdrIdl(uint32_t* low) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
uint32_t pointer = ReadWord((D + adr) & 0xffff, false);
pointer |= ReadByte((D + adr + 2) & 0xffff) << 16;
*low = pointer;
return (pointer + 1) & 0xffffff;
}
uint32_t Cpu::AdrIly(uint32_t* low) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
uint32_t pointer = ReadWord((D + adr) & 0xffff, false);
pointer |= ReadByte((D + adr + 2) & 0xffff) << 16;
*low = (pointer + Y) & 0xffffff;
return (pointer + Y + 1) & 0xffffff;
}
uint32_t Cpu::AdrSr(uint32_t* low) {
uint8_t adr = ReadOpcode();
callbacks_.idle(false);
*low = (SP() + adr) & 0xffff;
return (SP() + adr + 1) & 0xffff;
}
uint32_t Cpu::AdrIsy(uint32_t* low) {
uint8_t adr = ReadOpcode();
callbacks_.idle(false);
uint16_t pointer = ReadWord((SP() + adr) & 0xffff, false);
callbacks_.idle(false);
*low = ((DB << 16) + pointer + Y) & 0xffffff;
return ((DB << 16) + pointer + Y + 1) & 0xffffff;
}
uint32_t Cpu::Absolute(uint32_t* low) {
uint16_t adr = ReadOpcodeWord(false);
*low = (DB << 16) + adr;
return ((DB << 16) + adr + 1) & 0xffffff;
}
uint32_t Cpu::AdrAbx(uint32_t* low, bool write) {
uint16_t adr = ReadOpcodeWord(false);
// writing opcode or x = 0 or page crossed: 1 extra cycle
if (write || !GetIndexSize() || ((adr >> 8) != ((adr + X) >> 8)))
callbacks_.idle(false);
*low = ((DB << 16) + adr + X) & 0xffffff;
return ((DB << 16) + adr + X + 1) & 0xffffff;
}
uint32_t Cpu::AdrAby(uint32_t* low, bool write) {
uint16_t adr = ReadOpcodeWord(false);
// writing opcode or x = 0 or page crossed: 1 extra cycle
if (write || !GetIndexSize() || ((adr >> 8) != ((adr + Y) >> 8)))
callbacks_.idle(false);
*low = ((DB << 16) + adr + Y) & 0xffffff;
return ((DB << 16) + adr + Y + 1) & 0xffffff;
}
uint32_t Cpu::AdrAbl(uint32_t* low) {
uint32_t adr = ReadOpcodeWord(false);
adr |= ReadOpcode() << 16;
*low = adr;
return (adr + 1) & 0xffffff;
}
uint32_t Cpu::AdrAlx(uint32_t* low) {
uint32_t adr = ReadOpcodeWord(false);
adr |= ReadOpcode() << 16;
*low = (adr + X) & 0xffffff;
return (adr + X + 1) & 0xffffff;
}
uint32_t Cpu::AdrDp(uint32_t* low) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle
*low = (D + adr) & 0xffff;
return (D + adr + 1) & 0xffff;
}
uint16_t Cpu::DirectPage() { uint16_t Cpu::DirectPage() {
uint8_t dp = FetchByte(); uint8_t dp = ReadOpcode();
return D + dp; return D + dp;
} }
uint16_t Cpu::DirectPageIndexedX() { uint16_t Cpu::DirectPageIndexedX() {
uint8_t operand = FetchByte(); uint8_t operand = ReadOpcode();
uint16_t x_by_mode = GetAccumulatorSize() ? X : X & 0xFF; uint16_t x_by_mode = GetAccumulatorSize() ? X : X & 0xFF;
return D + operand + x_by_mode; return D + operand + x_by_mode;
} }
uint16_t Cpu::DirectPageIndexedY() { uint16_t Cpu::DirectPageIndexedY() {
uint8_t operand = FetchByte(); uint8_t operand = ReadOpcode();
return (operand + Y) & 0xFF; return (operand + Y) & 0xFF;
} }
uint16_t Cpu::DirectPageIndexedIndirectX() { uint32_t Cpu::AdrIdx(uint32_t* low) {
uint8_t operand = FetchByte(); uint8_t adr = ReadOpcode();
uint16_t indirect_address = D + operand + X; if (D & 0xff) callbacks_.idle(false);
uint16_t effective_address = memory.ReadWord(indirect_address & 0xFFFF); callbacks_.idle(false);
return effective_address; uint16_t pointer = ReadWord((D + adr + X) & 0xffff, false);
} *low = (DB << 16) + pointer;
return ((DB << 16) + pointer + 1) & 0xffffff;
uint16_t Cpu::DirectPageIndirect() {
uint8_t dp = FetchByte();
uint16_t effective_address = D + dp;
return memory.ReadWord(effective_address);
} }
uint32_t Cpu::DirectPageIndirectLong() { uint32_t Cpu::DirectPageIndirectLong() {
uint8_t dp = FetchByte(); uint8_t dp = ReadOpcode();
uint16_t effective_address = D + dp; uint16_t effective_address = D + dp;
return memory.ReadWordLong((0x00 << 0x10) | effective_address); return ReadWordLong((0x00 << 0x10) | effective_address);
}
uint16_t Cpu::DirectPageIndirectIndexedY() {
uint8_t operand = FetchByte();
uint16_t indirect_address = D + operand;
return memory.ReadWord(indirect_address) + Y;
} }
uint32_t Cpu::DirectPageIndirectLongIndexedY() { uint32_t Cpu::DirectPageIndirectLongIndexedY() {
uint8_t operand = FetchByte(); uint8_t operand = ReadOpcode();
uint16_t indirect_address = D + operand; uint16_t indirect_address = D + operand;
uint16_t y_by_mode = GetAccumulatorSize() ? Y : Y & 0xFF; uint16_t y_by_mode = GetAccumulatorSize() ? Y : Y & 0xFF;
uint32_t effective_address = uint32_t effective_address = ReadWordLong(indirect_address) + y_by_mode;
memory.ReadWordLong(indirect_address) + y_by_mode;
return effective_address; return effective_address;
} }
uint16_t Cpu::Immediate(bool index_size) {
bool bit_mode = index_size ? GetIndexSize() : GetAccumulatorSize();
if (bit_mode) {
return memory.ReadByte((PB << 16) | PC + 1);
} else {
return memory.ReadWord((PB << 16) | PC + 1);
}
}
uint16_t Cpu::StackRelative() { uint16_t Cpu::StackRelative() {
uint8_t sr = FetchByte(); uint8_t sr = ReadOpcode();
uint16_t effective_address = SP() + sr; uint16_t effective_address = SP() + sr;
return effective_address; return effective_address;
} }
uint32_t Cpu::StackRelativeIndirectIndexedY() {
uint8_t sr = FetchByte();
return (DB << 0x10) | (memory.ReadWord(SP() + sr) + Y);
}
} // namespace emu } // namespace emu
} // namespace app } // namespace app
} // namespace yaze } // namespace yaze

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,43 @@ const std::unordered_map<uint8_t, std::string> opcode_to_mnemonic = {
{0xF0, "BEQ"}, {0xF1, "SBC"}, {0xF2, "SBC"}, {0xF3, "SBC"}, {0xF4, "PEA"}, {0xF0, "BEQ"}, {0xF1, "SBC"}, {0xF2, "SBC"}, {0xF3, "SBC"}, {0xF4, "PEA"},
{0xF5, "SBC"}, {0xF6, "INC"}, {0xF7, "SBC"}, {0xF8, "SED"}, {0xF9, "SBC"}, {0xF5, "SBC"}, {0xF6, "INC"}, {0xF7, "SBC"}, {0xF8, "SED"}, {0xF9, "SBC"},
{0xFA, "PLX"}, {0xFB, "XCE"}, {0xFC, "JSR"}, {0xFD, "SBC"}, {0xFE, "INC"}, {0xFA, "PLX"}, {0xFB, "XCE"}, {0xFC, "JSR"}, {0xFD, "SBC"}, {0xFE, "INC"},
{0xFF, "SBC"} {0xFF, "SBC"}};
}; const std::unordered_map<uint8_t, uint8_t> opcode_to_cycle_count = {
{0x00, 7}, {0x01, 6}, {0x02, 8}, {0x03, 8}, {0x04, 3}, {0x05, 3}, {0x06, 5},
{0x07, 5}, {0x08, 3}, {0x09, 2}, {0x0A, 2}, {0x0B, 4}, {0x0C, 4}, {0x0D, 4},
{0x0E, 6}, {0x0F, 6}, {0x10, 2}, {0x11, 5}, {0x12, 6}, {0x13, 8}, {0x14, 4},
{0x15, 4}, {0x16, 6}, {0x17, 6}, {0x18, 2}, {0x19, 4}, {0x1A, 2}, {0x1B, 4},
{0x1C, 4}, {0x1D, 4}, {0x1E, 7}, {0x1F, 7}, {0x20, 6}, {0x21, 6}, {0x22, 8},
{0x23, 8}, {0x24, 3}, {0x25, 3}, {0x26, 5}, {0x27, 5}, {0x28, 4}, {0x29, 2},
{0x2A, 2}, {0x2B, 4}, {0x2C, 4}, {0x2D, 4}, {0x2E, 6}, {0x2F, 6}, {0x30, 2},
{0x31, 5}, {0x32, 6}, {0x33, 8}, {0x34, 4}, {0x35, 4}, {0x36, 6}, {0x37, 6},
{0x38, 2}, {0x39, 4}, {0x3A, 2}, {0x3B, 4}, {0x3C, 4}, {0x3D, 4}, {0x3E, 7},
{0x3F, 7}, {0x40, 6}, {0x41, 6}, {0x42, 8}, {0x43, 8}, {0x44, 3}, {0x45, 3},
{0x46, 5}, {0x47, 5}, {0x48, 3}, {0x49, 2}, {0x4A, 2}, {0x4B, 4}, {0x4C, 3},
{0x4D, 4}, {0x4E, 6}, {0x4F, 6}, {0x50, 2}, {0x51, 5}, {0x52, 6}, {0x53, 8},
{0x54, 4}, {0x55, 4}, {0x56, 6}, {0x57, 6}, {0x58, 2}, {0x59, 4}, {0x5A, 2},
{0x5B, 4}, {0x5C, 4}, {0x5D, 4}, {0x5E, 7}, {0x5F, 7}, {0x60, 6}, {0x61, 6},
{0x62, 8}, {0x63, 8}, {0x64, 3}, {0x65, 3}, {0x66, 5}, {0x67, 5}, {0x68, 4},
{0x69, 2}, {0x6A, 2}, {0x6B, 4}, {0x6C, 5}, {0x6D, 4}, {0x6E, 6}, {0x6F, 6},
{0x70, 2}, {0x71, 5}, {0x72, 6}, {0x73, 8}, {0x74, 4}, {0x75, 4}, {0x76, 6},
{0x77, 6}, {0x78, 2}, {0x79, 4}, {0x7A, 2}, {0x7B, 4}, {0x7C, 6}, {0x7D, 4},
{0x7E, 7}, {0x7F, 7}, {0x80, 2}, {0x81, 6}, {0x82, 5}, {0x83, 8}, {0x84, 3},
{0x85, 3}, {0x86, 3}, {0x87, 5}, {0x88, 2}, {0x89, 2}, {0x8A, 2}, {0x8B, 4},
{0x8C, 4}, {0x8D, 4}, {0x8E, 4}, {0x8F, 6}, {0x90, 2}, {0x91, 6}, {0x92, 5},
{0x93, 8}, {0x94, 4}, {0x95, 4}, {0x96, 4}, {0x97, 6}, {0x98, 2}, {0x99, 5},
{0x9A, 2}, {0x9B, 4}, {0x9C, 4}, {0x9D, 4}, {0x9E, 4}, {0x9F, 5}, {0xA0, 2},
{0xA1, 6}, {0xA2, 2}, {0xA3, 8}, {0xA4, 3}, {0xA5, 3}, {0xA6, 3}, {0xA7, 5},
{0xA8, 2}, {0xA9, 2}, {0xAA, 2}, {0xAB, 4}, {0xAC, 4}, {0xAD, 4}, {0xAE, 4},
{0xAF, 6}, {0xB0, 2}, {0xB1, 5}, {0xB2, 5}, {0xB3, 8}, {0xB4, 4}, {0xB5, 4},
{0xB6, 4}, {0xB7, 6}, {0xB8, 2}, {0xB9, 4}, {0xBA, 2}, {0xBB, 4}, {0xBC, 4},
{0xBD, 4}, {0xBE, 4}, {0xBF, 5}, {0xC0, 2}, {0xC1, 6}, {0xC2, 2}, {0xC3, 8},
{0xC4, 3}, {0xC5, 3}, {0xC6, 5}, {0xC7, 5}, {0xC8, 2}, {0xC9, 2}, {0xCA, 2},
{0xCB, 4}, {0xCC, 4}, {0xCD, 4}, {0xCE, 6}, {0xCF, 6}, {0xD0, 2}, {0xD1, 5},
{0xD2, 5}, {0xD3, 8}, {0xD4, 4}, {0xD5, 4}, {0xD6, 6}, {0xD7, 6}, {0xD8, 2},
{0xD9, 4}, {0xDA, 2}, {0xDB, 4}, {0xDC, 3}, {0xDD, 4}, {0xDE, 7}, {0xDF, 7},
{0xE0, 2}, {0xE1, 6}, {0xE2, 2}, {0xE3, 8}, {0xE4, 3}, {0xE5, 3}, {0xE6, 5},
{0xE7, 5}, {0xE8, 2}, {0xE9, 2}, {0xEA, 2}, {0xEB, 2}, {0xEC, 4}, {0xED, 4},
{0xEE, 6}, {0xEF, 6}, {0xF0, 2}, {0xF1, 5}, {0xF2, 5}, {0xF3, 8}, {0xF4, 4},
{0xF5, 4}, {0xF6, 6}, {0xF7, 6}, {0xF8, 2}, {0xF9, 4}, {0xFA, 2}, {0xFB, 2},
{0xFC, 3}, {0xFD, 4}, {0xFE, 7}, {0xFF, 7}};

View File

@@ -8,109 +8,439 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "app/emu/cpu/internal/opcodes.h" #include "app/emu/cpu/internal/opcodes.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
enum class AddressingMode {
kAbsolute,
kAbsoluteLong,
kAbsoluteIndexedIndirect,
kAbsoluteIndexedX,
kAbsoluteIndexedY,
kAbsoluteIndirect,
kAbsoluteIndirectLong,
kAbsoluteLongIndexedX,
kAccumulator,
kBlockMove,
kDirectPage,
kDirectPageIndexedX,
kDirectPageIndexedY,
kDirectPageIndirect,
kDirectPageIndirectIndexedY,
kDirectPageIndirectLong,
kDirectPageIndirectLongIndexedY,
kDirectPageIndirectIndexedX,
kDirectPageIndirectLongIndexedX,
kImmediate,
kImplied,
kProgramCounterRelative,
kProgramCounterRelativeLong,
kStackRelative,
kStackRelativeIndirectIndexedY,
kStackRelativeIndirectIndexedYLong,
kStack,
kStackRelativeIndexedY,
};
// Key structure for mnemonic and addressing mode
struct MnemonicMode {
std::string mnemonic;
AddressingMode mode;
bool operator==(const MnemonicMode& other) const {
return mnemonic == other.mnemonic && mode == other.mode;
}
};
// Custom hash function for the MnemonicMode structure
struct MnemonicModeHash {
std::size_t operator()(const MnemonicMode& k) const {
return std::hash<std::string>()(k.mnemonic) ^
(std::hash<int>()(static_cast<int>(k.mode)) << 1);
}
};
class AsmParser { class AsmParser {
public: public:
std::vector<uint8_t> Parse(const std::string& instruction) { std::vector<uint8_t> Parse(const std::string& instruction) {
std::smatch match; CreateInternalOpcodeMap();
if (!std::regex_match(instruction, match, instruction_regex_)) { auto tokens = Tokenize(instruction);
if (tokens.size() < 1) {
throw std::runtime_error("Invalid instruction format: " + instruction); throw std::runtime_error("Invalid instruction format: " + instruction);
} }
std::string mnemonic = match[1]; size_t index = 0;
std::string addressing_mode = match[2]; std::vector<uint8_t> bytes;
std::string operand = match[3]; while (index < tokens.size()) {
// For each "line" worth of tokens, we need to extract the
// mnemonic, optional addressing mode qualifier, and operand.
// The operand can come in a variety of formats:
// - Immediate: #$01
// - Immediate Word: #$1234
// - Absolute: $1234
// - Absolute Long: $123456
// This parser is not exhaustive and only supports a subset of
// the possible addressing modes and operands.
const std::string& mnemonic = tokens[index];
index++;
std::string lookup_string = mnemonic.substr(0, 3); // Check if addressing mode qualifier is present
// Either .b, .w, .l, or nothing, which could mean
// it was omitted or the operand is implied
std::string qualifier = "";
std::string potential_mode = tokens[index];
if (absl::StrContains(potential_mode, ".")) {
qualifier = potential_mode;
index++;
}
auto opcode_entry = mnemonic_to_opcode_.find(mnemonic); // Now we check for either the immediate mode
if (opcode_entry == mnemonic_to_opcode_.end()) { // symbol # or the address symbol $ to determine
throw std::runtime_error( // the next step
"Unknown mnemonic or addressing mode: " + mnemonic + addressing_mode); std::string operand = tokens[index];
if (operand == "#") {
index++;
// Check if the next token is a # character, in which case it is
// a hexadecimal value that needs to be converted to a byte
if (tokens[index] == "#") {
index++;
operand = tokens[index];
index++;
}
} else if (operand == "$") {
index++;
operand = tokens[index];
index++;
}
AddressingMode mode = DetermineMode(tokens);
MnemonicMode key{mnemonic, mode};
auto opcode_entry = mnemonic_to_opcode_.find(key);
if (opcode_entry == mnemonic_to_opcode_.end()) {
throw std::runtime_error("Opcode not found for mnemonic and mode: " +
mnemonic);
}
bytes.push_back(opcode_entry->second);
AppendOperandBytes(bytes, operand, mode);
} }
std::vector<uint8_t> bytes = {opcode_entry->second};
// AppendOperandBytes(bytes, operand, addressing_mode);
return bytes; return bytes;
} }
void CreateInternalOpcodeMap() { // Example: ADC.b #$01
for (const auto& opcode_entry : opcode_to_mnemonic) { // Returns: ["ADC", ".b", "#", "$", "01"]
std::string name = opcode_entry.second; std::vector<std::string> Tokenize(const std::string& instruction) {
uint8_t opcode = opcode_entry.first; std::vector<std::string> tokens;
mnemonic_to_opcode_[name] = opcode; std::regex tokenRegex{R"((\w+|\.\w+|[\#$]|[0-9a-fA-F]+|[a-zA-Z]+))"};
auto words_begin = std::sregex_iterator(instruction.begin(),
instruction.end(), tokenRegex);
auto words_end = std::sregex_iterator();
for (std::sregex_iterator i = words_begin; i != words_end; ++i) {
std::smatch match = *i;
tokens.push_back(match.str());
} }
return tokens;
} }
private: private:
void AppendOperandBytes(std::vector<uint8_t>& bytes, void AppendOperandBytes(std::vector<uint8_t>& bytes,
const std::string& operand, const std::string& operand,
const std::string& addressing_mode) { const AddressingMode& addressing_mode) {
if (addressing_mode == ".b") { // Handle different addressing modes
bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16))); switch (addressing_mode) {
} else if (addressing_mode == ".w") { case AddressingMode::kImmediate: {
uint16_t word_operand = bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16)));
static_cast<uint16_t>(std::stoi(operand, nullptr, 16)); break;
bytes.push_back(static_cast<uint8_t>(word_operand & 0xFF)); }
bytes.push_back(static_cast<uint8_t>((word_operand >> 8) & 0xFF)); case AddressingMode::kAbsolute: {
} else if (addressing_mode == ".l") { uint16_t word_operand =
uint32_t long_operand = static_cast<uint16_t>(std::stoi(operand, nullptr, 16));
static_cast<uint32_t>(std::stoul(operand, nullptr, 16)); bytes.push_back(static_cast<uint8_t>(word_operand & 0xFF));
bytes.push_back(static_cast<uint8_t>(long_operand & 0xFF)); bytes.push_back(static_cast<uint8_t>((word_operand >> 8) & 0xFF));
bytes.push_back(static_cast<uint8_t>((long_operand >> 8) & 0xFF)); break;
bytes.push_back(static_cast<uint8_t>((long_operand >> 16) & 0xFF)); }
case AddressingMode::kAbsoluteLong: {
uint32_t long_operand =
static_cast<uint32_t>(std::stoul(operand, nullptr, 16));
bytes.push_back(static_cast<uint8_t>(long_operand & 0xFF));
bytes.push_back(static_cast<uint8_t>((long_operand >> 8) & 0xFF));
bytes.push_back(static_cast<uint8_t>((long_operand >> 16) & 0xFF));
break;
}
case AddressingMode::kImplied: {
break;
}
default:
// Unknown, append it anyway
bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16)));
} }
} }
enum class AddressingMode { AddressingMode DetermineMode(const std::vector<std::string>& tokens) {
kAbsolute, const std::string& addressingMode = tokens[1];
kAbsoluteLong, if (addressingMode == ".b") {
kAbsoluteIndexedIndirect,
kAbsoluteIndexedX,
kAbsoluteIndexedY,
kAbsoluteIndirect,
kAbsoluteIndirectLong,
kAbsoluteLongIndexedX,
kAccumulator,
kBlockMove,
kDirectPage,
kDirectPageIndexedX,
kDirectPageIndexedY,
kDirectPageIndirect,
kDirectPageIndirectIndexedY,
kDirectPageIndirectLong,
kDirectPageIndirectLongIndexedY,
kDirectPageIndirectIndexedX,
kDirectPageIndirectLongIndexedX,
kImmediate,
kImplied,
kProgramCounterRelative,
kProgramCounterRelativeLong,
kStackRelative,
kStackRelativeIndirectIndexedY,
kStackRelativeIndirectIndexedYLong,
kStack,
kStackRelativeIndexedY,
};
AddressingMode InferAddressingModeFromOperand(const std::string& operand) {
if (operand[0] == '$') {
return AddressingMode::kAbsolute;
} else if (operand[0] == '#') {
return AddressingMode::kImmediate; return AddressingMode::kImmediate;
} else if (addressingMode == ".w") {
return AddressingMode::kAbsolute;
} else if (addressingMode == ".l") {
return AddressingMode::kAbsoluteLong;
} else { } else {
return AddressingMode::kImplied; return AddressingMode::kImplied;
} }
} }
const std::regex instruction_regex_{R"((\w+)\s*(\.\w)?\s*(\$\w+|\#\w+|\w+))"}; bool TryParseByte(const std::string& str, uint8_t& value) {
std::unordered_map<std::string, uint8_t> mnemonic_to_opcode_; try {
value = std::stoi(str, nullptr, 16);
return true;
} catch (const std::invalid_argument& e) {
return false;
}
}
bool TryParseHex(const std::string& str, uint32_t& value) {
try {
value = std::stoul(str, nullptr, 16);
return true;
} catch (const std::invalid_argument& e) {
return false;
}
}
void CreateInternalOpcodeMap() {
mnemonic_to_opcode_[{"ADC", AddressingMode::kImmediate}] = 0x69;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPage}] = 0x65;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPageIndexedX}] = 0x75;
mnemonic_to_opcode_[{"ADC", AddressingMode::kAbsolute}] = 0x6D;
mnemonic_to_opcode_[{"ADC", AddressingMode::kAbsoluteIndexedX}] = 0x7D;
mnemonic_to_opcode_[{"ADC", AddressingMode::kAbsoluteIndexedY}] = 0x79;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPageIndirect}] = 0x61;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPageIndirectIndexedY}] =
0x71;
mnemonic_to_opcode_[{"ADC", AddressingMode::kStackRelative}] = 0x63;
mnemonic_to_opcode_[{
"ADC", AddressingMode::kStackRelativeIndirectIndexedY}] = 0x73;
mnemonic_to_opcode_[{"ADC", AddressingMode::kImmediate}] = 0x69;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPage}] = 0x65;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPageIndexedX}] = 0x75;
mnemonic_to_opcode_[{"ADC", AddressingMode::kAbsolute}] = 0x6D;
mnemonic_to_opcode_[{"ADC", AddressingMode::kAbsoluteIndexedX}] = 0x7D;
mnemonic_to_opcode_[{"ADC", AddressingMode::kAbsoluteIndexedY}] = 0x79;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPageIndirect}] = 0x61;
mnemonic_to_opcode_[{"ADC", AddressingMode::kDirectPageIndirectIndexedY}] =
0x71;
mnemonic_to_opcode_[{"ADC", AddressingMode::kStackRelative}] = 0x63;
mnemonic_to_opcode_[{
"ADC", AddressingMode::kStackRelativeIndirectIndexedY}] = 0x73;
mnemonic_to_opcode_[{"AND", AddressingMode::kImmediate}] = 0x29;
mnemonic_to_opcode_[{"AND", AddressingMode::kDirectPage}] = 0x25;
mnemonic_to_opcode_[{"AND", AddressingMode::kDirectPageIndexedX}] = 0x35;
mnemonic_to_opcode_[{"AND", AddressingMode::kAbsolute}] = 0x2D;
mnemonic_to_opcode_[{"AND", AddressingMode::kAbsoluteIndexedX}] = 0x3D;
mnemonic_to_opcode_[{"AND", AddressingMode::kAbsoluteIndexedY}] = 0x39;
mnemonic_to_opcode_[{"AND", AddressingMode::kDirectPageIndirect}] = 0x21;
mnemonic_to_opcode_[{"AND", AddressingMode::kDirectPageIndirectIndexedY}] =
0x31;
mnemonic_to_opcode_[{"AND", AddressingMode::kStackRelative}] = 0x23;
mnemonic_to_opcode_[{
"AND", AddressingMode::kStackRelativeIndirectIndexedY}] = 0x33;
mnemonic_to_opcode_[{"ASL", AddressingMode::kAccumulator}] = 0x0A;
mnemonic_to_opcode_[{"ASL", AddressingMode::kDirectPage}] = 0x06;
mnemonic_to_opcode_[{"ASL", AddressingMode::kDirectPageIndexedX}] = 0x16;
mnemonic_to_opcode_[{"ASL", AddressingMode::kAbsolute}] = 0x0E;
mnemonic_to_opcode_[{"ASL", AddressingMode::kAbsoluteIndexedX}] = 0x1E;
mnemonic_to_opcode_[{"BCC", AddressingMode::kProgramCounterRelative}] =
0x90;
mnemonic_to_opcode_[{"BCS", AddressingMode::kProgramCounterRelative}] =
0xB0;
mnemonic_to_opcode_[{"BEQ", AddressingMode::kProgramCounterRelative}] =
0xF0;
mnemonic_to_opcode_[{"BIT", AddressingMode::kImmediate}] = 0x89;
mnemonic_to_opcode_[{"BIT", AddressingMode::kDirectPage}] = 0x24;
mnemonic_to_opcode_[{"BIT", AddressingMode::kAbsolute}] = 0x2C;
mnemonic_to_opcode_[{"BMI", AddressingMode::kProgramCounterRelative}] =
0x30;
mnemonic_to_opcode_[{"BNE", AddressingMode::kProgramCounterRelative}] =
0xD0;
mnemonic_to_opcode_[{"BPL", AddressingMode::kProgramCounterRelative}] =
0x10;
mnemonic_to_opcode_[{"BRA", AddressingMode::kProgramCounterRelative}] =
0x80;
mnemonic_to_opcode_[{"BRK", AddressingMode::kImplied}] = 0x00;
mnemonic_to_opcode_[{"BRL", AddressingMode::kProgramCounterRelativeLong}] =
0x82;
mnemonic_to_opcode_[{"BVC", AddressingMode::kProgramCounterRelative}] =
0x50;
mnemonic_to_opcode_[{"BVS", AddressingMode::kProgramCounterRelative}] =
0x70;
mnemonic_to_opcode_[{"CLC", AddressingMode::kImplied}] = 0x18;
mnemonic_to_opcode_[{"CLD", AddressingMode::kImplied}] = 0xD8;
mnemonic_to_opcode_[{"CLI", AddressingMode::kImplied}] = 0x58;
mnemonic_to_opcode_[{"CLV", AddressingMode::kImplied}] = 0xB8;
mnemonic_to_opcode_[{"CMP", AddressingMode::kImmediate}] = 0xC9;
mnemonic_to_opcode_[{"CMP", AddressingMode::kDirectPage}] = 0xC5;
mnemonic_to_opcode_[{"CMP", AddressingMode::kDirectPageIndexedX}] = 0xD5;
mnemonic_to_opcode_[{"CMP", AddressingMode::kAbsolute}] = 0xCD;
mnemonic_to_opcode_[{"CMP", AddressingMode::kAbsoluteIndexedX}] = 0xDD;
mnemonic_to_opcode_[{"CMP", AddressingMode::kAbsoluteIndexedY}] = 0xD9;
mnemonic_to_opcode_[{"CMP", AddressingMode::kDirectPageIndirect}] = 0xC1;
mnemonic_to_opcode_[{"CMP", AddressingMode::kDirectPageIndirectIndexedY}] =
0xD1;
mnemonic_to_opcode_[{"COP", AddressingMode::kImmediate}] = 0x02;
mnemonic_to_opcode_[{"CPX", AddressingMode::kImmediate}] = 0xE0;
mnemonic_to_opcode_[{"CPX", AddressingMode::kDirectPage}] = 0xE4;
mnemonic_to_opcode_[{"CPX", AddressingMode::kAbsolute}] = 0xEC;
mnemonic_to_opcode_[{"CPY", AddressingMode::kImmediate}] = 0xC0;
mnemonic_to_opcode_[{"CPY", AddressingMode::kDirectPage}] = 0xC4;
mnemonic_to_opcode_[{"CPY", AddressingMode::kAbsolute}] = 0xCC;
mnemonic_to_opcode_[{"DEC", AddressingMode::kDirectPage}] = 0xC6;
mnemonic_to_opcode_[{"DEC", AddressingMode::kDirectPageIndexedX}] = 0xD6;
mnemonic_to_opcode_[{"DEC", AddressingMode::kAbsolute}] = 0xCE;
mnemonic_to_opcode_[{"DEC", AddressingMode::kAbsoluteIndexedX}] = 0xDE;
mnemonic_to_opcode_[{"DEX", AddressingMode::kImplied}] = 0xCA;
mnemonic_to_opcode_[{"DEY", AddressingMode::kImplied}] = 0x88;
mnemonic_to_opcode_[{"EOR", AddressingMode::kImmediate}] = 0x49;
mnemonic_to_opcode_[{"EOR", AddressingMode::kDirectPage}] = 0x45;
mnemonic_to_opcode_[{"EOR", AddressingMode::kDirectPageIndexedX}] = 0x55;
mnemonic_to_opcode_[{"EOR", AddressingMode::kAbsolute}] = 0x4D;
mnemonic_to_opcode_[{"EOR", AddressingMode::kAbsoluteIndexedX}] = 0x5D;
mnemonic_to_opcode_[{"EOR", AddressingMode::kAbsoluteIndexedY}] = 0x59;
mnemonic_to_opcode_[{"EOR", AddressingMode::kDirectPageIndirect}] = 0x41;
mnemonic_to_opcode_[{"EOR", AddressingMode::kDirectPageIndirectIndexedY}] =
0x51;
mnemonic_to_opcode_[{"INC", AddressingMode::kDirectPage}] = 0xE6;
mnemonic_to_opcode_[{"INC", AddressingMode::kDirectPageIndexedX}] = 0xF6;
mnemonic_to_opcode_[{"INC", AddressingMode::kAbsolute}] = 0xEE;
mnemonic_to_opcode_[{"INC", AddressingMode::kAbsoluteIndexedX}] = 0xFE;
mnemonic_to_opcode_[{"INX", AddressingMode::kImplied}] = 0xE8;
mnemonic_to_opcode_[{"INY", AddressingMode::kImplied}] = 0xC8;
mnemonic_to_opcode_[{"JMP", AddressingMode::kAbsolute}] = 0x4C;
mnemonic_to_opcode_[{"JMP", AddressingMode::kAbsoluteIndirect}] = 0x6C;
mnemonic_to_opcode_[{"JSR", AddressingMode::kAbsolute}] = 0x20;
mnemonic_to_opcode_[{"LDA", AddressingMode::kImmediate}] = 0xA9;
mnemonic_to_opcode_[{"LDA", AddressingMode::kDirectPage}] = 0xA5;
mnemonic_to_opcode_[{"LDA", AddressingMode::kDirectPageIndexedX}] = 0xB5;
mnemonic_to_opcode_[{"LDA", AddressingMode::kAbsolute}] = 0xAD;
mnemonic_to_opcode_[{"LDA", AddressingMode::kAbsoluteIndexedX}] = 0xBD;
mnemonic_to_opcode_[{"LDA", AddressingMode::kAbsoluteIndexedY}] = 0xB9;
mnemonic_to_opcode_[{"LDA", AddressingMode::kDirectPageIndirect}] = 0xA1;
mnemonic_to_opcode_[{"LDA", AddressingMode::kDirectPageIndirectIndexedY}] =
0xB1;
mnemonic_to_opcode_[{"LDX", AddressingMode::kImmediate}] = 0xA2;
mnemonic_to_opcode_[{"LDX", AddressingMode::kDirectPage}] = 0xA6;
mnemonic_to_opcode_[{"LDX", AddressingMode::kDirectPageIndexedY}] = 0xB6;
mnemonic_to_opcode_[{"LDX", AddressingMode::kAbsolute}] = 0xAE;
mnemonic_to_opcode_[{"LDX", AddressingMode::kAbsoluteIndexedY}] = 0xBE;
mnemonic_to_opcode_[{"LDY", AddressingMode::kImmediate}] = 0xA0;
mnemonic_to_opcode_[{"LDY", AddressingMode::kDirectPage}] = 0xA4;
mnemonic_to_opcode_[{"LDY", AddressingMode::kDirectPageIndexedX}] = 0xB4;
mnemonic_to_opcode_[{"LDY", AddressingMode::kAbsolute}] = 0xAC;
mnemonic_to_opcode_[{"LDY", AddressingMode::kAbsoluteIndexedX}] = 0xBC;
mnemonic_to_opcode_[{"LSR", AddressingMode::kAccumulator}] = 0x4A;
mnemonic_to_opcode_[{"LSR", AddressingMode::kDirectPage}] = 0x46;
mnemonic_to_opcode_[{"LSR", AddressingMode::kDirectPageIndexedX}] = 0x56;
mnemonic_to_opcode_[{"LSR", AddressingMode::kAbsolute}] = 0x4E;
mnemonic_to_opcode_[{"LSR", AddressingMode::kAbsoluteIndexedX}] = 0x5E;
mnemonic_to_opcode_[{"NOP", AddressingMode::kImplied}] = 0xEA;
mnemonic_to_opcode_[{"ORA", AddressingMode::kImmediate}] = 0x09;
mnemonic_to_opcode_[{"ORA", AddressingMode::kDirectPage}] = 0x05;
mnemonic_to_opcode_[{"ORA", AddressingMode::kDirectPageIndexedX}] = 0x15;
mnemonic_to_opcode_[{"ORA", AddressingMode::kAbsolute}] = 0x0D;
mnemonic_to_opcode_[{"ORA", AddressingMode::kAbsoluteIndexedX}] = 0x1D;
mnemonic_to_opcode_[{"ORA", AddressingMode::kAbsoluteIndexedY}] = 0x19;
mnemonic_to_opcode_[{"ORA", AddressingMode::kDirectPageIndirect}] = 0x01;
mnemonic_to_opcode_[{"ORA", AddressingMode::kDirectPageIndirectIndexedY}] =
0x11;
mnemonic_to_opcode_[{"PEA", AddressingMode::kImmediate}] = 0xF4;
mnemonic_to_opcode_[{"PEI", AddressingMode::kDirectPageIndirect}] = 0xD4;
mnemonic_to_opcode_[{"PER", AddressingMode::kProgramCounterRelativeLong}] =
0x62;
mnemonic_to_opcode_[{"PHA", AddressingMode::kImplied}] = 0x48;
mnemonic_to_opcode_[{"PHB", AddressingMode::kImplied}] = 0x8B;
mnemonic_to_opcode_[{"PHD", AddressingMode::kImplied}] = 0x0B;
mnemonic_to_opcode_[{"PHK", AddressingMode::kImplied}] = 0x4B;
mnemonic_to_opcode_[{"PHP", AddressingMode::kImplied}] = 0x08;
mnemonic_to_opcode_[{"PHX", AddressingMode::kImplied}] = 0xDA;
mnemonic_to_opcode_[{"PHY", AddressingMode::kImplied}] = 0x5A;
mnemonic_to_opcode_[{"PLA", AddressingMode::kImplied}] = 0x68;
mnemonic_to_opcode_[{"PLB", AddressingMode::kImplied}] = 0xAB;
mnemonic_to_opcode_[{"PLD", AddressingMode::kImplied}] = 0x2B;
mnemonic_to_opcode_[{"PLP", AddressingMode::kImplied}] = 0x28;
mnemonic_to_opcode_[{"PLX", AddressingMode::kImplied}] = 0xFA;
mnemonic_to_opcode_[{"PLY", AddressingMode::kImplied}] = 0x7A;
mnemonic_to_opcode_[{"REP", AddressingMode::kImmediate}] = 0xC2;
mnemonic_to_opcode_[{"ROL", AddressingMode::kAccumulator}] = 0x2A;
mnemonic_to_opcode_[{"ROL", AddressingMode::kDirectPage}] = 0x26;
mnemonic_to_opcode_[{"ROL", AddressingMode::kDirectPageIndexedX}] = 0x36;
mnemonic_to_opcode_[{"ROL", AddressingMode::kAbsolute}] = 0x2E;
mnemonic_to_opcode_[{"ROL", AddressingMode::kAbsoluteIndexedX}] = 0x3E;
mnemonic_to_opcode_[{"ROR", AddressingMode::kAccumulator}] = 0x6A;
mnemonic_to_opcode_[{"ROR", AddressingMode::kDirectPage}] = 0x66;
mnemonic_to_opcode_[{"ROR", AddressingMode::kDirectPageIndexedX}] = 0x76;
mnemonic_to_opcode_[{"ROR", AddressingMode::kAbsolute}] = 0x6E;
mnemonic_to_opcode_[{"ROR", AddressingMode::kAbsoluteIndexedX}] = 0x7E;
mnemonic_to_opcode_[{"RTI", AddressingMode::kImplied}] = 0x40;
mnemonic_to_opcode_[{"RTL", AddressingMode::kImplied}] = 0x6B;
mnemonic_to_opcode_[{"RTS", AddressingMode::kImplied}] = 0x60;
mnemonic_to_opcode_[{"SBC", AddressingMode::kImmediate}] = 0xE9;
mnemonic_to_opcode_[{"SBC", AddressingMode::kDirectPage}] = 0xE5;
mnemonic_to_opcode_[{"SBC", AddressingMode::kDirectPageIndexedX}] = 0xF5;
mnemonic_to_opcode_[{"SBC", AddressingMode::kAbsolute}] = 0xED;
mnemonic_to_opcode_[{"SBC", AddressingMode::kAbsoluteIndexedX}] = 0xFD;
mnemonic_to_opcode_[{"SBC", AddressingMode::kAbsoluteIndexedY}] = 0xF9;
mnemonic_to_opcode_[{"SBC", AddressingMode::kDirectPageIndirect}] = 0xE1;
mnemonic_to_opcode_[{"SBC", AddressingMode::kDirectPageIndirectIndexedY}] =
0xF1;
mnemonic_to_opcode_[{"SEC", AddressingMode::kImplied}] = 0x38;
mnemonic_to_opcode_[{"SED", AddressingMode::kImplied}] = 0xF8;
mnemonic_to_opcode_[{"SEI", AddressingMode::kImplied}] = 0x78;
mnemonic_to_opcode_[{"SEP", AddressingMode::kImmediate}] = 0xE2;
mnemonic_to_opcode_[{"STA", AddressingMode::kDirectPage}] = 0x85;
mnemonic_to_opcode_[{"STA", AddressingMode::kDirectPageIndexedX}] = 0x95;
mnemonic_to_opcode_[{"STA", AddressingMode::kAbsolute}] = 0x8D;
mnemonic_to_opcode_[{"STA", AddressingMode::kAbsoluteIndexedX}] = 0x9D;
mnemonic_to_opcode_[{"STA", AddressingMode::kAbsoluteIndexedY}] = 0x99;
mnemonic_to_opcode_[{"STA", AddressingMode::kDirectPageIndirect}] = 0x81;
mnemonic_to_opcode_[{"STA", AddressingMode::kDirectPageIndirectIndexedY}] =
0x91;
mnemonic_to_opcode_[{"STP", AddressingMode::kImplied}] = 0xDB;
mnemonic_to_opcode_[{"STX", AddressingMode::kDirectPage}] = 0x86;
mnemonic_to_opcode_[{"STX", AddressingMode::kDirectPageIndexedY}] = 0x96;
mnemonic_to_opcode_[{"STX", AddressingMode::kAbsolute}] = 0x8E;
mnemonic_to_opcode_[{"STY", AddressingMode::kDirectPage}] = 0x84;
mnemonic_to_opcode_[{"STY", AddressingMode::kDirectPageIndexedX}] = 0x94;
mnemonic_to_opcode_[{"STY", AddressingMode::kAbsolute}] = 0x8C;
mnemonic_to_opcode_[{"STZ", AddressingMode::kDirectPage}] = 0x64;
mnemonic_to_opcode_[{"STZ", AddressingMode::kDirectPageIndexedX}] = 0x74;
mnemonic_to_opcode_[{"STZ", AddressingMode::kAbsolute}] = 0x9C;
mnemonic_to_opcode_[{"STZ", AddressingMode::kAbsoluteIndexedX}] = 0x9E;
mnemonic_to_opcode_[{"TAX", AddressingMode::kImplied}] = 0xAA;
mnemonic_to_opcode_[{"TAY", AddressingMode::kImplied}] = 0xA8;
mnemonic_to_opcode_[{"TCD", AddressingMode::kImplied}] = 0x5B;
mnemonic_to_opcode_[{"TCS", AddressingMode::kImplied}] = 0x1B;
mnemonic_to_opcode_[{"TDC", AddressingMode::kImplied}] = 0x7B;
mnemonic_to_opcode_[{"TSC", AddressingMode::kImplied}] = 0x3B;
mnemonic_to_opcode_[{"TSX", AddressingMode::kImplied}] = 0xBA;
mnemonic_to_opcode_[{"TXA", AddressingMode::kImplied}] = 0x8A;
mnemonic_to_opcode_[{"TXS", AddressingMode::kImplied}] = 0x9A;
mnemonic_to_opcode_[{"TXY", AddressingMode::kImplied}] = 0x9B;
mnemonic_to_opcode_[{"TYA", AddressingMode::kImplied}] = 0x98;
mnemonic_to_opcode_[{"TYX", AddressingMode::kImplied}] = 0xBB;
mnemonic_to_opcode_[{"WAI", AddressingMode::kImplied}] = 0xCB;
mnemonic_to_opcode_[{"XBA", AddressingMode::kImplied}] = 0xEB;
mnemonic_to_opcode_[{"XCE", AddressingMode::kImplied}] = 0xFB;
}
std::unordered_map<MnemonicMode, uint8_t, MnemonicModeHash>
mnemonic_to_opcode_;
}; };
} // namespace emu } // namespace emu

215
src/app/emu/debug/emu.cc Normal file
View File

@@ -0,0 +1,215 @@
#if defined(_WIN32)
#define main SDL_main
#elif __APPLE__
#include "app/core/platform/app_delegate.h"
#endif
#include <SDL.h>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include <memory>
#include <string>
#include <vector>
#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/container/flat_hash_map.h"
#include "absl/debugging/failure_signal_handler.h"
#include "absl/debugging/leak_check.h"
#include "absl/debugging/stacktrace.h"
#include "absl/debugging/symbolize.h"
#include "absl/flags/flag.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/emu/snes.h"
#include "app/rom.h"
using namespace yaze::app;
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); }
};
int main(int argc, char **argv) {
absl::InitializeSymbolizer(argv[0]);
absl::FailureSignalHandlerOptions options;
options.symbolize_stacktrace = true;
options.alarm_on_failure_secs = true;
absl::InstallFailureSignalHandler(options);
std::unique_ptr<SDL_Window, sdl_deleter> window_;
std::unique_ptr<SDL_Renderer, sdl_deleter> renderer_;
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
return EXIT_FAILURE;
} 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("Yaze Emulator", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
512, // width, in pixels
480, // height, in pixels
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI),
sdl_deleter());
if (window_ == nullptr) {
return EXIT_FAILURE;
}
}
renderer_ = std::unique_ptr<SDL_Renderer, sdl_deleter>(
SDL_CreateRenderer(window_.get(), -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
sdl_deleter());
if (renderer_ == nullptr) {
return EXIT_FAILURE;
} else {
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
}
int audio_frequency_ = 48000;
SDL_AudioSpec want, have;
SDL_memset(&want, 0, sizeof(want));
want.freq = audio_frequency_;
want.format = AUDIO_S16;
want.channels = 2;
want.samples = 2048;
want.callback = NULL; // Uses the queue
auto audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (audio_device_ == 0) {
return EXIT_FAILURE;
}
auto audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
SDL_PauseAudioDevice(audio_device_, 0);
#ifdef __APPLE__
InitializeCocoa();
#endif
auto ppu_texture_ =
SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBX8888,
SDL_TEXTUREACCESS_STREAMING, 512, 480);
if (ppu_texture_ == NULL) {
printf("Failed to create texture: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
Rom rom_;
emu::SNES snes_;
Bytes rom_data_;
bool running = true;
bool loaded = false;
auto count_frequency = SDL_GetPerformanceFrequency();
auto last_count = SDL_GetPerformanceCounter();
auto time_adder = 0.0;
int wanted_frames_ = 0;
int wanted_samples_ = 0;
SDL_Event event;
rom_.LoadFromFile("inidisp_hammer_0f00.sfc");
if (rom_.is_loaded()) {
rom_data_ = rom_.vector();
snes_.Init(rom_data_);
wanted_frames_ = 1.0 / (snes_.Memory().pal_timing() ? 50.0 : 60.0);
wanted_samples_ = 48000 / (snes_.Memory().pal_timing() ? 50 : 60);
loaded = true;
}
while (running) {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_DROPFILE:
rom_.LoadFromFile(event.drop.file);
if (rom_.is_loaded()) {
rom_data_ = rom_.vector();
snes_.Init(rom_data_);
wanted_frames_ = 1.0 / (snes_.Memory().pal_timing() ? 50.0 : 60.0);
wanted_samples_ = 48000 / (snes_.Memory().pal_timing() ? 50 : 60);
loaded = true;
}
SDL_free(event.drop.file);
break;
case SDL_KEYDOWN:
break;
case SDL_KEYUP:
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
running = false;
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
break;
default:
break;
}
break;
default:
break;
}
}
uint64_t current_count = SDL_GetPerformanceCounter();
uint64_t delta = current_count - last_count;
last_count = current_count;
float seconds = delta / (float)count_frequency;
time_adder += seconds;
// allow 2 ms earlier, to prevent skipping due to being just below wanted
while (time_adder >= wanted_frames_ - 0.002) {
time_adder -= wanted_frames_;
if (loaded) {
snes_.RunFrame();
snes_.SetSamples(audio_buffer_, wanted_samples_);
if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples_ * 4 * 6) {
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4);
}
void *ppu_pixels_;
int ppu_pitch_;
if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) !=
0) {
printf("Failed to lock texture: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
snes_.SetPixels(static_cast<uint8_t *>(ppu_pixels_));
SDL_UnlockTexture(ppu_texture_);
}
}
SDL_RenderClear(renderer_.get());
SDL_RenderCopy(renderer_.get(), ppu_texture_, NULL, NULL);
SDL_RenderPresent(renderer_.get()); // should vsync
}
SDL_PauseAudioDevice(audio_device_, 1);
SDL_CloseAudioDevice(audio_device_);
delete audio_buffer_;
// ImGui_ImplSDLRenderer2_Shutdown();
// ImGui_ImplSDL2_Shutdown();
// ImGui::DestroyContext();
SDL_Quit();
return EXIT_SUCCESS;
}

View File

@@ -1,15 +1,17 @@
#include "app/emu/emulator.h" #include "app/emu/emulator.h"
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <imgui_memory_editor.h> #include "imgui_memory_editor.h"
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "app/core/constants.h" #include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/emu/snes.h" #include "app/emu/snes.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/input.h" #include "app/gui/input.h"
#include "app/gui/zeml.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {
@@ -17,11 +19,26 @@ namespace app {
namespace emu { namespace emu {
namespace { namespace {
bool ShouldDisplay(const InstructionEntry& entry, const char* filter, bool ShouldDisplay(const InstructionEntry& entry, const char* filter) {
bool showAll) { if (filter[0] == '\0') {
// Implement logic to determine if the entry should be displayed based on the return true;
// filter and showAll flag }
return true;
// Supported fields: address, opcode, operands
if (entry.operands.find(filter) != std::string::npos) {
return true;
}
if (absl::StrFormat("%06X", entry.address).find(filter) !=
std::string::npos) {
return true;
}
if (opcode_to_mnemonic.at(entry.opcode).find(filter) != std::string::npos) {
return true;
}
return false;
} }
} // namespace } // namespace
@@ -33,35 +50,105 @@ using ImGui::TableNextColumn;
using ImGui::Text; using ImGui::Text;
void Emulator::Run() { void Emulator::Run() {
static bool loaded = false;
if (!snes_.running() && rom()->is_loaded()) { if (!snes_.running() && rom()->is_loaded()) {
snes_.SetupMemory(*rom()); ppu_texture_ =
snes_.Init(*rom()); SDL_CreateTexture(rom()->renderer().get(), SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, 512, 480);
if (ppu_texture_ == NULL) {
printf("Failed to create texture: %s\n", SDL_GetError());
return;
}
rom_data_ = rom()->vector();
snes_.Init(rom_data_);
wanted_frames_ = 1.0 / (snes_.Memory().pal_timing() ? 50.0 : 60.0);
wanted_samples_ = 48000 / (snes_.Memory().pal_timing() ? 50 : 60);
loaded = true;
count_frequency = SDL_GetPerformanceFrequency();
last_count = SDL_GetPerformanceCounter();
time_adder = 0.0;
} }
RenderNavBar(); RenderNavBar();
if (running_) { if (running_) {
HandleEvents(); HandleEvents();
if (!step_) {
snes_.Run(); uint64_t current_count = SDL_GetPerformanceCounter();
uint64_t delta = current_count - last_count;
last_count = current_count;
float seconds = delta / (float)count_frequency;
time_adder += seconds;
// allow 2 ms earlier, to prevent skipping due to being just below wanted
while (time_adder >= wanted_frames_ - 0.002) {
time_adder -= wanted_frames_;
if (loaded) {
if (turbo_mode_) {
snes_.RunFrame();
}
snes_.RunFrame();
snes_.SetSamples(audio_buffer_, wanted_samples_);
if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples_ * 4 * 6) {
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4);
}
void* ppu_pixels_;
int ppu_pitch_;
if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) !=
0) {
printf("Failed to lock texture: %s\n", SDL_GetError());
return;
}
snes_.SetPixels(static_cast<uint8_t*>(ppu_pixels_));
SDL_UnlockTexture(ppu_texture_);
}
} }
} }
RenderEmulator(); gui::zeml::Render(emulator_node_);
}
void Emulator::RenderSnesPpu() {
ImVec2 size = ImVec2(512, 480);
if (snes_.running()) {
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 480), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f);
ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f);
ImGui::Image((void*)ppu_texture_, size, ImVec2(0, 0), ImVec2(1, 1));
ImGui::EndChild();
} else {
ImGui::Text("Emulator output not available.");
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 480), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f);
ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f);
ImGui::Dummy(size);
ImGui::EndChild();
}
ImGui::Separator();
} }
void Emulator::RenderNavBar() { void Emulator::RenderNavBar() {
MENU_BAR() std::string navbar_layout = R"(
if (ImGui::BeginMenu("Options")) { BeginMenuBar {
MENU_ITEM("Input") {} BeginMenu title="Options" {
MENU_ITEM("Audio") {} MenuItem title="Input" {}
MENU_ITEM("Video") {} MenuItem title="Audio" {}
ImGui::EndMenu(); MenuItem title="Video" {}
} }
END_MENU_BAR() }
)";
static auto navbar_node = gui::zeml::Parse(navbar_layout);
gui::zeml::Render(navbar_node);
if (ImGui::Button(ICON_MD_PLAY_ARROW)) { if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
loading_ = true; running_ = true;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Start Emulation"); ImGui::SetTooltip("Start Emulation");
@@ -69,7 +156,7 @@ void Emulator::RenderNavBar() {
SameLine(); SameLine();
if (ImGui::Button(ICON_MD_PAUSE)) { if (ImGui::Button(ICON_MD_PAUSE)) {
snes_.SetCpuMode(1); running_ = false;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Pause Emulation"); ImGui::SetTooltip("Pause Emulation");
@@ -78,7 +165,7 @@ void Emulator::RenderNavBar() {
if (ImGui::Button(ICON_MD_SKIP_NEXT)) { if (ImGui::Button(ICON_MD_SKIP_NEXT)) {
// Step through Code logic // Step through Code logic
snes_.StepRun(); snes_.cpu().RunOpcode();
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Step Through Code"); ImGui::SetTooltip("Step Through Code");
@@ -87,6 +174,7 @@ void Emulator::RenderNavBar() {
if (ImGui::Button(ICON_MD_REFRESH)) { if (ImGui::Button(ICON_MD_REFRESH)) {
// Reset Emulator logic // Reset Emulator logic
snes_.Reset(true);
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reset Emulator"); ImGui::SetTooltip("Reset Emulator");
@@ -125,13 +213,22 @@ void Emulator::RenderNavBar() {
ImGui::SetTooltip("Settings"); ImGui::SetTooltip("Settings");
} }
static bool open_file = false;
SameLine(); SameLine();
if (ImGui::Button(ICON_MD_INFO)) { if (ImGui::Button(ICON_MD_INFO)) {
if (ImGui::IsItemHovered()) { open_file = true;
ImGui::SetTooltip("About Debugger");
}
// About Debugger logic // About Debugger logic
} }
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("About Debugger");
}
SameLine();
ImGui::Checkbox("Logging", snes_.cpu().mutable_log_instructions());
SameLine();
ImGui::Checkbox("Turbo", &turbo_mode_);
static bool show_memory_viewer = false; static bool show_memory_viewer = false;
SameLine(); SameLine();
@@ -147,6 +244,18 @@ void Emulator::RenderNavBar() {
RenderMemoryViewer(); RenderMemoryViewer();
ImGui::End(); ImGui::End();
} }
if (open_file) {
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
if (!file_name.empty()) {
std::ifstream file(file_name, std::ios::binary);
// Load the data directly into rom_data
rom_data_.assign(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
snes_.Init(rom_data_);
open_file = false;
}
}
} }
void Emulator::HandleEvents() { void Emulator::HandleEvents() {
@@ -154,54 +263,11 @@ void Emulator::HandleEvents() {
// ... // ...
} }
void Emulator::RenderEmulator() {
if (ImGui::BeginTable("##Emulator", 3,
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
ImGui::TableSetupColumn("CPU");
ImGui::TableSetupColumn("PPU");
ImGui::TableHeadersRow();
TableNextColumn();
RenderCpuInstructionLog(snes_.cpu().instruction_log_);
TableNextColumn();
RenderSnesPpu();
RenderBreakpointList();
TableNextColumn();
ImGui::BeginChild("##", ImVec2(0, 0), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
RenderCpuState(snes_.cpu());
ImGui::EndChild();
ImGui::EndTable();
}
}
void Emulator::RenderSnesPpu() {
ImVec2 size = ImVec2(320, 240);
if (snes_.running()) {
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f);
ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f);
ImGui::Image((void*)snes_.ppu().GetScreen()->texture(), size, ImVec2(0, 0),
ImVec2(1, 1));
ImGui::EndChild();
} else {
ImGui::Text("Emulator output not available.");
ImGui::BeginChild("EmulatorOutput", ImVec2(0, 240), true,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar);
ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f);
ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f);
ImGui::Dummy(size);
ImGui::EndChild();
}
ImGui::Separator();
}
void Emulator::RenderBreakpointList() { void Emulator::RenderBreakpointList() {
if (ImGui::Button("Set SPC PC")) {
snes_.apu().spc700().PC = 0xFFEF;
}
Separator();
Text("Breakpoints"); Text("Breakpoints");
Separator(); Separator();
static char breakpoint_input[10] = ""; static char breakpoint_input[10] = "";
@@ -245,64 +311,31 @@ void Emulator::RenderBreakpointList() {
for (auto breakpoint : breakpoints) { for (auto breakpoint : breakpoints) {
if (ImGui::Selectable(absl::StrFormat("0x%04X", breakpoint).c_str())) { if (ImGui::Selectable(absl::StrFormat("0x%04X", breakpoint).c_str())) {
// Jump to breakpoint // Jump to breakpoint
// snes_.Cpu().JumpToBreakpoint(breakpoint); // snes_.cpu().JumpToBreakpoint(breakpoint);
} }
} }
ImGui::EndChild(); ImGui::EndChild();
} }
Separator(); Separator();
gui::InputHexByte("PB", &manual_pb_, 1, 25.f); gui::InputHexByte("PB", &manual_pb_, 50.f);
gui::InputHexWord("PC", &manual_pc_, 25.f); gui::InputHexWord("PC", &manual_pc_, 75.f);
if (ImGui::Button("Set Current Address")) { if (ImGui::Button("Set Current Address")) {
snes_.cpu().PC = manual_pc_; snes_.cpu().PC = manual_pc_;
snes_.cpu().PB = manual_pb_; snes_.cpu().PB = manual_pb_;
} }
} }
void Emulator::RenderCpuState(Cpu& cpu) {
if (ImGui::CollapsingHeader("Register Values",
ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Columns(2, "RegistersColumns");
Separator();
Text("A: 0x%04X", cpu.A);
NextColumn();
Text("D: 0x%04X", cpu.D);
NextColumn();
Text("X: 0x%04X", cpu.X);
NextColumn();
Text("DB: 0x%02X", cpu.DB);
NextColumn();
Text("Y: 0x%04X", cpu.Y);
NextColumn();
Text("PB: 0x%02X", cpu.PB);
NextColumn();
Text("PC: 0x%04X", cpu.PC);
NextColumn();
Text("E: %d", cpu.E);
NextColumn();
ImGui::Columns(1);
Separator();
}
// Call Stack
if (ImGui::CollapsingHeader("Call Stack", ImGuiTreeNodeFlags_DefaultOpen)) {
// For each return address in the call stack:
Text("Return Address: 0x%08X", 0xFFFFFF); // Placeholder
}
snes_.SetCpuMode(0);
}
void Emulator::RenderMemoryViewer() { void Emulator::RenderMemoryViewer() {
static MemoryEditor ram_edit;
static MemoryEditor aram_edit;
static MemoryEditor mem_edit; static MemoryEditor mem_edit;
if (ImGui::Button("RAM")) {
mem_edit.GotoAddrAndHighlight(0x7E0000, 0x7E0001);
}
if (ImGui::BeginTable("MemoryViewerTable", 2, if (ImGui::BeginTable("MemoryViewerTable", 4,
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) { ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
ImGui::TableSetupColumn("Bookmarks"); ImGui::TableSetupColumn("Bookmarks");
ImGui::TableSetupColumn("Memory"); ImGui::TableSetupColumn("RAM");
ImGui::TableSetupColumn("ARAM");
ImGui::TableSetupColumn("ROM");
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
TableNextColumn(); TableNextColumn();
@@ -343,41 +376,70 @@ void Emulator::RenderMemoryViewer() {
} }
TableNextColumn(); TableNextColumn();
mem_edit.DrawContents((void*)snes_.Memory()->data(), if (ImGui::BeginChild("RAM", ImVec2(0, 0), true,
snes_.Memory()->size()); ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse)) {
ram_edit.DrawContents((void*)snes_.get_ram(), 0x20000);
ImGui::EndChild();
}
TableNextColumn();
if (ImGui::BeginChild("ARAM", ImVec2(0, 0), true,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse)) {
aram_edit.DrawContents((void*)snes_.apu().ram.data(),
snes_.apu().ram.size());
ImGui::EndChild();
}
TableNextColumn();
if (ImGui::BeginChild("ROM", ImVec2(0, 0), true,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse)) {
mem_edit.DrawContents((void*)snes_.Memory().rom_.data(),
snes_.Memory().rom_.size());
ImGui::EndChild();
}
ImGui::EndTable(); ImGui::EndTable();
} }
} }
void Emulator::RenderCpuInstructionLog( void Emulator::RenderCpuInstructionLog(
const std::vector<InstructionEntry>& instructionLog) { const std::vector<InstructionEntry>& instruction_log) {
if (ImGui::CollapsingHeader("CPU Instruction Log")) { if (ImGui::CollapsingHeader("Instruction Log",
ImGuiTreeNodeFlags_DefaultOpen)) {
// Filtering options // Filtering options
static char filterBuf[256]; static char filter[256];
ImGui::InputText("Filter", filterBuf, IM_ARRAYSIZE(filterBuf)); ImGui::InputText("Filter", filter, IM_ARRAYSIZE(filter));
SameLine();
if (ImGui::Button("Clear")) { /* Clear filter logic */
}
// Toggle for showing all opcodes
static bool showAllOpcodes = true;
ImGui::Checkbox("Show All Opcodes", &showAllOpcodes);
// Instruction list // Instruction list
ImGui::BeginChild("InstructionList", ImVec2(0, 0), ImGui::BeginChild("InstructionList", ImVec2(0, 0), ImGuiChildFlags_None);
ImGuiChildFlags_None); for (const auto& entry : instruction_log) {
for (const auto& entry : instructionLog) { if (ShouldDisplay(entry, filter)) {
if (ShouldDisplay(entry, filterBuf, showAllOpcodes)) {
if (ImGui::Selectable( if (ImGui::Selectable(
absl::StrFormat("%06X: %s %s", entry.address, absl::StrFormat("%06X:", entry.address).c_str())) {
opcode_to_mnemonic.at(entry.opcode),
entry.operands)
.c_str())) {
// Logic to handle click (e.g., jump to address, set breakpoint) // Logic to handle click (e.g., jump to address, set breakpoint)
} }
ImGui::SameLine();
ImVec4 color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
ImGui::TextColored(color, "%s",
opcode_to_mnemonic.at(entry.opcode).c_str());
ImVec4 operand_color = ImVec4(0.7f, 0.5f, 0.3f, 1.0f);
ImGui::SameLine();
ImGui::TextColored(operand_color, "%s", entry.operands.c_str());
} }
} }
// Jump to the bottom of the child scrollbar
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
ImGui::SetScrollHereY(1.0f);
}
ImGui::EndChild(); ImGui::EndChild();
} }
} }

View File

@@ -1,12 +1,13 @@
#ifndef YAZE_APP_CORE_EMULATOR_H #ifndef YAZE_APP_CORE_EMULATOR_H
#define YAZE_APP_CORE_EMULATOR_H #define YAZE_APP_CORE_EMULATOR_H
#include <imgui/imgui.h> #include "imgui/imgui.h"
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "app/emu/snes.h" #include "app/emu/snes.h"
#include "app/gui/zeml.h"
#include "app/rom.h" #include "app/rom.h"
namespace yaze { namespace yaze {
@@ -24,16 +25,90 @@ namespace emu {
*/ */
class Emulator : public SharedRom { class Emulator : public SharedRom {
public: public:
Emulator() {
std::string emulator_layout = R"(
Table id="Emulator" count="2" flags="Resizable|ScrollY" {
TableSetupColumn title="CPU",
TableSetupColumn title="PPU",
TableHeadersRow,
TableNextColumn,
CollapsingHeader id="cpuState" title="Register Values" flags="DefaultOpen" {
BeginChild id="##CpuState" size="0,100" flags="NoMove|NoScrollbar" {
Columns id="registersColumns" count="2" {
Text text="A: 0x%04X" data="cpu.A",
Text text="D: 0x%04X" data="cpu.D",
Text text="X: 0x%04X" data="cpu.X",
Text text="DB: 0x%02X" data="cpu.DB",
Text text="Y: 0x%04X" data="cpu.Y",
Text text="PB: 0x%02X" data="cpu.PB",
Text text="PC: 0x%04X" data="cpu.PC",
Text text="PS: 0x%02X" data="cpu.status",
Text text="SP: 0x%02X" data="cpu.SP",
Text text="Cycle: %d" data="snes.cycle_count",
}
}
}
CollapsingHeader id="spcState" title="SPC Registers" flags="DefaultOpen" {
BeginChild id="##SpcState" size="0,100" flags="NoMove|NoScrollbar" {
Columns id="spcRegistersColumns" count="2" {
Text text="A: 0x%02X" data="spc.A",
Text text="PC: 0x%04X" data="spc.PC",
Text text="X: 0x%02X" data="spc.X",
Text text="SP: 0x%02X" data="spc.SP",
Text text="Y: 0x%02X" data="spc.Y",
Text text="PSW: 0x%02X" data="spc.PSW",
}
}
}
Function id="CpuInstructionLog",
TableNextColumn,
Function id="SnesPpu",
Function id="BreakpointList"
}
)";
const std::map<std::string, void*> data_bindings = {
{"cpu.A", &snes_.cpu().A},
{"cpu.D", &snes_.cpu().D},
{"cpu.X", &snes_.cpu().X},
{"cpu.DB", &snes_.cpu().DB},
{"cpu.Y", &snes_.cpu().Y},
{"cpu.PB", &snes_.cpu().PB},
{"cpu.PC", &snes_.cpu().PC},
{"cpu.status", &snes_.cpu().status},
{"snes.cycle_count", &snes_.mutable_cycles()},
{"cpu.SP", &snes_.Memory().mutable_sp()},
{"spc.A", &snes_.apu().spc700().A},
{"spc.X", &snes_.apu().spc700().X},
{"spc.Y", &snes_.apu().spc700().Y},
{"spc.PC", &snes_.apu().spc700().PC},
{"spc.SP", &snes_.apu().spc700().SP},
{"spc.PSW", &snes_.apu().spc700().PSW}};
emulator_node_ = gui::zeml::Parse(emulator_layout, data_bindings);
Bind(emulator_node_.GetNode("CpuInstructionLog"),
[&]() { RenderCpuInstructionLog(snes_.cpu().instruction_log_); });
Bind(emulator_node_.GetNode("SnesPpu"), [&]() { RenderSnesPpu(); });
Bind(emulator_node_.GetNode("BreakpointList"),
[&]() { RenderBreakpointList(); });
}
void Run(); void Run();
auto snes() -> SNES& { return snes_; }
auto running() const -> bool { return running_; }
void set_audio_buffer(int16_t* audio_buffer) { audio_buffer_ = audio_buffer; }
auto set_audio_device_id(SDL_AudioDeviceID audio_device) {
audio_device_ = audio_device;
}
auto wanted_samples() const -> int { return wanted_samples_; }
private: private:
void RenderNavBar(); void RenderNavBar();
void HandleEvents(); void HandleEvents();
void RenderEmulator();
void RenderSnesPpu(); void RenderSnesPpu();
void RenderBreakpointList(); void RenderBreakpointList();
void RenderCpuState(Cpu& cpu);
void RenderMemoryViewer(); void RenderMemoryViewer();
struct Bookmark { struct Bookmark {
@@ -45,14 +120,32 @@ class Emulator : public SharedRom {
void RenderCpuInstructionLog( void RenderCpuInstructionLog(
const std::vector<InstructionEntry>& instructionLog); const std::vector<InstructionEntry>& instructionLog);
SNES snes_; bool step_ = true;
uint16_t manual_pc_ = 0;
uint8_t manual_pb_ = 0;
bool power_ = false; bool power_ = false;
bool loading_ = false; bool loading_ = false;
bool running_ = false; bool running_ = false;
bool step_ = true; bool turbo_mode_ = false;
float wanted_frames_;
int wanted_samples_;
uint8_t manual_pb_ = 0;
uint16_t manual_pc_ = 0;
// timing
uint64_t count_frequency;
uint64_t last_count;
float time_adder = 0.0;
int16_t* audio_buffer_;
SDL_AudioDeviceID audio_device_;
SNES snes_;
SDL_Texture* ppu_texture_;
std::vector<uint8_t> rom_data_;
gui::zeml::Node emulator_node_;
}; };
} // namespace emu } // namespace emu

View File

@@ -6,72 +6,370 @@ namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace memory { namespace memory {
namespace dma {
void DirectMemoryAccess::StartDMATransfer(uint8_t channelMask) { static const int bAdrOffsets[8][4] = {{0, 0, 0, 0}, {0, 1, 0, 1}, {0, 0, 0, 0},
for (int i = 0; i < 8; ++i) { {0, 0, 1, 1}, {0, 1, 2, 3}, {0, 1, 0, 1},
if ((channelMask & (1 << i)) != 0) { {0, 0, 0, 0}, {0, 0, 1, 1}};
Channel& ch = channels[i];
// Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, DASn) static const int transferLength[8] = {1, 2, 2, 4, 4, 4, 2, 4};
// ...
// Determine the transfer direction based on the DMAPn register void Reset(MemoryImpl* memory) {
bool fromMemory = (ch.DMAPn & 0x80) != 0; auto channel = memory->dma_channels();
for (int i = 0; i < 8; i++) {
channel[i].bAdr = 0xff;
channel[i].aAdr = 0xffff;
channel[i].aBank = 0xff;
channel[i].size = 0xffff;
channel[i].indBank = 0xff;
channel[i].tableAdr = 0xffff;
channel[i].repCount = 0xff;
channel[i].unusedByte = 0xff;
channel[i].dmaActive = false;
channel[i].hdmaActive = false;
channel[i].mode = 7;
channel[i].fixed = true;
channel[i].decrement = true;
channel[i].indirect = true;
channel[i].fromB = true;
channel[i].unusedBit = true;
channel[i].doTransfer = false;
channel[i].terminated = false;
}
memory->set_dma_state(0);
memory->set_hdma_init_requested(false);
memory->set_hdma_run_requested(false);
}
// Determine the transfer size based on the DMAPn register uint8_t Read(MemoryImpl* memory, uint16_t adr) {
bool transferTwoBytes = (ch.DMAPn & 0x40) != 0; auto channel = memory->dma_channels();
uint8_t c = (adr & 0x70) >> 4;
switch (adr & 0xf) {
case 0x0: {
uint8_t val = channel[c].mode;
val |= channel[c].fixed << 3;
val |= channel[c].decrement << 4;
val |= channel[c].unusedBit << 5;
val |= channel[c].indirect << 6;
val |= channel[c].fromB << 7;
return val;
}
case 0x1: {
return channel[c].bAdr;
}
case 0x2: {
return channel[c].aAdr & 0xff;
}
case 0x3: {
return channel[c].aAdr >> 8;
}
case 0x4: {
return channel[c].aBank;
}
case 0x5: {
return channel[c].size & 0xff;
}
case 0x6: {
return channel[c].size >> 8;
}
case 0x7: {
return channel[c].indBank;
}
case 0x8: {
return channel[c].tableAdr & 0xff;
}
case 0x9: {
return channel[c].tableAdr >> 8;
}
case 0xa: {
return channel[c].repCount;
}
case 0xb:
case 0xf: {
return channel[c].unusedByte;
}
default: {
return memory->open_bus();
}
}
}
// Perform the DirectMemoryAccess transfer based on the channel parameters void Write(MemoryImpl* memory, uint16_t adr, uint8_t val) {
std::cout << "Starting DirectMemoryAccess transfer for channel " << i auto channel = memory->dma_channels();
<< std::endl; uint8_t c = (adr & 0x70) >> 4;
switch (adr & 0xf) {
case 0x0: {
channel[c].mode = val & 0x7;
channel[c].fixed = val & 0x8;
channel[c].decrement = val & 0x10;
channel[c].unusedBit = val & 0x20;
channel[c].indirect = val & 0x40;
channel[c].fromB = val & 0x80;
break;
}
case 0x1: {
channel[c].bAdr = val;
break;
}
case 0x2: {
channel[c].aAdr = (channel[c].aAdr & 0xff00) | val;
break;
}
case 0x3: {
channel[c].aAdr = (channel[c].aAdr & 0xff) | (val << 8);
break;
}
case 0x4: {
channel[c].aBank = val;
break;
}
case 0x5: {
channel[c].size = (channel[c].size & 0xff00) | val;
break;
}
case 0x6: {
channel[c].size = (channel[c].size & 0xff) | (val << 8);
break;
}
case 0x7: {
channel[c].indBank = val;
break;
}
case 0x8: {
channel[c].tableAdr = (channel[c].tableAdr & 0xff00) | val;
break;
}
case 0x9: {
channel[c].tableAdr = (channel[c].tableAdr & 0xff) | (val << 8);
break;
}
case 0xa: {
channel[c].repCount = val;
break;
}
case 0xb:
case 0xf: {
channel[c].unusedByte = val;
break;
}
default: {
break;
}
}
}
for (uint16_t j = 0; j < ch.DASn; ++j) { void DoDma(SNES* snes, MemoryImpl* memory, int cpuCycles) {
// Read a byte or two bytes from memory based on the transfer size auto channel = memory->dma_channels();
// ... snes->cpu().set_int_delay(true);
// Write the data to the B-bus address (BBADn) if transferring from // align to multiple of 8
// memory snes->SyncCycles(true, 8);
// ...
// Update the A1Tn register based on the transfer direction // full transfer overhead
if (fromMemory) { WaitCycle(snes, memory);
ch.A1Tn += transferTwoBytes ? 2 : 1; for (int i = 0; i < 8; i++) {
} else { if (!channel[i].dmaActive) continue;
ch.A1Tn -= transferTwoBytes ? 2 : 1; // do channel i
WaitCycle(snes, memory); // overhead per channel
int offIndex = 0;
while (channel[i].dmaActive) {
WaitCycle(snes, memory);
TransferByte(snes, memory, channel[i].aAdr, channel[i].aBank,
channel[i].bAdr + bAdrOffsets[channel[i].mode][offIndex++],
channel[i].fromB);
offIndex &= 3;
if (!channel[i].fixed) {
channel[i].aAdr += channel[i].decrement ? -1 : 1;
}
channel[i].size--;
if (channel[i].size == 0) {
channel[i].dmaActive = false;
}
}
}
// re-align to cpu cycles
snes->SyncCycles(false, cpuCycles);
}
void HandleDma(SNES* snes, MemoryImpl* memory, int cpu_cycles) {
// if hdma triggered, do it, except if dmastate indicates dma will be done now
// (it will be done as part of the dma in that case)
if (memory->hdma_init_requested() && memory->dma_state() != 2)
InitHdma(snes, memory, true, cpu_cycles);
if (memory->hdma_run_requested() && memory->dma_state() != 2)
DoHdma(snes, memory, true, cpu_cycles);
if (memory->dma_state() == 1) {
memory->set_dma_state(2);
return;
}
if (memory->dma_state() == 2) {
// do dma
DoDma(snes, memory, cpu_cycles);
memory->set_dma_state(0);
}
}
void WaitCycle(SNES* snes, MemoryImpl* memory) {
// run hdma if requested, no sync (already sycned due to dma)
if (memory->hdma_init_requested()) InitHdma(snes, memory, false, 0);
if (memory->hdma_run_requested()) DoHdma(snes, memory, false, 0);
snes->RunCycles(8);
}
void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) {
auto channel = memory->dma_channels();
memory->set_hdma_init_requested(false);
bool hdmaEnabled = false;
// check if a channel is enabled, and do reset
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive) hdmaEnabled = true;
channel[i].doTransfer = false;
channel[i].terminated = false;
}
if (!hdmaEnabled) return;
snes->cpu().set_int_delay(true);
if (do_sync) snes->SyncCycles(true, 8);
// full transfer overhead
snes->RunCycles(8);
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive) {
// terminate any dma
channel[i].dmaActive = false;
// load address, repCount, and indirect address if needed
snes->RunCycles(8);
channel[i].tableAdr = channel[i].aAdr;
channel[i].repCount =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++);
if (channel[i].repCount == 0) channel[i].terminated = true;
if (channel[i].indirect) {
snes->RunCycles(8);
channel[i].size =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++);
snes->RunCycles(8);
channel[i].size |=
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8;
}
channel[i].doTransfer = true;
}
}
if (do_sync) snes->SyncCycles(false, cpu_cycles);
}
void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles) {
auto channel = memory->dma_channels();
memory->set_hdma_run_requested(false);
bool hdmaActive = false;
int lastActive = 0;
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive) {
hdmaActive = true;
if (!channel[i].terminated) lastActive = i;
}
}
if (!hdmaActive) return;
snes->cpu().set_int_delay(true);
if (do_sync) snes->SyncCycles(true, 8);
// full transfer overhead
snes->RunCycles(8);
// do all copies
for (int i = 0; i < 8; i++) {
// terminate any dma
if (channel[i].hdmaActive) channel[i].dmaActive = false;
if (channel[i].hdmaActive && !channel[i].terminated) {
// do the hdma
if (channel[i].doTransfer) {
for (int j = 0; j < transferLength[channel[i].mode]; j++) {
snes->RunCycles(8);
if (channel[i].indirect) {
TransferByte(snes, memory, channel[i].size++, channel[i].indBank,
channel[i].bAdr + bAdrOffsets[channel[i].mode][j],
channel[i].fromB);
} else {
TransferByte(snes, memory, channel[i].tableAdr++, channel[i].aBank,
channel[i].bAdr + bAdrOffsets[channel[i].mode][j],
channel[i].fromB);
}
} }
} }
// Update the channel registers after the transfer (e.g., A1Tn, DASn)
// ...
} }
} }
MDMAEN = channelMask; // Set the MDMAEN register to the channel mask // do all updates
} for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive && !channel[i].terminated) {
void DirectMemoryAccess::EnableHDMATransfers(uint8_t channelMask) { channel[i].repCount--;
for (int i = 0; i < 8; ++i) { channel[i].doTransfer = channel[i].repCount & 0x80;
if ((channelMask & (1 << i)) != 0) { snes->RunCycles(8);
Channel& ch = channels[i]; uint8_t newRepCount =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr);
// Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, A2An, NLTRn) if ((channel[i].repCount & 0x7f) == 0) {
// ... channel[i].repCount = newRepCount;
channel[i].tableAdr++;
// Perform the HDMA setup based on the channel parameters if (channel[i].indirect) {
std::cout << "Enabling HDMA transfer for channel " << i << std::endl; if (channel[i].repCount == 0 && i == lastActive) {
// if this is the last active channel, only fetch high, and use 0
// Read the HDMA table from memory starting at A1Tn // for low
// ... channel[i].size = 0;
} else {
// Update the A2An register based on the HDMA table snes->RunCycles(8);
// ... channel[i].size =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++);
// Update the NLTRn register based on the HDMA table }
// ... snes->RunCycles(8);
channel[i].size |=
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8;
}
if (channel[i].repCount == 0) channel[i].terminated = true;
channel[i].doTransfer = true;
}
} }
} }
HDMAEN = channelMask; // Set the HDMAEN register to the channel mask
if (do_sync) snes->SyncCycles(false, cycles);
} }
void TransferByte(SNES* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank,
uint8_t bAdr, bool fromB) {
// accessing 0x2180 via b-bus while a-bus accesses ram gives open bus
bool validB =
!(bAdr == 0x80 &&
(aBank == 0x7e || aBank == 0x7f ||
((aBank < 0x40 || (aBank >= 0x80 && aBank < 0xc0)) && aAdr < 0x2000)));
// accesing b-bus, or dma regs via a-bus gives open bus
bool validA = !((aBank < 0x40 || (aBank >= 0x80 && aBank < 0xc0)) &&
(aAdr == 0x420b || aAdr == 0x420c ||
(aAdr >= 0x4300 && aAdr < 0x4380) ||
(aAdr >= 0x2100 && aAdr < 0x2200)));
if (fromB) {
uint8_t val = validB ? snes->ReadBBus(bAdr) : memory->open_bus();
if (validA) snes->Write((aBank << 16) | aAdr, val);
} else {
uint8_t val =
validA ? snes->Read((aBank << 16) | aAdr) : memory->open_bus();
if (validB) snes->WriteBBus(bAdr, val);
}
}
void StartDma(MemoryImpl* memory, uint8_t val, bool hdma) {
auto channel = memory->dma_channels();
for (int i = 0; i < 8; i++) {
if (hdma) {
channel[i].hdmaActive = val & (1 << i);
} else {
channel[i].dmaActive = val & (1 << i);
}
}
if (!hdma) {
memory->set_dma_state(val != 0 ? 1 : 0);
}
}
} // namespace dma
} // namespace memory } // namespace memory
} // namespace emu } // namespace emu
} // namespace app } // namespace app

View File

@@ -3,56 +3,32 @@
#include <cstdint> #include <cstdint>
#include "app/emu/memory/memory.h"
#include "app/emu/snes.h"
namespace yaze { namespace yaze {
namespace app { namespace app {
namespace emu { namespace emu {
namespace memory { namespace memory {
namespace dma {
class DirectMemoryAccess { void Reset(MemoryImpl* memory);
public: void HandleDma(SNES* snes, MemoryImpl* memory, int cpu_cycles);
DirectMemoryAccess() {
// Initialize DMA and HDMA channels
for (int i = 0; i < 8; ++i) {
channels[i].DMAPn = 0;
channels[i].BBADn = 0;
channels[i].UNUSEDn = 0;
channels[i].A1Tn = 0xFFFFFF;
channels[i].DASn = 0xFFFF;
channels[i].A2An = 0xFFFF;
channels[i].NLTRn = 0xFF;
}
}
// DMA Transfer Modes void WaitCycle(SNES* snes, MemoryImpl* memory);
enum class DMA_TRANSFER_TYPE {
OAM,
PPUDATA,
CGDATA,
FILL_VRAM,
CLEAR_VRAM,
RESET_VRAM
};
// Functions for handling DMA and HDMA transfers void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles);
void StartDMATransfer(uint8_t channels); void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles);
void EnableHDMATransfers(uint8_t channels);
// Structure for DMA and HDMA channel registers void TransferByte(SNES* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank,
struct Channel { uint8_t bAdr, bool fromB);
uint8_t DMAPn; // DMA/HDMA parameters
uint8_t BBADn; // B-bus address
uint8_t UNUSEDn; // Unused byte
uint32_t A1Tn; // DMA Current Address / HDMA Table Start Address
uint16_t DASn; // DMA Byte-Counter / HDMA indirect table address
uint16_t A2An; // HDMA Table Current Address
uint8_t NLTRn; // HDMA Line-Counter
};
Channel channels[8];
uint8_t MDMAEN = 0; // Start DMA transfer uint8_t Read(MemoryImpl* memory, uint16_t address);
uint8_t HDMAEN = 0; // Enable HDMA transfers void Write(MemoryImpl* memory, uint16_t address, uint8_t data);
}; void StartDma(MemoryImpl* memory, uint8_t val, bool hdma);
void DoDma(SNES* snes, MemoryImpl* memory, int cycles);
} // namespace dma
} // namespace memory } // namespace memory
} // namespace emu } // namespace emu
} // namespace app } // namespace app

View File

@@ -0,0 +1,37 @@
#ifndef YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H
#define YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H
#include <cstdint>
namespace yaze {
namespace app {
namespace emu {
namespace memory {
typedef struct DmaChannel {
uint8_t bAdr;
uint16_t aAdr;
uint8_t aBank;
uint16_t size; // also indirect hdma adr
uint8_t indBank; // hdma
uint16_t tableAdr; // hdma
uint8_t repCount; // hdma
uint8_t unusedByte;
bool dmaActive;
bool hdmaActive;
uint8_t mode;
bool fixed;
bool decrement;
bool indirect; // hdma
bool fromB;
bool unusedBit;
bool doTransfer; // hdma
bool terminated; // hdma
} DmaChannel;
} // namespace memory
} // namespace emu
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H

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