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
run: sudo apt install libwavpack-dev
- name: Install Abseil-cpp
run: sudo apt install libabsl-dev
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type

12
.gitmodules vendored
View File

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

View File

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

View File

@@ -2689,7 +2689,7 @@ CALL_GRAPH = YES
# The default value is: NO.
# 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
# hierarchy of all classes instead of a textual one.

View File

@@ -34,7 +34,7 @@ Building and installation
## Documentation
- 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
--------
@@ -44,9 +44,10 @@ SDL2, ImGui and Abseil are subject to respective licenses.
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_CXX_STANDARD 17)
set(ABSL_USE_GOOGLETEST_HEAD ON)

View File

@@ -21,6 +21,14 @@ target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
target_compile_definitions(ImGuiColorTextEdit PUBLIC
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
set(IMGUI_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(
IMGUI_SRC
${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)
add_subdirectory(src/lib/SDL)
else()
find_package(SDL2)
endif()
set(SDL2MIXER_OPUS OFF)
set(SDL2MIXER_FLAC OFF)
set(SDL2MIXER_MOD OFF)
set(SDL2MIXER_MIDI_FLUIDSYNTH OFF)
find_library(SDL_MIXER_LIBRARY
NAMES SDL_mixer
HINTS
ENV SDLMIXERDIR
ENV SDLDIR
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
)
add_subdirectory(src/lib/SDL_mixer)
find_package(SDL2_image)
endif()

View File

@@ -3,22 +3,7 @@ set(
app/core/common.cc
app/core/controller.cc
app/core/labeling.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
app/emu/emulator.cc
)
set(
@@ -32,32 +17,18 @@ set(
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(
YAZE_GUI_SRC
app/gui/asset_browser.cc
app/gui/canvas.cc
app/gui/input.cc
app/gui/style.cc
app/gui/widgets.cc
app/gui/color.cc
app/gui/pipeline.cc
app/gui/zeml.cc
)
set(
YAZE_APP_EMU_SRC
app/emu/emulator.cc
app/emu/audio/apu.cc
app/emu/audio/spc700.cc
app/emu/audio/dsp.cc
@@ -92,6 +63,7 @@ if(APPLE)
app/core/platform/app_delegate.mm
app/core/platform/font_loader.mm
app/core/platform/clipboard.mm
app/core/platform/file_path.mm
)
find_library(COCOA_LIBRARY Cocoa)
@@ -102,7 +74,7 @@ if(APPLE)
endif()
include(app/CMakeLists.txt)
include(cli/CMakeLists.txt)
# include(cli/CMakeLists.txt) Excluded for now, macOS include breaks action build
if (UNIX)
target_compile_definitions(yaze PRIVATE "linux")
@@ -135,4 +107,6 @@ set_target_properties(yaze
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
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(
yaze
app/yaze.cc
@@ -9,14 +12,15 @@ add_executable(
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
)
target_include_directories(
yaze PUBLIC
lib/
app/
lib/SDL_mixer/include/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
@@ -27,7 +31,7 @@ target_link_libraries(
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
SDL2_mixer
ImGuiTestEngine
ImGui
)

View File

@@ -1,6 +1,6 @@
#include "common.h"
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <chrono>
#include <cstdint>
@@ -26,11 +26,11 @@ std::string UppercaseHexByte(uint8_t byte, bool leading) {
return result;
}
std::string UppercaseHexWord(uint16_t word) {
std::string result = absl::StrFormat("0x%04x", word);
std::string result = absl::StrFormat("0x%04X", word);
return result;
}
std::string UppercaseHexLong(uint32_t dword) {
std::string result = absl::StrFormat("0x%08x", dword);
std::string result = absl::StrFormat("0x%06X", dword);
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
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 app
} // namespace yaze

View File

@@ -1,7 +1,7 @@
#ifndef YAZE_CORE_COMMON_H
#define YAZE_CORE_COMMON_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <chrono>
#include <cstdint>
@@ -32,7 +32,7 @@ class ExperimentFlags {
bool kUseBitmapManager = true;
// Log instructions to the GUI debugger.
bool kLogInstructions = false;
bool kLogInstructions = true;
// Flag to enable ImGui input config flags. Currently is
// 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.
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
// save routine for the Rom. In practice, just the overworld tilemap
// and tile32 save.
@@ -66,6 +69,9 @@ class ExperimentFlags {
// Log to the console.
bool kLogToConsole = false;
// Load audio device for emulator
bool kLoadAudioDevice = false;
// Overworld flags
struct Overworld {
// 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);
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);
@@ -205,13 +216,19 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
uint16_t ldle16b(uint8_t const *const p_arr);
void stle16b(uint8_t *const p_arr, uint16_t const p_val);
void stle32b(uint8_t *const p_arr, uint32_t const p_val);
void stle32b_i(uint8_t *const p_arr, size_t const p_index,
uint32_t const p_val);
struct FolderItem {
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 app
} // namespace yaze
#endif
#endif

View File

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

View File

@@ -1,11 +1,22 @@
#include "controller.h"
#include <SDL.h>
#include <SDL_mixer.h>
#include <imgui/backends/imgui_impl_sdl2.h>
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#if defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1
#include "imgui/backends/imgui_impl_metal.h"
#elif TARGET_OS_IPHONE == 1
#include "imgui/backends/imgui_impl_metal.h"
#elif TARGET_OS_MAC == 1
#endif
#endif
#include <memory>
@@ -22,8 +33,37 @@ namespace core {
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() {
ImGuiIO &io = ImGui::GetIO();
io.KeyMap[ImGuiKey_LeftSuper] = SDL_GetScancodeFromKey(SDLK_LGUI);
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
@@ -49,6 +89,44 @@ void InitializeKeymap() {
io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8);
io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9);
io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0);
io.KeyMap[ImGuiKey_A] = SDL_GetScancodeFromKey(SDLK_a);
io.KeyMap[ImGuiKey_B] = SDL_GetScancodeFromKey(SDLK_b);
io.KeyMap[ImGuiKey_C] = SDL_GetScancodeFromKey(SDLK_c);
io.KeyMap[ImGuiKey_D] = SDL_GetScancodeFromKey(SDLK_d);
io.KeyMap[ImGuiKey_E] = SDL_GetScancodeFromKey(SDLK_e);
io.KeyMap[ImGuiKey_F] = SDL_GetScancodeFromKey(SDLK_f);
io.KeyMap[ImGuiKey_G] = SDL_GetScancodeFromKey(SDLK_g);
io.KeyMap[ImGuiKey_H] = SDL_GetScancodeFromKey(SDLK_h);
io.KeyMap[ImGuiKey_I] = SDL_GetScancodeFromKey(SDLK_i);
io.KeyMap[ImGuiKey_J] = SDL_GetScancodeFromKey(SDLK_j);
io.KeyMap[ImGuiKey_K] = SDL_GetScancodeFromKey(SDLK_k);
io.KeyMap[ImGuiKey_L] = SDL_GetScancodeFromKey(SDLK_l);
io.KeyMap[ImGuiKey_M] = SDL_GetScancodeFromKey(SDLK_m);
io.KeyMap[ImGuiKey_N] = SDL_GetScancodeFromKey(SDLK_n);
io.KeyMap[ImGuiKey_O] = SDL_GetScancodeFromKey(SDLK_o);
io.KeyMap[ImGuiKey_P] = SDL_GetScancodeFromKey(SDLK_p);
io.KeyMap[ImGuiKey_Q] = SDL_GetScancodeFromKey(SDLK_q);
io.KeyMap[ImGuiKey_R] = SDL_GetScancodeFromKey(SDLK_r);
io.KeyMap[ImGuiKey_S] = SDL_GetScancodeFromKey(SDLK_s);
io.KeyMap[ImGuiKey_T] = SDL_GetScancodeFromKey(SDLK_t);
io.KeyMap[ImGuiKey_U] = SDL_GetScancodeFromKey(SDLK_u);
io.KeyMap[ImGuiKey_V] = SDL_GetScancodeFromKey(SDLK_v);
io.KeyMap[ImGuiKey_W] = SDL_GetScancodeFromKey(SDLK_w);
io.KeyMap[ImGuiKey_X] = SDL_GetScancodeFromKey(SDLK_x);
io.KeyMap[ImGuiKey_Y] = SDL_GetScancodeFromKey(SDLK_y);
io.KeyMap[ImGuiKey_Z] = SDL_GetScancodeFromKey(SDLK_z);
io.KeyMap[ImGuiKey_F1] = SDL_GetScancodeFromKey(SDLK_F1);
io.KeyMap[ImGuiKey_F2] = SDL_GetScancodeFromKey(SDLK_F2);
io.KeyMap[ImGuiKey_F3] = SDL_GetScancodeFromKey(SDLK_F3);
io.KeyMap[ImGuiKey_F4] = SDL_GetScancodeFromKey(SDLK_F4);
io.KeyMap[ImGuiKey_F5] = SDL_GetScancodeFromKey(SDLK_F5);
io.KeyMap[ImGuiKey_F6] = SDL_GetScancodeFromKey(SDLK_F6);
io.KeyMap[ImGuiKey_F7] = SDL_GetScancodeFromKey(SDLK_F7);
io.KeyMap[ImGuiKey_F8] = SDL_GetScancodeFromKey(SDLK_F8);
io.KeyMap[ImGuiKey_F9] = SDL_GetScancodeFromKey(SDLK_F9);
io.KeyMap[ImGuiKey_F10] = SDL_GetScancodeFromKey(SDLK_F10);
io.KeyMap[ImGuiKey_F11] = SDL_GetScancodeFromKey(SDLK_F11);
io.KeyMap[ImGuiKey_F12] = SDL_GetScancodeFromKey(SDLK_F12);
}
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
@@ -66,25 +144,63 @@ void InitializeClipboard() {
io.ClipboardUserData = nullptr;
}
void HandleKeyDown(SDL_Event &event) {
void HandleKeyDown(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO();
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
switch (event.key.keysym.sym) {
case SDLK_UP:
case SDLK_DOWN:
case SDLK_RETURN:
case SDLK_BACKSPACE:
case SDLK_LSHIFT:
case SDLK_LCTRL:
case SDLK_TAB:
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
break;
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:
break;
}
}
void HandleKeyUp(SDL_Event &event) {
void HandleKeyUp(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO();
int key = event.key.keysym.scancode;
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
@@ -93,6 +209,47 @@ void HandleKeyUp(SDL_Event &event) {
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
switch (event.key.keysym.sym) {
case SDLK_z:
editor.emulator().snes().SetButtonState(1, 0, false);
break;
case SDLK_a:
editor.emulator().snes().SetButtonState(1, 1, false);
break;
case SDLK_RSHIFT:
editor.emulator().snes().SetButtonState(1, 2, false);
break;
case SDLK_RETURN:
editor.emulator().snes().SetButtonState(1, 3, false);
break;
case SDLK_UP:
editor.emulator().snes().SetButtonState(1, 4, false);
break;
case SDLK_DOWN:
editor.emulator().snes().SetButtonState(1, 5, false);
break;
case SDLK_LEFT:
editor.emulator().snes().SetButtonState(1, 6, false);
break;
case SDLK_RIGHT:
editor.emulator().snes().SetButtonState(1, 7, false);
break;
case SDLK_x:
editor.emulator().snes().SetButtonState(1, 8, false);
break;
case SDLK_s:
editor.emulator().snes().SetButtonState(1, 9, false);
break;
case SDLK_d:
editor.emulator().snes().SetButtonState(1, 10, false);
break;
case SDLK_c:
editor.emulator().snes().SetButtonState(1, 11, false);
break;
default:
break;
}
}
void ChangeWindowSizeEvent(SDL_Event &event) {
@@ -117,12 +274,36 @@ void HandleMouseMovement(int &wheel) {
} // 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(CreateRenderer())
RETURN_IF_ERROR(CreateGuiContext())
if (flags()->kLoadAudioDevice) {
RETURN_IF_ERROR(LoadAudioDevice())
master_editor_.emulator().set_audio_buffer(audio_buffer_);
master_editor_.emulator().set_audio_device_id(audio_device_);
}
InitializeKeymap();
master_editor_.SetupScreen(renderer_);
master_editor_.SetupScreen(renderer_, filename);
active_ = true;
return absl::OkStatus();
}
@@ -135,10 +316,10 @@ void Controller::OnInput() {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
HandleKeyDown(event);
HandleKeyDown(event, master_editor_);
break;
case SDL_KEYUP:
HandleKeyUp(event);
HandleKeyUp(event, master_editor_);
break;
case SDL_TEXTINPUT:
io.AddInputCharactersUTF8(event.text.text);
@@ -166,26 +347,57 @@ void Controller::OnInput() {
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 {
ImGui::Render();
SDL_RenderClear(renderer_.get());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get());
SDL_RenderPresent(renderer_.get());
}
void Controller::OnExit() {
master_editor_.Shutdown();
Mix_CloseAudio();
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
if (flags()->kLoadAudioDevice) {
SDL_PauseAudioDevice(audio_device_, 1);
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();
SDL_Quit();
}
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(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
} else {
@@ -206,11 +418,6 @@ absl::Status Controller::CreateSDL_Window() {
return absl::InternalError(
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();
}
@@ -264,13 +471,13 @@ absl::Status Controller::CreateGuiContext() {
absl::Status Controller::LoadFontFamilies() const {
ImGuiIO &io = ImGui::GetIO();
// Define constants
static const char *KARLA_REGULAR = "assets/font/Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "assets/font/Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "assets/font/Cousine-Regular.ttf";
static const char *DROID_SANS = "assets/font/DroidSans.ttf";
static const char *NOTO_SANS_JP = "assets/font/NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "assets/font/IBMPlexSansJP-Bold.ttf";
const char *font_path = "assets/font/";
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 14.0f;
static const float FONT_SIZE_DROID_SANS = 16.0f;
static const float ICON_FONT_SIZE = 18.0f;
@@ -299,23 +506,78 @@ absl::Status Controller::LoadFontFamilies() const {
float font_size =
(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(
absl::StrFormat("Failed to load font from %s", font_path));
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
// 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);
// 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());
}
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 app
} // namespace yaze
} // namespace yaze

View File

@@ -2,10 +2,11 @@
#define YAZE_APP_CORE_CONTROLLER_H
#include <SDL.h>
#include <imgui/backends/imgui_impl_sdl2.h>
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imconfig.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include <memory>
@@ -22,6 +23,8 @@ namespace yaze {
namespace app {
namespace core {
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
/**
* @brief Main controller for the application.
*
@@ -31,12 +34,20 @@ namespace core {
class Controller : public ExperimentFlags {
public:
bool IsActive() const { return active_; }
absl::Status OnEntry();
absl::Status OnEntry(std::string filename = "");
void OnInput();
void OnLoad();
absl::Status OnLoad();
void DoRender() const;
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:
struct sdl_deleter {
void operator()(SDL_Window *p) const {
@@ -52,16 +63,17 @@ class Controller : public ExperimentFlags {
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; }
friend int ::main(int argc, char **argv);
bool active_;
Platform platform_;
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_Renderer> renderer_;
};
@@ -70,4 +82,4 @@ class Controller : public ExperimentFlags {
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_CONTROLLER_H
#endif // YAZE_APP_CORE_CONTROLLER_H

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#ifdef TARGET_OS_MAC
#ifdef __cplusplus
extern "C" {
#endif
@@ -11,4 +13,6 @@ void InitializeCocoa();
} // extern "C"
#endif
#endif // TARGET_OS_MAC
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H

View File

@@ -1,12 +1,26 @@
// 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/controller.h"
#import "app/core/platform/file_dialog.h"
#import "app/editor/utils/editor.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>
- (void)setupMenus;
// - (void)changeApplicationIcon;
@@ -195,7 +209,14 @@
}
- (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 {
@@ -215,4 +236,8 @@ extern "C" void InitializeCocoa() {
}
}
@end
@end
#endif
#endif

View File

@@ -3,6 +3,7 @@
#include <iostream>
#include <vector>
#ifdef TARGET_OS_MAC
#import <Cocoa/Cocoa.h>
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
@@ -39,4 +40,6 @@ void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& he
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
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>
#ifdef _WIN32
@@ -42,13 +45,39 @@ class FileDialogWrapper {
#elif defined(__APPLE__)
#include "TargetConditionals.h"
#include <string>
#include <vector>
#ifdef TARGET_OS_MAC
// Other kinds of Mac OS
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);
};
#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__)
class FileDialogWrapper {
@@ -62,4 +91,6 @@ class FileDialogWrapper {
#else
#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"
#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() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:YES];
@@ -15,3 +58,59 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
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 <imgui/imgui.h>
#include "imgui/imgui.h"
#include <string>
#include <vector>

View File

@@ -2,14 +2,25 @@
#ifndef FONTLOADER_H
#define FONTLOADER_H
// Function declaration for loading system fonts into ImGui
void LoadSystemFonts();
#include "TargetConditionals.h"
#ifdef _WIN32
#include <Windows.h>
// Windows specific function declaration for loading system fonts into ImGui
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
DWORD FontType, LPARAM lParam);
#elif __APPLE__
#ifdef TARGET_OS_MAC
void LoadSystemFonts();
#elif TARGET_OS_IPHONE
void LoadSystemFonts();
#endif
#endif
#endif // FONTLOADER_H

View File

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

View File

@@ -1,13 +1,13 @@
#ifndef 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/editor/utils/editor.h"
#include "app/core/labeling.h"
#include "app/editor/modules/gfx_group_editor.h"
#include "app/editor/modules/palette_editor.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
@@ -46,12 +46,15 @@ class DungeonEditor : public Editor,
public SharedRom,
public core::ExperimentFlags {
public:
DungeonEditor() { type_ = EditorType::kDungeon; }
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 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"); }
void add_room(int i) { active_rooms_.push_back(i); }
@@ -74,8 +77,6 @@ class DungeonEditor : public Editor,
void DrawTileSelector();
void DrawObjectRenderer();
void LoadRoomEntrances();
void CalculateUsageStats();
void DrawUsageStats();
void DrawUsageGrid();
@@ -141,6 +142,8 @@ class DungeonEditor : public Editor,
std::unordered_map<int, int> room_size_addresses_;
std::unordered_map<int, ImVec4> room_palette_;
absl::Status status_;
};
} // 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
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/modules/palette_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/pipeline.h"
#include "app/gui/widgets.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
@@ -42,7 +40,9 @@ class GfxGroupEditor : public SharedRom {
selected_spriteset_ = spriteset;
}
void InitBlockset(gfx::Bitmap tile16_blockset);
void InitBlockset(gfx::Bitmap* tile16_blockset) {
tile16_blockset_bmp_ = tile16_blockset;
}
private:
int preview_palette_id_ = 0;
@@ -50,8 +50,7 @@ class GfxGroupEditor : public SharedRom {
uint8_t selected_blockset_ = 0;
uint8_t selected_roomset_ = 0;
uint8_t selected_spriteset_ = 0;
PaletteEditor palette_editor_;
uint8_t selected_paletteset_ = 0;
gui::Canvas blockset_canvas_;
gui::Canvas roomset_canvas_;
@@ -59,7 +58,7 @@ class GfxGroupEditor : public SharedRom {
gfx::SnesPalette palette_;
gfx::PaletteGroup palette_group_;
gfx::Bitmap tile16_blockset_bmp_;
gfx::Bitmap* tile16_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_;
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 <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "absl/status/statusor.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/compression.h"
#include "app/gfx/scad_format.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/gui/style.h"
#include "app/rom.h"
@@ -24,12 +25,18 @@ namespace yaze {
namespace app {
namespace editor {
using gfx::kPaletteGroupAddressesKeys;
using ImGui::Button;
using ImGui::InputInt;
using ImGui::InputText;
using ImGui::SameLine;
using ImGui::TableNextColumn;
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
constexpr ImGuiTableFlags kGfxEditTableFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
@@ -40,9 +47,20 @@ constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton;
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
absl::Status GraphicsEditor::Update() {
TAB_BAR("##TabBar")
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_ = UpdateLinkGfxView();
END_TAB_BAR()
@@ -109,7 +127,7 @@ void GraphicsEditor::DrawGfxEditToolset() {
TableNextColumn();
if (Button(ICON_MD_CONTENT_COPY)) {
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);
}
HOVER_HINT("Copy to Clipboard");
@@ -124,10 +142,8 @@ void GraphicsEditor::DrawGfxEditToolset() {
->mutable_bitmap_manager()
->mutable_bitmap(current_sheet_)
->Create(width, height, 8, png_data);
rom()->UpdateBitmap(rom()
->mutable_bitmap_manager()
->mutable_bitmap(current_sheet_)
.get());
rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_));
}
}
HOVER_HINT("Paste from Clipboard");
@@ -148,7 +164,7 @@ void GraphicsEditor::DrawGfxEditToolset() {
TableNextColumn();
auto bitmap = rom()->bitmap_manager()[current_sheet_];
auto palette = bitmap->palette();
auto palette = bitmap.palette();
for (int i = 0; i < 8; i++) {
ImGui::SameLine();
auto color =
@@ -161,7 +177,7 @@ void GraphicsEditor::DrawGfxEditToolset() {
}
TableNextColumn();
gui::InputHexByte("Tile Size", &tile_size_, 0x01);
gui::InputHexByte("Tile Size", &tile_size_);
ImGui::EndTable();
}
@@ -172,30 +188,38 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
"##GfxEditChild", ImVec2(0, 0), true,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
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()) {
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
ImVec2(0x100 + 1, 0x40 + 1), true,
ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleVar();
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_.DrawContextMenu();
if (value.get()->is_active()) {
auto texture = value.get()->texture();
if (value.is_active()) {
auto texture = value.texture();
graphics_bin_canvas_.draw_list()->AddImage(
(void*)texture,
ImVec2(graphics_bin_canvas_.zero_point().x + 2,
graphics_bin_canvas_.zero_point().y + 2),
ImVec2(graphics_bin_canvas_.zero_point().x +
value.get()->width() * sheet_scale_,
value.width() * sheet_scale_,
graphics_bin_canvas_.zero_point().y +
value.get()->height() * sheet_scale_));
value.height() * sheet_scale_));
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
current_sheet_ = key;
@@ -224,6 +248,8 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
ImGui::EndChild();
}
ImGui::PopStyleVar();
ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io);
ImGui::EndChild();
return absl::OkStatus();
}
@@ -232,8 +258,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
static int next_tab_id = 0;
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
if (ImGui::TabItemButton(
"+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
ImGuiTabItemFlags_NoTooltip)) {
open_sheets_.insert(next_tab_id++);
}
@@ -269,7 +295,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
};
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_);
ImGui::EndChild();
@@ -302,7 +328,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
current_sheet_ = id;
// ImVec2(0x100, 0x40),
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() {
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()) {
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");
ImGui::SetNextItemWidth(100.f);
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
@@ -335,21 +359,20 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
ImGui::SetNextItemWidth(100.f);
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();
}
@@ -366,10 +389,12 @@ absl::Status GraphicsEditor::UpdateLinkGfxView() {
NEXT_COLUMN();
link_canvas_.DrawBackground();
link_canvas_.DrawGrid(16.0f);
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 y_offset = core::kTilesheetHeight * i * 4;
link_canvas_.DrawContextMenu(&link_sheet);
link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4);
i++;
}
@@ -435,7 +460,7 @@ absl::Status GraphicsEditor::UpdateScadView() {
if (super_donkey_) {
if (refresh_graphics_) {
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_]);
rom()->UpdateBitmap(&graphics_bin_[i]);
}
@@ -498,19 +523,21 @@ absl::Status GraphicsEditor::DrawCgxImport() {
is_open_ = 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_,
decoded_cgx_, extra_cgx_data_);
cgx_bitmap_.InitializeFromData(0x80, 0x200, 8, decoded_cgx_);
cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_);
if (col_file_) {
cgx_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&cgx_bitmap_);
}
});
}
return absl::OkStatus();
}
@@ -532,7 +559,7 @@ absl::Status GraphicsEditor::DrawScrImport() {
InputInt("SCR Mod", &scr_mod_value_);
gui::ButtonPipe("Load Scr Data", [this]() {
if (ImGui::Button("Load Scr Data")) {
status_ =
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_,
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_) {
scr_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&scr_bitmap_);
}
});
}
return absl::OkStatus();
}
@@ -582,8 +609,9 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
is_open_ = true;
});
gui::ButtonPipe("Copy COL Path",
[this]() { ImGui::SetClipboardText(col_file_path_); });
if (ImGui::Button("Copy Col Path")) {
ImGui::SetClipboardText(col_file_path_);
}
if (rom()->is_loaded()) {
gui::TextWithSeparators("ROM Palette");
@@ -655,8 +683,9 @@ absl::Status GraphicsEditor::DrawFileImport() {
is_open_ = true;
});
gui::ButtonPipe("Copy File Path",
[this]() { ImGui::SetClipboardText(file_path_); });
if (Button("Copy File Path")) {
ImGui::SetClipboardText(file_path_);
}
gui::InputHex("BIN Offset", &current_offset_);
gui::InputHex("BIN Size", &bin_size_);
@@ -675,7 +704,7 @@ absl::Status GraphicsEditor::DrawFileImport() {
absl::Status GraphicsEditor::DrawClipboardImport() {
gui::TextWithSeparators("Clipboard Import");
gui::ButtonPipe("Paste from Clipboard", [this]() {
if (Button("Paste From Clipboard")) {
const char* text = ImGui::GetClipboardText();
if (text) {
const auto clipboard_data = Bytes(text, text + strlen(text));
@@ -684,12 +713,12 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
is_open_ = true;
open_memory_editor_ = true;
}
});
}
gui::InputHex("Offset", &clipboard_offset_);
gui::InputHex("Size", &clipboard_size_);
gui::InputHex("Num Sheets", &num_sheets_to_load_);
gui::ButtonPipe("Decompress Clipboard Data", [this]() {
if (Button("Decompress Clipboard Data")) {
if (temp_rom_.is_loaded()) {
status_ = DecompressImportData(0x40000);
} else {
@@ -697,7 +726,7 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
"Please paste data into the clipboard before "
"decompressing.");
}
});
}
return absl::OkStatus();
}
@@ -740,9 +769,9 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
auto palette_group = rom()->palette_group().overworld_animated;
z3_rom_palette_ = palette_group[current_palette_];
if (col_file_) {
bin_bitmap_.ApplyPalette(col_file_palette_);
status_ = bin_bitmap_.ApplyPalette(col_file_palette_);
} 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,
core::kTilesheetDepth, converted_sheet);
if (col_file_) {
graphics_bin_[i].ApplyPalette(
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
@@ -773,7 +802,7 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
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]);
@@ -791,14 +820,14 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet);
if (col_file_) {
graphics_bin_[i].ApplyPalette(
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
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]);
@@ -812,4 +841,4 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
} // namespace editor
} // namespace app
} // namespace yaze
} // namespace yaze

View File

@@ -1,19 +1,20 @@
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "absl/status/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/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
@@ -43,22 +44,6 @@ const std::string kSuperDonkeySprites[] = {
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
"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
* @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,
* and memory editor.
*/
class GraphicsEditor : public SharedRom {
class GraphicsEditor : public SharedRom, public Editor {
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:
enum class GfxEditMode {
@@ -163,6 +157,8 @@ class GraphicsEditor : public SharedRom {
char tilemap_file_path_[256] = "";
char tilemap_file_name_[256] = "";
gui::GfxSheetAssetBrowser asset_browser_;
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
Rom temp_rom_;
@@ -193,9 +189,10 @@ class GraphicsEditor : public SharedRom {
gui::Canvas import_canvas_;
gui::Canvas scr_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::Canvas link_canvas_{
"LinkCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k16x16};
absl::Status status_;
@@ -205,4 +202,4 @@ class GraphicsEditor : public SharedRom {
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H

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
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
#include <imgui/imgui.h>
#include "imgui/imgui.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/gui/canvas.h"
#include "app/gui/icons.h"
@@ -13,18 +15,6 @@ namespace yaze {
namespace app {
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 {
struct PaletteChange {
std::string group_name;
@@ -56,9 +46,9 @@ class PaletteEditorHistory {
}
// Restore the original color
gfx::SnesColor GetOriginalColor(const std::string& groupName,
size_t paletteIndex,
size_t colorIndex) const {
gfx::SnesColor RestoreOriginalColor(const std::string& groupName,
size_t paletteIndex,
size_t colorIndex) const {
for (const auto& change : recentChanges) {
if (change.group_name == groupName &&
change.palette_index == paletteIndex &&
@@ -71,6 +61,15 @@ class PaletteEditorHistory {
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:
std::deque<PaletteChange> recentChanges;
static const size_t maxHistorySize = 50; // or any other number you deem fit
@@ -81,21 +80,36 @@ class PaletteEditorHistory {
* @class PaletteEditor
* @brief Allows the user to view and edit in game palettes.
*/
class PaletteEditor : public SharedRom {
class PaletteEditor : public SharedRom, public Editor {
public:
absl::Status Update();
absl::Status DrawPaletteGroups();
PaletteEditor() {
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 ResetColorToOriginal(gfx::SnesPalette& palette, int index,
const gfx::SnesPalette& originalPalette);
void DisplayPalette(gfx::SnesPalette& palette, bool loaded);
void DrawPortablePalette(gfx::SnesPalette& palette);
absl::Status DrawPaletteGroup(int category);
absl::Status DrawPaletteGroup(int category, bool right_side = false);
void DrawCustomPalette();
void DrawModifiedColors();
private:
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
absl::Status InitializeSavedPalette(const gfx::SnesPalette& palette) {
for (int n = 0; n < palette.size(); n++) {
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
@@ -108,18 +122,15 @@ class PaletteEditor : public SharedRom {
}
absl::Status status_;
palette_internal::PaletteEditorHistory history_;
ImVec4 saved_palette_[256] = {};
gfx::SnesColor current_color_;
ImGuiColorEditFlags color_popup_flags =
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha;
ImGuiColorEditFlags palette_button_flags_2 = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip;
GfxGroupEditor gfx_group_editor_;
std::vector<gfx::SnesColor> custom_palette_;
ImVec4 saved_palette_[256] = {};
palette_internal::PaletteEditorHistory history_;
};
} // 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 <fstream>
@@ -25,9 +25,7 @@ namespace yaze {
namespace app {
namespace editor {
ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); }
void ScreenEditor::Update() {
absl::Status ScreenEditor::Update() {
TAB_BAR("##TabBar")
TAB_ITEM("Dungeon Maps")
if (rom()->is_loaded()) {
@@ -39,6 +37,8 @@ void ScreenEditor::Update() {
DrawTitleScreenEditor();
DrawNamingScreenEditor();
END_TAB_BAR()
return absl::OkStatus();
}
void ScreenEditor::DrawInventoryMenuEditor() {
@@ -46,7 +46,7 @@ void ScreenEditor::DrawInventoryMenuEditor() {
static bool create = false;
if (!create && rom()->is_loaded()) {
inventory_.Create();
status_ = inventory_.Create();
palette_ = inventory_.Palette();
create = true;
}
@@ -74,7 +74,7 @@ void ScreenEditor::DrawInventoryMenuEditor() {
tilesheet_canvas_.DrawOverlay();
ImGui::TableNextColumn();
gui::DisplayPalette(palette_, create);
status_ = gui::DisplayPalette(palette_, create);
ImGui::EndTable();
}
@@ -233,8 +233,8 @@ absl::Status ScreenEditor::LoadDungeonMapTile16() {
tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4);
}
tile16_sheet_.mutable_bitmap()->ApplyPalette(
*rom()->mutable_dungeon_palette(3));
RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
@@ -293,13 +293,7 @@ void ScreenEditor::DrawDungeonMapsTabs() {
std::string label =
dungeon_map_labels_[selected_dungeon][floor_number][j];
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);
@@ -369,6 +363,9 @@ void ScreenEditor::DrawDungeonMapsEditor() {
rom()->resource_label()->SelectableLabelWithNameEdit(
selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
dungeon_names[i]);
if (ImGui::IsItemClicked()) {
selected_dungeon = i;
}
}
// Map column
@@ -437,4 +434,4 @@ void ScreenEditor::DrawToolset() {
} // namespace editor
} // namespace app
} // namespace yaze
} // namespace yaze

View File

@@ -1,12 +1,13 @@
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <array>
#include "absl/status/status.h"
#include "app/core/constants.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
@@ -36,10 +37,21 @@ namespace editor {
*
* The class inherits from the SharedRom class.
*/
class ScreenEditor : public SharedRom {
class ScreenEditor : public SharedRom, public Editor {
public:
ScreenEditor();
void Update();
ScreenEditor() {
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();
@@ -83,10 +95,12 @@ class ScreenEditor : public SharedRom {
gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_;
absl::Status status_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif
#endif

View File

@@ -1,12 +1,13 @@
#include "tile16_editor.h"
#include <imgui/imgui.h>
#include "ImGuiFileDialog/ImGuiFileDialog.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/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
@@ -15,9 +16,7 @@
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/gui/style.h"
#include "app/gui/widgets.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
@@ -26,50 +25,73 @@ namespace app {
namespace editor {
using ImGui::BeginChild;
using ImGui::BeginMenu;
using ImGui::BeginMenuBar;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::Checkbox;
using ImGui::Combo;
using ImGui::EndChild;
using ImGui::EndMenu;
using ImGui::EndMenuBar;
using ImGui::EndTabBar;
using ImGui::EndTabItem;
using ImGui::EndTable;
using ImGui::GetContentRegionAvail;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
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() {
if (rom()->is_loaded() && !map_blockset_loaded_) {
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;
if (!map_blockset_loaded_) {
return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
}
RETURN_IF_ERROR(DrawMenu());
if (BeginTabBar("Tile16 Editor Tabs")) {
RETURN_IF_ERROR(DrawTile16Editor());
RETURN_IF_ERROR(UpdateTile16Transfer());
ImGui::EndTabBar();
EndTabBar();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawMenu() {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("View")) {
ImGui::Checkbox("Show Collision Types",
tile8_source_canvas_.custom_labels_enabled());
ImGui::EndMenu();
if (BeginMenuBar()) {
if (BeginMenu("View")) {
Checkbox("Show Collision Types",
tile8_source_canvas_.custom_labels_enabled());
EndMenu();
}
ImGui::EndMenuBar();
EndMenuBar();
}
return absl::OkStatus();
@@ -80,9 +102,9 @@ absl::Status Tile16Editor::DrawTile16Editor() {
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
ImGui::GetContentRegionAvail().x);
GetContentRegionAvail().x);
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
GetContentRegionAvail().x);
TableHeadersRow();
TableNextRow();
TableNextColumn();
@@ -92,10 +114,10 @@ absl::Status Tile16Editor::DrawTile16Editor() {
RETURN_IF_ERROR(UpdateTile16Edit());
RETURN_IF_ERROR(DrawTileEditControls());
ImGui::EndTable();
EndTable();
}
ImGui::EndTabItem();
EndTabItem();
}
return absl::OkStatus();
}
@@ -108,26 +130,23 @@ absl::Status Tile16Editor::UpdateBlockset() {
{
blockset_canvas_.DrawContextMenu();
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_.DrawOverlay();
ImGui::EndChild();
EndChild();
}
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.apply_changes();
if (notify_tile16.modified()) {
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;
RETURN_IF_ERROR(current_tile16_bmp_.ApplyPalette(
RETURN_IF_ERROR(current_tile16_bmp_->ApplyPalette(
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 =
(start_position.y + y) * tile16_size + ((start_position.x) + x);
int gfx_pixel_index = y * tile8_size + x;
current_tile16_bmp_.WriteToPixel(
current_tile16_bmp_->WriteToPixel(
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() {
auto ow_main_pal_group = rom()->palette_group().overworld_main;
if (ImGui::BeginChild("Tile8 Selector",
ImVec2(ImGui::GetContentRegionAvail().x, 0x175),
true)) {
if (BeginChild("Tile8 Selector", ImVec2(GetContentRegionAvail().x, 0x175),
true)) {
tile8_source_canvas_.DrawBackground();
tile8_source_canvas_.DrawContextMenu(&current_gfx_bmp_);
if (tile8_source_canvas_.DrawTileSelector(32)) {
@@ -185,7 +203,7 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
tile8_source_canvas_.DrawGrid();
tile8_source_canvas_.DrawOverlay();
}
ImGui::EndChild();
EndChild();
// The user selected a tile8
if (!tile8_source_canvas_.points().empty()) {
@@ -199,31 +217,31 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
rom()->UpdateBitmap(&current_gfx_individual_[current_tile8_]);
}
if (ImGui::BeginChild("Tile16 Editor Options",
ImVec2(ImGui::GetContentRegionAvail().x, 0x50), true)) {
if (BeginChild("Tile16 Editor Options",
ImVec2(GetContentRegionAvail().x, 0x50), true)) {
tile16_edit_canvas_.DrawBackground();
tile16_edit_canvas_.DrawContextMenu(&current_tile16_bmp_);
tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
tile16_edit_canvas_.DrawContextMenu(current_tile16_bmp_);
tile16_edit_canvas_.DrawBitmap(*current_tile16_bmp_, 0, 0, 4.0f);
if (!tile8_source_canvas_.points().empty()) {
if (tile16_edit_canvas_.DrawTilePainter(
current_gfx_individual_[current_tile8_], 16, 2.0f)) {
RETURN_IF_ERROR(
DrawToCurrentTile16(tile16_edit_canvas_.drawn_tile_position()));
rom()->UpdateBitmap(&current_tile16_bmp_);
rom()->UpdateBitmap(current_tile16_bmp_);
}
}
tile16_edit_canvas_.DrawGrid();
tile16_edit_canvas_.DrawOverlay();
}
ImGui::EndChild();
EndChild();
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawTileEditControls() {
ImGui::Separator();
ImGui::Text("Tile16 ID: %d", current_tile16_);
ImGui::Text("Tile8 ID: %d", current_tile8_);
ImGui::Text("Options:");
Separator();
Text("Tile16 ID: %d", current_tile16_);
Text("Tile8 ID: %d", current_tile8_);
Text("Options:");
gui::InputHexByte("Palette", &notify_palette.mutable_get());
notify_palette.apply_changes();
if (notify_palette.modified()) {
@@ -241,15 +259,15 @@ absl::Status Tile16Editor::DrawTileEditControls() {
RETURN_IF_ERROR(
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
RETURN_IF_ERROR(
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value));
current_tile16_bmp_->ApplyPaletteWithTransparent(palette, value));
rom()->UpdateBitmap(&current_gfx_bmp_);
rom()->UpdateBitmap(&current_tile16_bmp_);
rom()->UpdateBitmap(current_tile16_bmp_);
}
}
ImGui::Checkbox("X Flip", &x_flip);
ImGui::Checkbox("Y Flip", &y_flip);
ImGui::Checkbox("Priority Tile", &priority_tile);
Checkbox("X Flip", &x_flip);
Checkbox("Y Flip", &y_flip);
Checkbox("Priority Tile", &priority_tile);
return absl::OkStatus();
}
@@ -308,9 +326,9 @@ absl::Status Tile16Editor::UpdateTile16Transfer() {
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
ImGui::GetContentRegionAvail().x / 2);
GetContentRegionAvail().x / 2);
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
ImGui::GetContentRegionAvail().x / 2);
GetContentRegionAvail().x / 2);
TableHeadersRow();
TableNextRow();
@@ -320,17 +338,17 @@ absl::Status Tile16Editor::UpdateTile16Transfer() {
TableNextColumn();
RETURN_IF_ERROR(UpdateTransferTileCanvas());
ImGui::EndTable();
EndTable();
}
ImGui::EndTabItem();
EndTabItem();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
// Create a button for loading another ROM
if (ImGui::Button("Load ROM")) {
if (Button("Load ROM")) {
ImGuiFileDialog::Instance()->OpenDialog(
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", ".");
}
@@ -353,9 +371,9 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
palette_ = transfer_overworld_.AreaPalette();
// Create the tile16 blockset image
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x80,
transfer_overworld_.Tile16Blockset(),
transfer_blockset_bmp_, palette_));
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
0x80, 0x2000, 0x80, transfer_overworld_.Tile16Blockset(),
transfer_blockset_bmp_, palette_));
transfer_blockset_loaded_ = true;
}
@@ -367,6 +385,16 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
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 app
} // namespace yaze
} // namespace yaze

View File

@@ -1,22 +1,21 @@
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
#define YAZE_APP_EDITOR_TILE16EDITOR_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/editor/context/gfx_context.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/gfx_context.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
@@ -29,6 +28,11 @@ namespace editor {
*/
class Tile16Editor : public context::GfxContext, public SharedRom {
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 DrawMenu();
@@ -44,28 +48,9 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
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 set_tile16(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();
}
absl::Status SetCurrentTile(int id);
private:
bool map_blockset_loaded_ = false;
@@ -79,13 +64,6 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
core::NotifyValue<uint32_t> notify_tile16;
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
bool x_flip;
bool y_flip;
@@ -95,26 +73,24 @@ class Tile16Editor : public context::GfxContext, public SharedRom {
uint8_t* all_tiles_types_;
// 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};
gfx::Bitmap tile16_blockset_bmp_;
gfx::Bitmap* tile16_blockset_bmp_;
// 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};
gfx::Bitmap current_tile16_bmp_;
gfx::Bitmap current_tile8_bmp_;
gfx::Bitmap* current_tile16_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
gui::Canvas tile8_source_canvas_{
"Tile8SourceCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_;
std::vector<gfx::Tilesheet> current_tilesheets_;
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
gfx::Bitmap transfer_current_bmp_;
std::vector<Bytes> tile16_individual_data_;
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
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
#define IMGUI_DEFINE_MATH_OPERATORS 1
#define IMGUI_DEFINE_MATH_OPERATORS
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/core/constants.h"
#include "app/editor/context/gfx_context.h"
#include "app/editor/dungeon_editor.h"
#include "app/editor/graphics_editor.h"
#include "app/editor/modules/assembly_editor.h"
#include "app/editor/modules/music_editor.h"
#include "app/editor/modules/palette_editor.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/message/message_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h"
#include "app/editor/screen_editor.h"
#include "app/editor/sprite_editor.h"
#include "app/editor/settings_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/gfx_context.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/pipeline.h"
#include "app/rom.h"
namespace yaze {
@@ -56,14 +59,29 @@ class MasterEditor : public SharedRom,
public context::GfxContext,
public core::ExperimentFlags {
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();
void Shutdown() { overworld_editor_.Shutdown(); }
auto emulator() -> emu::Emulator& { return emulator_; }
auto quit() { return quit_; }
auto overworld_editor() -> OverworldEditor& { return overworld_editor_; }
private:
void ManageActiveEditors();
void ManageKeyboardShortcuts();
void OpenRomOrProject(const std::string& filename);
void DrawFileDialog();
void DrawStatusPopup();
void DrawAboutPopup();
@@ -73,10 +91,16 @@ class MasterEditor : public SharedRom,
void DrawFileMenu();
void DrawEditMenu();
void DrawViewMenu();
void DrawTestMenu();
void DrawProjectMenu();
void DrawHelpMenu();
void LoadRom();
void SaveRom();
absl::Status OpenProject();
bool quit_ = false;
bool about_ = false;
bool rom_info_ = false;
bool backup_rom_ = false;
@@ -91,6 +115,8 @@ class MasterEditor : public SharedRom,
emu::Emulator emulator_;
Project current_project_;
AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_;
@@ -99,12 +125,17 @@ class MasterEditor : public SharedRom,
PaletteEditor palette_editor_;
ScreenEditor screen_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 app
} // namespace yaze
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H

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

View File

@@ -1,11 +1,11 @@
#ifndef 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 "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/icons.h"
#include "app/gui/input.h"
@@ -59,9 +59,18 @@ static constexpr absl::string_view kSongNotes[] = {
* @class MusicEditor
* @brief A class for editing music data in a Rom.
*/
class MusicEditor : public SharedRom {
class MusicEditor : public SharedRom, public Editor {
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:
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
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "imgui/imgui.h"
#include <cmath>
#include <unordered_map>
@@ -12,18 +11,18 @@
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/core/common.h"
#include "app/editor/context/entrance_context.h"
#include "app/editor/context/gfx_context.h"
#include "app/editor/modules/gfx_group_editor.h"
#include "app/editor/modules/palette_editor.h"
#include "app/editor/modules/tile16_editor.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/tile16_editor.h"
#include "app/editor/overworld/entity.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/gui/zeml.h"
#include "app/rom.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 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
* @brief Manipulates the Overworld and OverworldMap data in a Rom.
@@ -81,15 +102,20 @@ constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
class OverworldEditor : public Editor,
public SharedRom,
public context::GfxContext,
public context::EntranceContext,
public EntranceContext,
public core::ExperimentFlags {
public:
OverworldEditor() { type_ = EditorType::kOverworld; }
void InitializeZeml();
absl::Status Update() final;
absl::Status Undo() { return absl::UnimplementedError("Undo"); }
absl::Status Redo() { return absl::UnimplementedError("Redo"); }
absl::Status Cut() { return absl::UnimplementedError("Cut"); }
absl::Status Copy() { return absl::UnimplementedError("Copy"); }
absl::Status Paste() { return absl::UnimplementedError("Paste"); }
absl::Status Find() { return absl::UnimplementedError("Find Unused Tiles"); }
auto overworld() { return &overworld_; }
@@ -99,25 +125,6 @@ class OverworldEditor : public Editor,
int jump_to_tab() { return jump_to_tab_; }
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.
*
@@ -128,7 +135,6 @@ class OverworldEditor : public Editor,
absl::Status LoadGraphics();
private:
absl::Status UpdateOverworldEdit();
absl::Status UpdateFullscreenCanvas();
absl::Status DrawToolset();
@@ -259,14 +265,15 @@ class OverworldEditor : public Editor,
PaletteEditor palette_editor_;
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::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::Canvas blockset_canvas_{ImVec2(0x100 + 1, 0x2000 + 1),
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100 + 1, 0x2000 + 1),
gui::CanvasGridSize::k32x32};
gui::Canvas graphics_bin_canvas_{
ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
"graphicsBinCanvas", ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
@@ -277,12 +284,13 @@ class OverworldEditor : public Editor,
gfx::Bitmap all_gfx_bmp;
gfx::BitmapTable maps_bmp_;
gfx::BitmapTable graphics_bin_;
gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_;
gfx::BitmapTable animated_maps_;
OWBlockset refresh_blockset_;
gui::zeml::Node layout_node_;
absl::Status status_;
};
} // 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 {
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
* @brief Interface for editor classes.
@@ -31,6 +49,13 @@ class Editor {
virtual absl::Status Redo() = 0;
virtual absl::Status Update() = 0;
virtual absl::Status Find() = 0;
EditorType type() const { return type_; }
protected:
EditorType type_;
};
} // 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 "app/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/modules/palette_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/pipeline.h"
#include "app/rom.h"
namespace yaze {

View File

@@ -1,19 +1,17 @@
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <cmath>
#include <vector>
#include "app/editor/utils/editor.h"
#include "app/editor/modules/palette_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/pipeline.h"
#include "app/rom.h"
namespace yaze {
@@ -25,9 +23,6 @@ namespace context {
* @brief Shared graphical context across editors.
*/
class GfxContext {
public:
absl::Status Update();
protected:
// Palettesets for the tile16 individual tiles
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 <SDL.h>
#include <cstdint>
#include <functional>
#include <iostream>
@@ -15,127 +17,191 @@ namespace app {
namespace emu {
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() {
// Set the clock frequency
clock_.SetFrequency(kApuClockSpeed);
// Initialize Digital Signal Processor Callbacks
dsp_.SetSampleFetcher([this](uint16_t address) -> uint8_t {
return this->FetchSampleFromRam(address);
});
dsp_.SetSamplePusher(
[this](int16_t sample) { this->PushToAudioBuffer(sample); });
ram.resize(0x10000);
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];
}
}
void Apu::Reset() {
clock_.ResetAccumulatedTime();
spc700_.Reset();
spc700_.Reset(true);
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() {
auto cycles_to_run = clock_.GetCycleCount();
void Apu::RunCycles(uint64_t cycles) {
uint64_t sync_to =
(uint64_t)cycles *
(memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster);
for (auto i = 0; i < cycles_to_run; ++i) {
// Update the Apu
UpdateChannelSettings();
// Update the SPC700
uint8_t opcode = spc700_.read(spc700_.PC);
spc700_.ExecuteInstructions(opcode);
spc700_.PC++;
while (cycles_ < sync_to) {
spc700_.RunOpcode();
}
ProcessSamples();
}
void Apu::Notify(uint32_t address, uint8_t data) {
if (address < 0x2140 || address > 0x2143) {
return;
void Apu::Cycle() {
if ((cycles_ & 0x1f) == 0) {
// 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
ports_[address - 0x2140] = data;
switch (address) {
case 0x2140:
if (data == BEGIN_SIGNAL) {
SignalReady();
// handle timers
for (int i = 0; i < 3; i++) {
if (timer_[i].cycles == 0) {
timer_[i].cycles = i == 2 ? 16 : 128;
if (timer_[i].enabled) {
timer_[i].divider++;
if (timer_[i].divider == timer_[i].target) {
timer_[i].divider = 0;
timer_[i].counter++;
timer_[i].counter &= 0xf;
}
}
break;
case 0x2141:
// 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;
}
timer_[i].cycles--;
}
cycles_++;
}
void Apu::ProcessSamples() {
// Fetch sample data from AudioRam
// Iterate over all voices
for (uint8_t voice_num = 0; voice_num < 8; voice_num++) {
// Fetch the sample data for the current voice from AudioRam
uint8_t sample = FetchSampleForVoice(voice_num);
// Process the sample through DSP
int16_t processed_sample = dsp_.ProcessSample(voice_num, sample);
// Add the processed sample to the audio buffer
audio_samples_.push_back(processed_sample);
uint8_t Apu::Read(uint16_t adr) {
switch (adr) {
case 0xf0:
case 0xf1:
case 0xfa:
case 0xfb:
case 0xfc: {
return 0;
}
case 0xf2: {
return dsp_adr_;
}
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;
}
}
}
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;
if (rom_readable_ && adr >= 0xffc0) {
return bootRom[adr - 0xffc0];
}
return 0; // TODO: Return the last sample instead of 0.
return ram[adr];
}
const std::vector<int16_t>& Apu::GetAudioSamples() const {
return audio_samples_;
void Apu::Write(uint16_t adr, uint8_t val) {
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() {
// TODO: Implement this method to update the channel settings.
uint8_t Apu::SpcRead(uint16_t adr) {
Cycle();
return Read(adr);
}
int16_t Apu::GenerateSample(int channel) {
// TODO: Implement this method to generate a sample for the specified channel.
void Apu::SpcWrite(uint16_t adr, uint8_t val) {
Cycle();
Write(adr, val);
}
void Apu::ApplyEnvelope(int channel) {
// TODO: Implement this method to apply an envelope to the specified channel.
}
void Apu::SpcIdle(bool waiting) { Cycle(); }
uint8_t Apu::ReadDspMemory(uint16_t address) {
return dsp_.ReadGlobalReg(address);
}
void Apu::WriteDspMemory(uint16_t address, uint8_t value) {
dsp_.WriteGlobalReg(address, value);
}
} // namespace audio
} // namespace audio
} // namespace emu
} // namespace app
} // namespace yaze

View File

@@ -7,7 +7,6 @@
#include "app/emu/audio/dsp.h"
#include "app/emu/audio/spc700.h"
#include "app/emu/cpu/clock.h"
#include "app/emu/memory/memory.h"
namespace yaze {
@@ -17,15 +16,13 @@ namespace audio {
using namespace memory;
/**
*
*
*/
const int kApuClockSpeed = 1024000; // 1.024 MHz
const int apuSampleRate = 32000; // 32 KHz
const int apuClocksPerSample = 64; // 64 clocks per sample
typedef struct Timer {
uint8_t cycles;
uint8_t divider;
uint8_t target;
uint8_t counter;
bool enabled;
} Timer;
/**
* @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
* this RAM.
*/
class Apu : public Observer {
class Apu {
public:
Apu(MemoryImpl &memory, AudioRam &aram, Clock &clock)
: aram_(aram), clock_(clock), memory_(memory) {}
Apu(MemoryImpl &memory) : memory_(memory) {}
void Init();
void Reset();
void Update();
void Notify(uint32_t address, uint8_t data) override;
void ProcessSamples();
uint8_t FetchSampleForVoice(uint8_t voice_num);
uint16_t CalculateAddressForVoice(uint8_t voice_num);
int16_t GetNextSample();
void RunCycles(uint64_t cycles);
uint8_t SpcRead(uint16_t address);
void SpcWrite(uint16_t address, uint8_t data);
void SpcIdle(bool waiting);
// Called upon a reset
void Initialize() {
spc700_.Reset();
dsp_.Reset();
SignalReady();
}
void Cycle();
// Set Port 0 = $AA and Port 1 = $BB
void SignalReady() {
memory_.WriteByte(0x2140, READY_SIGNAL_0);
memory_.WriteByte(0x2141, READY_SIGNAL_1);
}
uint8_t Read(uint16_t address);
void Write(uint16_t address, uint8_t data);
void WriteToPort(uint8_t portNum, uint8_t value) {
ports_[portNum] = value;
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;
auto dsp() -> Dsp & { return dsp_; }
auto spc700() -> Spc700 & { return spc700_; }
// 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
void UpdateChannelSettings();
private:
bool rom_readable_ = false;
// Generates a sample for an audio channel
int16_t GenerateSample(int channel);
uint8_t dsp_adr_ = 0;
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_;
std::array<Timer, 3> timer_;
DigitalSignalProcessor dsp_;
Spc700 spc700_{aram_};
std::vector<int16_t> audio_samples_;
std::function<void()> ready_callback_;
ApuCallbacks callbacks_ = {
[&](uint16_t adr, uint8_t val) { SpcWrite(adr, val); },
[&](uint16_t adr) { return SpcRead(adr); },
[&](bool waiting) { SpcIdle(waiting); },
};
Dsp dsp_{ram};
Spc700 spc700_{callbacks_};
};
} // namespace audio

View File

@@ -1,5 +1,7 @@
#include "app/emu/audio/dsp.h"
#include <cstring>
#include "app/emu/memory/memory.h"
namespace yaze {
@@ -7,280 +9,626 @@ namespace app {
namespace emu {
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 {
voice %= kNumVoices;
switch (reg % kNumVoiceRegs) {
case 0:
return voices_[voice].vol_left;
case 1:
return voices_[voice].vol_right;
case 2:
return voices_[voice].pitch_low;
case 3:
return voices_[voice].pitch_high;
case 4:
return voices_[voice].source_number;
case 5:
return voices_[voice].adsr1;
case 6:
return voices_[voice].adsr2;
case 7:
return voices_[voice].gain;
case 8:
return voices_[voice].envx;
case 9:
return voices_[voice].outx;
default:
return 0; // This shouldn't happen, but it's good to have a default
// case
static const int rateOffsets[32] = {0, 0, 1040, 536, 0, 1040, 536, 0, 1040,
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
536, 0, 1040, 536, 0};
static const int gaussValues[512] = {
0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
0x000, 0x000, 0x000, 0x000, 0x000, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002,
0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004,
0x005, 0x005, 0x005, 0x005, 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007,
0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00a, 0x00a, 0x00a, 0x00b, 0x00b,
0x00b, 0x00c, 0x00c, 0x00d, 0x00d, 0x00e, 0x00e, 0x00f, 0x00f, 0x00f, 0x010,
0x010, 0x011, 0x011, 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016,
0x017, 0x017, 0x018, 0x018, 0x019, 0x01a, 0x01b, 0x01b, 0x01c, 0x01d, 0x01d,
0x01e, 0x01f, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026,
0x027, 0x028, 0x029, 0x02a, 0x02b, 0x02c, 0x02d, 0x02e, 0x02f, 0x030, 0x031,
0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x03a, 0x03b, 0x03c, 0x03d,
0x03e, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04a, 0x04c,
0x04d, 0x04e, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05a, 0x05c,
0x05e, 0x05f, 0x061, 0x063, 0x064, 0x066, 0x068, 0x06a, 0x06b, 0x06d, 0x06f,
0x071, 0x073, 0x075, 0x076, 0x078, 0x07a, 0x07c, 0x07e, 0x080, 0x082, 0x084,
0x086, 0x089, 0x08b, 0x08d, 0x08f, 0x091, 0x093, 0x096, 0x098, 0x09a, 0x09c,
0x09f, 0x0a1, 0x0a3, 0x0a6, 0x0a8, 0x0ab, 0x0ad, 0x0af, 0x0b2, 0x0b4, 0x0b7,
0x0ba, 0x0bc, 0x0bf, 0x0c1, 0x0c4, 0x0c7, 0x0c9, 0x0cc, 0x0cf, 0x0d2, 0x0d4,
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,
uint8_t value) {
voice %= kNumVoices;
switch (reg % kNumVoiceRegs) {
case 0:
voices_[voice].vol_left = static_cast<int8_t>(value);
break;
case 1:
voices_[voice].vol_right = static_cast<int8_t>(value);
break;
case 2:
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
void Dsp::NewFrame() {
lastFrameBoundary = sampleOffset;
}
void Dsp::Cycle() {
sampleOutL = 0;
sampleOutR = 0;
echoOutL = 0;
echoOutR = 0;
for (int i = 0; i < 8; i++) {
CycleChannel(i);
}
}
// Set the callbacks
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) {
sample_fetcher_ = fetcher;
}
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) {
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;
HandleEcho(); // also applies master volume
counter = counter == 0 ? 30720 : counter - 1;
HandleNoise();
evenCycle = !evenCycle;
// handle mute flag
if (mute) {
sampleOutL = 0;
sampleOutR = 0;
}
// Clamp the mixed sample to 16-bit range
if (mixed_sample > 32767) {
mixed_sample = 32767;
} else if (mixed_sample < -32768) {
mixed_sample = -32768;
}
// Use the callback to push the mixed sample
sample_pusher_(mixed_sample);
// put final sample in the samplebuffer
sampleBuffer[(sampleOffset & 0x3ff) * 2] = sampleOutL;
sampleBuffer[(sampleOffset++ & 0x3ff) * 2 + 1] = sampleOutR;
}
void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) {
uint8_t adsr1 = ReadVoiceReg(voice, 0x05);
uint8_t adsr2 = ReadVoiceReg(voice, 0x06);
uint8_t gain = ReadVoiceReg(voice, 0x07);
static int clamp16(int val) {
return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val);
}
uint8_t enableADSR = (adsr1 & 0x80) >> 7;
static int clip16(int val) { return (int16_t)(val & 0xffff); }
if (enableADSR) {
// Handle ADSR envelope
Voice& voice_obj = voices_[voice];
switch (voice_obj.state) {
case VoiceState::ATTACK:
// Update amplitude based on attack rate
voice_obj.current_amplitude += AttackRate(adsr1);
if (voice_obj.current_amplitude >= ENVELOPE_MAX) {
voice_obj.current_amplitude = ENVELOPE_MAX;
voice_obj.state = VoiceState::DECAY;
}
break;
case VoiceState::DECAY:
// Update amplitude based on decay rate
voice_obj.current_amplitude -= DecayRate(adsr2);
if (voice_obj.current_amplitude <= voice_obj.decay_level) {
voice_obj.current_amplitude = voice_obj.decay_level;
voice_obj.state = VoiceState::SUSTAIN;
}
break;
case VoiceState::SUSTAIN:
// Keep amplitude at the calculated decay level
voice_obj.current_amplitude = voice_obj.decay_level;
break;
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;
bool Dsp::CheckCounter(int rate) {
if (rate == 0) return false;
return ((counter + rateOffsets[rate]) % rateValues[rate]) == 0;
}
void Dsp::HandleEcho() {
// increment fir buffer index
firBufferIndex++;
firBufferIndex &= 0x7;
// get value out of ram
uint16_t adr = echoBufferAdr + echoBufferIndex;
int16_t ramSample = aram_[adr] | (aram_[(adr + 1) & 0xffff] << 8);
firBufferL[firBufferIndex] = ramSample >> 1;
ramSample = aram_[(adr + 2) & 0xffff] | (aram_[(adr + 3) & 0xffff] << 8);
firBufferR[firBufferIndex] = ramSample >> 1;
// calculate FIR-sum
int sumL = 0, sumR = 0;
for (int i = 0; i < 8; i++) {
sumL += (firBufferL[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
sumR += (firBufferR[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
if (i == 6) {
// clip to 16-bit before last addition
sumL = clip16(sumL);
sumR = clip16(sumR);
}
}
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 {
// Handle Gain envelope
// Extract mode from the gain byte
uint8_t mode = (gain & 0xE0) >> 5;
uint8_t rate = gain & 0x1F;
Voice& voice_obj = voices_[voice];
switch (mode) {
case 0: // Direct Designation
case 1:
case 2:
case 3:
voice_obj.current_amplitude =
rate << 3; // Multiplying by 8 to scale to 0-255
break;
case 6: // Increase Mode (Linear)
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;
sample = GetSample(ch);
}
sample = ((sample * channel[ch].gain) >> 11) & ~1;
// handle reset and release
if (reset || (channel[ch].brrHeader & 0x03) == 1) {
channel[ch].adsrState = 3; // go to release
channel[ch].gain = 0;
}
// handle keyon/keyoff
if (evenCycle) {
if (channel[ch].keyOff) {
channel[ch].adsrState = 3; // go to release
}
if (channel[ch].keyOn) {
channel[ch].startDelay = 5;
channel[ch].adsrState = 0; // go to attack
channel[ch].keyOn = false;
}
}
}
void DigitalSignalProcessor::update_voice_state(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
Voice& voice = voices_[voice_num];
switch (voice.state) {
case VoiceState::OFF:
// Reset current amplitude
voice.current_amplitude = 0;
break;
case VoiceState::ATTACK:
// 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);
// handle envelope
if (channel[ch].startDelay == 0) {
HandleGain(ch);
}
// decode new brr samples if needed and update offsets
if (channel[ch].pitchCounter >= 0x4000) {
DecodeBrr(ch);
if (channel[ch].blockOffset >= 7) {
if (channel[ch].brrHeader & 0x1) {
channel[ch].decodeOffset = sampleAdr;
ram[0x7c] |= 1 << ch; // set ENDx
} else {
channel[ch].decodeOffset += 9;
}
break;
case VoiceState::DECAY:
// 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) {
voice.current_amplitude = voice.decay_level;
voice.state = VoiceState::SUSTAIN;
}
break;
case VoiceState::SUSTAIN:
// Keep the current amplitude at the decay level
break;
case VoiceState::RELEASE:
// Decrease the current amplitude at a rate defined by the RELEASE
// setting
voice.current_amplitude -= ReleaseRate(voice.adsr2);
if (voice.current_amplitude == 0) {
voice.state = VoiceState::OFF;
}
break;
channel[ch].blockOffset = 1;
} else {
channel[ch].blockOffset += 2;
}
}
// update pitch counter
channel[ch].pitchCounter &= 0x3fff;
channel[ch].pitchCounter += pitch;
if (channel[ch].pitchCounter > 0x7fff) channel[ch].pitchCounter = 0x7fff;
// set outputs
ram[(ch << 4) | 8] = channel[ch].gain >> 4;
ram[(ch << 4) | 9] = sample >> 8;
channel[ch].sampleOut = sample;
sampleOutL = clamp16(sampleOutL + ((sample * channel[ch].volumeL) >> 7));
sampleOutR = clamp16(sampleOutR + ((sample * channel[ch].volumeR) >> 7));
if (channel[ch].echoEnable) {
echoOutL = clamp16(echoOutL + ((sample * channel[ch].volumeL) >> 7));
echoOutR = clamp16(echoOutR + ((sample * channel[ch].volumeR) >> 7));
}
}
void DigitalSignalProcessor::process_envelope(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
void Dsp::HandleGain(int ch) {
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.)
update_voice_state(voice_num);
void Dsp::DecodeBrr(int ch) {
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
voice.envx = calculate_envelope_value(voice.current_amplitude);
void Dsp::HandleNoise() {
if (CheckCounter(noiseRate)) {
int bit = (noiseSample & 1) ^ ((noiseSample >> 1) & 1);
noiseSample = ((noiseSample >> 1) & 0x3fff) | (bit << 14);
}
}
// Apply the envelope value to the audio output
apply_envelope_to_output(voice_num);
uint8_t Dsp::Read(uint8_t adr) { return ram[adr]; }
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

View File

@@ -5,6 +5,7 @@
#include <functional>
#include <vector>
#include "app/emu/audio/spc700.h"
#include "app/emu/memory/memory.h"
namespace yaze {
@@ -12,8 +13,40 @@ namespace app {
namespace emu {
namespace audio {
using SampleFetcher = std::function<uint8_t(uint16_t)>;
using SamplePusher = std::function<void(int16_t)>;
typedef struct DspChannel {
// 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.
@@ -34,277 +67,92 @@ using SamplePusher = std::function<void(int16_t)>;
* There are 8 voices, numbered 0 to 7.
* 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 (R) | $X1 | SVVV VVVV | Right channel volume, signed. |
* | P (L) | $X2 | LLLL LLLL | Low 8 bits of sample pitch. |
* | P (H) | $X3 | --HH HHHH | High 6 bits of sample pitch. |
* | SCRN | $X4 | SSSS SSSS | Selects a sample source entry from the directory. |
* | ADSR (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A). |
* | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). |
* | GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). |
* | ENVX | $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 |
* | | | | multiplied by ENVX, before applying VOL. |
* | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | | VOL (R) |
* $X1 | SVVV VVVV | Right channel volume, signed. | | P (L) | $X2 |
* LLLL LLLL | Low 8 bits of sample pitch. | | P (H)
* | $X3 | --HH HHHH | High 6 bits of sample pitch. | | SCRN | $X4 |
* SSSS SSSS | Selects a sample source entry from the directory. | | ADSR
* (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A).
* | | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | |
* GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | | ENVX |
* $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 | | | | | multiplied by ENVX, before
* applying VOL. |
*/
class DigitalSignalProcessor {
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_;
class Dsp {
public:
DigitalSignalProcessor() = default;
Dsp(std::vector<uint8_t>& aram) : aram_(aram) {}
void NewFrame();
void Reset();
void SetSampleFetcher(std::function<uint8_t(uint16_t)> fetcher);
void SetSamplePusher(std::function<void(int16_t)> pusher);
void Cycle();
// Read a byte from a voice register
uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const;
void HandleEcho();
void CycleChannel(int ch);
// Write a byte to a voice register
void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value);
void HandleNoise();
void HandleGain(int ch);
// Read a byte from a global register
uint8_t ReadGlobalReg(uint8_t reg) const {
return globalRegs[reg % kNumGlobalRegs];
}
bool CheckCounter(int rate);
// Write a byte to a global register
void WriteGlobalReg(uint8_t reg, uint8_t value) {
globalRegs[reg % kNumGlobalRegs] = value;
}
void DecodeBrr(int ch);
int16_t DecodeSample(uint8_t voice_num);
int16_t ProcessSample(uint8_t voice_num, int16_t sample);
void MixSamples();
uint8_t Read(uint8_t adr);
void Write(uint8_t adr, uint8_t val);
// Trigger a voice to start playing
void trigger_voice(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
int16_t GetSample(int ch);
Voice& voice = voices_[voice_num];
voice.state = VoiceState::ATTACK;
// Initialize other state management variables if needed
}
void GetSamples(int16_t* sample_data, int samples_per_frame, bool pal_timing);
// Release a voice (e.g., note release in ADSR)
void release_voice(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
private:
int16_t sample_buffer_[0x400 * 2]; // (1024 samples, *2 for stereo)
int16_t sample_offset_; // current offset in samplebuffer
Voice& voice = voices_[voice_num];
if (voice.state != VoiceState::OFF) {
voice.state = VoiceState::RELEASE;
}
// Update other state management variables if needed
}
std::vector<uint8_t>& aram_;
// Calculate envelope for a given voice
void UpdateEnvelope(uint8_t voice);
// Voice-related functions (implementations)
void set_voice_volume(int voice_num, int8_t left, int8_t right) {
voices_[voice_num].vol_left = left;
voices_[voice_num].vol_right = right;
}
void set_voice_pitch(int voice_num, uint16_t pitch) {
voices_[voice_num].pitch_low = pitch & 0xFF;
voices_[voice_num].pitch_high = (pitch >> 8) & 0xFF;
}
void set_voice_source_number(int voice_num, uint8_t srcn) {
voices_[voice_num].source_number = srcn;
}
void set_voice_adsr(int voice_num, uint8_t adsr1, uint8_t adsr2) {
voices_[voice_num].adsr1 = adsr1;
voices_[voice_num].adsr2 = adsr2;
}
void set_voice_gain(int voice_num, uint8_t gain) {
voices_[voice_num].gain = gain;
}
uint8_t read_voice_envx(int voice_num) { return voices_[voice_num].envx; }
int8_t read_voice_outx(int voice_num) { return voices_[voice_num].outx; }
// Global DSP functions
void set_master_volume(int8_t left, int8_t right) {
mvol_left = left;
mvol_right = right;
}
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);
// mirror ram
uint8_t ram[0x80];
// 8 channels
DspChannel channel[8];
// overarching
uint16_t counter;
uint16_t dirPage;
bool evenCycle;
bool mute;
bool reset;
int8_t masterVolumeL;
int8_t masterVolumeR;
// accumulation
int16_t sampleOutL;
int16_t sampleOutR;
int16_t echoOutL;
int16_t echoOutR;
// noise
int16_t noiseSample;
uint8_t noiseRate;
// echo
bool echoWrites;
int8_t echoVolumeL;
int8_t echoVolumeR;
int8_t feedbackVolume;
uint16_t echoBufferAdr;
uint16_t echoDelay;
uint16_t echoLength;
uint16_t echoBufferIndex;
uint8_t firBufferIndex;
int8_t firValues[8];
int16_t firBufferL[8];
int16_t firBufferR[8];
// sample ring buffer (1024 samples, *2 for stereo)
int16_t sampleBuffer[0x400 * 2];
uint16_t sampleOffset; // current offset in samplebuffer
uint32_t lastFrameBoundary;
};
} // namespace audio

View File

@@ -5,29 +5,84 @@ namespace app {
namespace emu {
namespace audio {
// Immediate
uint8_t Spc700::imm() {
PC++;
return read(PC);
// adressing modes
uint16_t Spc700::ind() {
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
uint8_t Spc700::dp() {
PC++;
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;
return ReadOpcode() | (PSW.P << 8);
}
// Direct page indexed by X
@@ -47,23 +102,24 @@ uint8_t Spc700::dp_plus_y() {
// Indexed indirect (add index before 16-bit lookup).
uint16_t Spc700::dp_plus_x_indirect() {
PC++;
uint16_t addr = read_16(PC + X);
uint16_t addr = read_word(PC + X);
return addr;
}
// Indirect indexed (add index after 16-bit lookup).
uint16_t Spc700::dp_indirect_plus_y() {
PC++;
uint16_t offset = read_16(PC);
uint16_t offset = read_word(PC);
return offset + Y;
}
uint16_t Spc700::abs() {
PC++;
uint16_t addr = read(PC) | (read(PC) << 8);
return addr;
uint16_t Spc700::dp_dp(uint8_t* src) {
*src = read(ReadOpcode() | (PSW.P << 8));
return ReadOpcode() | (PSW.P << 8);
}
uint16_t Spc700::abs() { return ReadOpcodeWord(); }
int8_t Spc700::rel() {
PC++;
return static_cast<int8_t>(read(PC));

View File

@@ -5,10 +5,45 @@ namespace app {
namespace emu {
namespace audio {
void Spc700::MOV(uint8_t& dest, uint8_t operand) {
dest = operand;
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
// opcode functions
void Spc700::MOVX(uint16_t adr) {
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) {
@@ -17,75 +52,162 @@ void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
PSW.N = (operand & 0x80);
}
void Spc700::ADC(uint8_t& dest, uint8_t operand) {
uint16_t result = dest + operand + PSW.C;
PSW.V = ((A ^ result) & (operand ^ result) & 0x80);
void Spc700::ADC(uint16_t adr) {
uint8_t value = read(adr);
uint16_t result = A + value + PSW.C;
PSW.V = ((A ^ result) & (adr ^ result) & 0x80);
PSW.C = (result > 0xFF);
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80);
PSW.H = ((A ^ operand ^ result) & 0x10);
dest = result & 0xFF;
PSW.H = ((A ^ adr ^ result) & 0x10);
A = result & 0xFF;
PSW.Z = ((A & 0xFF) == 0);
PSW.N = (A & 0x80);
}
void Spc700::SBC(uint8_t& dest, uint8_t operand) {
uint16_t result = dest - operand - (1 - PSW.C);
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x80);
PSW.C = (result < 0x100);
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80);
PSW.H = ((dest ^ operand ^ result) & 0x10);
dest = result & 0xFF;
}
void Spc700::CMP(uint8_t& dest, uint8_t operand) {
uint16_t result = dest - operand;
PSW.C = (result < 0x100);
void Spc700::ADCM(uint16_t& dest, uint8_t operand) {
uint8_t applyOn = read(dest);
int result = applyOn + operand + PSW.C;
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
(operand & 0x80) != (result & 0x80);
PSW.H = ((applyOn & 0xf) + (operand & 0xf) + PSW.C) > 0xf;
PSW.C = result > 0xff;
write(dest, result);
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80);
}
void Spc700::AND(uint8_t& dest, uint8_t operand) {
dest &= operand;
PSW.Z = (dest == 0);
PSW.N = (dest & 0x80);
void Spc700::SBC(uint16_t adr) {
uint8_t value = read(adr) ^ 0xff;
int result = A + value + PSW.C;
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) {
dest |= operand;
PSW.Z = (dest == 0);
PSW.N = (dest & 0x80);
void Spc700::SBCM(uint16_t& dest, uint8_t operand) {
operand ^= 0xff;
uint8_t applyOn = read(dest);
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) {
dest ^= operand;
PSW.Z = (dest == 0);
PSW.N = (dest & 0x80);
void Spc700::CMPX(uint16_t adr) {
uint8_t value = read(adr) ^ 0xff;
int result = X + value + 1;
PSW.C = result > 0xff;
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
}
void Spc700::ASL(uint8_t operand) {
PSW.C = (operand & 0x80);
operand <<= 1;
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
// A = value;
void Spc700::CMPY(uint16_t adr) {
uint8_t value = read(adr) ^ 0xff;
int result = Y + value + 1;
PSW.C = result > 0xff;
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
}
void Spc700::LSR(uint8_t& operand) {
PSW.C = (operand & 0x01);
operand >>= 1;
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
void Spc700::CMPM(uint16_t dst, uint8_t value) {
value ^= 0xff;
int result = read(dst) + value + 1;
PSW.C = result > 0xff;
callbacks_.idle(false);
PSW.Z = (result == 0);
PSW.N = (result & 0x80);
}
void Spc700::ROL(uint8_t operand, bool isImmediate) {
uint8_t value = isImmediate ? imm() : operand;
uint8_t carry = PSW.C;
PSW.C = (value & 0x80);
value <<= 1;
value |= carry;
PSW.Z = (value == 0);
PSW.N = (value & 0x80);
// operand = value;
void Spc700::CMP(uint16_t adr) {
uint8_t value = read(adr) ^ 0xff;
int result = A + value + 1;
PSW.C = result > 0xff;
PSW.Z = ((result & 0xFF) == 0);
PSW.N = (result & 0x80);
}
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) {
@@ -96,14 +218,16 @@ void Spc700::XCN(uint8_t operand, bool isImmediate) {
// operand = value;
}
void Spc700::INC(uint8_t& operand) {
operand++;
PSW.Z = (operand == 0);
PSW.N = (operand & 0x80);
void Spc700::INC(uint16_t adr) {
uint8_t val = read(adr) + 1;
write(adr, val);
PSW.Z = (val == 0);
PSW.N = (val & 0x80);
}
void Spc700::DEC(uint8_t& operand) {
operand--;
void Spc700::DEC(uint16_t operand) {
uint8_t val = read(operand) - 1;
write(operand, val);
PSW.Z = (operand == 0);
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
#include <cstdint>
#include <functional>
#include <iostream>
#include <unordered_map>
#include <vector>
@@ -21,6 +22,7 @@ class AudioRam {
virtual uint8_t read(uint16_t address) const = 0;
virtual uint8_t& mutable_read(uint16_t address) = 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 {
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
* @brief The Spc700 class represents the SPC700 processor.
@@ -60,9 +71,21 @@ class AudioRamImpl : public AudioRam {
*/
class Spc700 {
private:
AudioRam& aram_;
ApuCallbacks callbacks_;
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]{
0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, 0xFC, 0x8F, 0xAA,
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};
public:
explicit Spc700(AudioRam& aram) : aram_(aram) {}
explicit Spc700(ApuCallbacks& callbacks) : callbacks_(callbacks) {}
// Registers
uint8_t A = 0x00; // 8-bit accumulator
@@ -83,14 +106,14 @@ class Spc700 {
uint8_t SP = 0x00; // stack pointer
struct Flags {
uint8_t N : 1; // Negative flag
uint8_t V : 1; // Overflow flag
uint8_t P : 1; // Direct page flag
uint8_t B : 1; // Break flag
uint8_t H : 1; // Half-carry flag
uint8_t I : 1; // Interrupt enable
uint8_t Z : 1; // Zero flag
uint8_t C : 1; // Carry flag
bool N : 1; // Negative flag
bool V : 1; // Overflow flag
bool P : 1; // Direct page flag
bool B : 1; // Break flag
bool H : 1; // Half-carry flag
bool I : 1; // Interrupt enable
bool Z : 1; // Zero flag
bool C : 1; // Carry flag
};
Flags PSW; // Processor status word
@@ -112,80 +135,97 @@ class Spc700 {
return flags;
}
void Reset();
void Reset(bool hard = false);
void BootIplRom();
void RunOpcode();
void ExecuteInstructions(uint8_t opcode);
void LogInstruction(uint16_t initial_pc, uint8_t opcode);
// Read a byte from the memory-mapped registers
uint8_t read(uint16_t address) {
if (address < 0xFFC0) {
return aram_.read(address);
} else {
// Check if register is set to unmap the IPL ROM
if (read(0xF1) & 0x80) {
return aram_.read(address);
}
return ipl_rom_[address - 0xFFC0];
}
uint8_t read(uint16_t address) { return callbacks_.read(address); }
uint16_t read_word(uint16_t address) {
uint8_t adrl = address;
uint8_t adrh = address + 1;
uint8_t value = callbacks_.read(adrl);
return value | (callbacks_.read(adrh) << 8);
}
uint8_t& mutable_read(uint16_t address) {
if (address < 0xFFC0) {
return aram_.mutable_read(address);
} else {
// NOTE: Mutable access to IPL ROM is not allowed
return aram_.mutable_read(address);
}
uint8_t ReadOpcode() {
uint8_t opcode = callbacks_.read(PC++);
return opcode;
}
uint16_t& mutable_read_16(uint16_t address) {
if (address < 0xFFC0) {
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address));
} else {
// NOTE: Mutable access to IPL ROM is not allowed
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address));
}
uint16_t ReadOpcodeWord() {
uint8_t low = ReadOpcode();
uint8_t high = ReadOpcode();
return low | (high << 8);
}
uint16_t read_16(uint16_t address) {
if (address < 0xFFC0) {
return (aram_.read(address) | (aram_.read(address + 1) << 8));
} else {
// Check if register is set to unmap the IPL ROM
if (read(0xF1) & 0x80) {
return aram_.read(address);
}
return ipl_rom_[address - 0xFFC0];
void DoBranch(uint8_t value, bool check) {
if (check) {
// taken branch: 2 extra cycles
callbacks_.idle(false);
callbacks_.idle(false);
PC += (int8_t)value;
}
}
// Write a byte to the memory-mapped registers
void write(uint16_t address, uint8_t value) {
if (address < 0xFFC0) {
aram_.write(address, value);
} else {
// Check if register is set to unmap the IPL ROM
if (read(0xF1) & 0x80) {
aram_.write(address, value);
}
}
callbacks_.write(address, value);
}
void push_byte(uint8_t value) {
callbacks_.write(0x100 | SP, 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
// Immediate
uint8_t imm();
uint16_t imm();
// Direct page
uint8_t dp();
uint8_t& mutable_dp();
uint16_t dpx();
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
uint8_t dp_plus_x();
@@ -197,11 +237,6 @@ class Spc700 {
// Indirect indexed (add index after 16-bit lookup).
uint16_t dp_indirect_plus_y();
uint16_t abs();
int8_t rel();
uint8_t i();
uint8_t i_postinc();
@@ -213,20 +248,43 @@ class Spc700 {
// ==========================================================================
// Instructions
void MOV(uint8_t& dest, uint8_t operand);
void MOV(uint16_t adr);
void MOV_ADDR(uint16_t address, uint8_t operand);
void ADC(uint8_t& dest, uint8_t operand);
void SBC(uint8_t& dest, uint8_t operand);
void CMP(uint8_t& dest, uint8_t operand);
void AND(uint8_t& dest, uint8_t operand);
void OR(uint8_t& dest, uint8_t operand);
void EOR(uint8_t& dest, uint8_t operand);
void ASL(uint8_t operand);
void LSR(uint8_t& operand);
void ROL(uint8_t operand, bool isImmediate = false);
void MOVY(uint16_t adr);
void MOVX(uint16_t adr);
void MOVS(uint16_t adr);
void MOVSX(uint16_t adr);
void MOVSY(uint16_t adr);
void ADC(uint16_t adr);
void ADCM(uint16_t& dest, uint8_t operand);
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 INC(uint8_t& operand);
void DEC(uint8_t& operand);
void INC(uint16_t adr);
void DEC(uint16_t operand);
void MOVW(uint16_t& dest, uint16_t operand);
void INCW(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
};
const int kCpuClockSpeed = 21477272; // 21.477272 MHz
class Cpu : public memory::Memory,
public Loggable,
public core::ExperimentFlags {
class Cpu : public Loggable, public core::ExperimentFlags {
public:
explicit Cpu(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {}
enum class UpdateMode { Run, Step, Pause };
explicit Cpu(memory::Memory& mem, Clock& vclock,
memory::CpuCallbacks& callbacks)
: memory(mem), clock(vclock), callbacks_(callbacks) {}
void Reset(bool hard = false);
void Init(bool verbose = false) { clock.SetFrequency(kCpuClockSpeed); }
void Update(UpdateMode mode = UpdateMode::Run, int stepCount = 1);
void RunOpcode();
void ExecuteInstruction(uint8_t opcode);
void LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand,
bool immediate, bool accumulator_mode);
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); }
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 SetIrq(bool state) { irq_wanted_ = state; }
void Nmi() { nmi_wanted_ = true; }
uint8_t GetInstructionLength(uint8_t opcode);
std::vector<uint32_t> breakpoints_;
std::vector<InstructionEntry> instruction_log_;
@@ -90,7 +70,7 @@ class Cpu : public memory::Memory,
// 0xFFF8,F9 - ABORT 0xFFE8,E9 - ABORT
// 0xFFE6,E7 - BRK
// 0xFFF4,F5 - COP 0xFFE4,E5 - COP
void HandleInterrupts();
void DoInterrupt();
// ======================================================
// Registers
@@ -117,18 +97,34 @@ class Cpu : public memory::Memory,
// E 6502 emulation mode
// 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
bool m() { return GetAccumulatorSize() ? 1 : 0; }
bool xf() { return GetIndexSize() ? 1 : 0; }
int GetAccumulatorSize() const { return status & 0x20; }
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 SetIndexSize(bool set) { SetFlag(0x10, set); }
@@ -152,7 +148,95 @@ class Cpu : public memory::Memory,
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
// Effective Address:
@@ -162,7 +246,7 @@ class Cpu : public memory::Memory,
// Low: First operand byte
//
// LDA addr
uint32_t Absolute(AccessType access_type = AccessType::Data);
uint32_t Absolute(uint32_t* low);
// Effective Address:
// The Data Bank Register is concatened with the 16-bit operand
@@ -171,6 +255,7 @@ class Cpu : public memory::Memory,
//
// LDA addr, X
uint32_t AbsoluteIndexedX();
uint32_t AdrAbx(uint32_t* low, bool write);
// Effective Address:
// The Data Bank Register is concatened with the 16-bit operand
@@ -179,6 +264,17 @@ class Cpu : public memory::Memory,
//
// LDA addr, Y
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:
// Bank: Program Bank Register (PBR)
@@ -211,12 +307,14 @@ class Cpu : public memory::Memory,
//
// LDA long
uint32_t AbsoluteLong();
uint32_t AdrAbl(uint32_t* low);
// Effective Address:
// The 24-bit operand is added to X based on the emulation mode
//
// LDA long, X
uint32_t AbsoluteLongIndexedX();
uint32_t AdrAlx(uint32_t* low);
// Source Effective Address:
// Bank: Second operand byte
@@ -238,6 +336,7 @@ class Cpu : public memory::Memory,
//
// LDA dp
uint16_t DirectPage();
uint32_t AdrDp(uint32_t* low);
// Effective Address:
// Bank: Zero
@@ -246,6 +345,7 @@ class Cpu : public memory::Memory,
//
// LDA dp, X
uint16_t DirectPageIndexedX();
uint32_t AdrDpx(uint32_t* low);
// Effective Address:
// Bank: Zero
@@ -253,6 +353,7 @@ class Cpu : public memory::Memory,
// based on the emulation mode
// LDA dp, Y
uint16_t DirectPageIndexedY();
uint32_t AdrDpy(uint32_t* low);
// Effective Address:
// Bank: Data bank register
@@ -310,6 +411,7 @@ class Cpu : public memory::Memory,
uint16_t Immediate(bool index_size = false);
uint16_t StackRelative();
uint32_t AdrSr(uint32_t* low);
// Effective Address:
// The Data Bank Register is concatenated to the Indirect Address;
@@ -321,88 +423,6 @@ class Cpu : public memory::Memory,
// LDA (sr, S), Y
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
@@ -501,13 +521,13 @@ class Cpu : public memory::Memory,
void JMP(uint16_t address);
// JML: Jump long
void JML(uint32_t address);
void JML(uint16_t address);
// JSR: Jump to subroutine
void JSR(uint16_t address);
// JSL: Jump to subroutine long
void JSL(uint32_t address);
void JSL(uint16_t address);
// LDA: Load accumulator
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);
// MVN: Block move next
void MVN(uint16_t source, uint16_t dest, uint16_t length);
void MVN();
// MVP: Block move previous
void MVP(uint16_t source, uint16_t dest, uint16_t length);
void MVP();
// NOP: No operation
void NOP();
// 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
void PEA();
@@ -684,6 +704,57 @@ class Cpu : public memory::Memory,
// XCE: Exchange carry and emulation bits
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:
void compare(uint16_t register_value, uint16_t memory_value) {
uint16_t result;
@@ -711,14 +782,20 @@ class Cpu : public memory::Memory,
}
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_;
uint16_t next_pc_;
bool log_instructions_ = false;
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;
};

View File

@@ -4,120 +4,187 @@ namespace yaze {
namespace app {
namespace emu {
uint32_t Cpu::Absolute(Cpu::AccessType access_type) {
auto operand = FetchWord();
uint32_t bank =
(access_type == Cpu::AccessType::Data) ? (DB << 16) : (PB << 16);
return bank | (operand & 0xFFFF);
}
uint32_t Cpu::AbsoluteIndexedX() {
uint16_t address = memory.ReadWord((PB << 16) | (PC + 1));
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));
void Cpu::AdrImp() {
// only for 2-cycle implied opcodes
CheckInt();
if (int_wanted_) {
// if interrupt detected in 2-cycle implied/accumulator opcode,
// idle cycle turns into read from pc
ReadByte((PB << 16) | PC);
} else {
callbacks_.idle(false);
}
}
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() {
uint8_t dp = FetchByte();
uint8_t dp = ReadOpcode();
return D + dp;
}
uint16_t Cpu::DirectPageIndexedX() {
uint8_t operand = FetchByte();
uint8_t operand = ReadOpcode();
uint16_t x_by_mode = GetAccumulatorSize() ? X : X & 0xFF;
return D + operand + x_by_mode;
}
uint16_t Cpu::DirectPageIndexedY() {
uint8_t operand = FetchByte();
uint8_t operand = ReadOpcode();
return (operand + Y) & 0xFF;
}
uint16_t Cpu::DirectPageIndexedIndirectX() {
uint8_t operand = FetchByte();
uint16_t indirect_address = D + operand + X;
uint16_t effective_address = memory.ReadWord(indirect_address & 0xFFFF);
return effective_address;
}
uint16_t Cpu::DirectPageIndirect() {
uint8_t dp = FetchByte();
uint16_t effective_address = D + dp;
return memory.ReadWord(effective_address);
uint32_t Cpu::AdrIdx(uint32_t* low) {
uint8_t adr = ReadOpcode();
if (D & 0xff) callbacks_.idle(false);
callbacks_.idle(false);
uint16_t pointer = ReadWord((D + adr + X) & 0xffff, false);
*low = (DB << 16) + pointer;
return ((DB << 16) + pointer + 1) & 0xffffff;
}
uint32_t Cpu::DirectPageIndirectLong() {
uint8_t dp = FetchByte();
uint8_t dp = ReadOpcode();
uint16_t effective_address = D + dp;
return memory.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;
return ReadWordLong((0x00 << 0x10) | effective_address);
}
uint32_t Cpu::DirectPageIndirectLongIndexedY() {
uint8_t operand = FetchByte();
uint8_t operand = ReadOpcode();
uint16_t indirect_address = D + operand;
uint16_t y_by_mode = GetAccumulatorSize() ? Y : Y & 0xFF;
uint32_t effective_address =
memory.ReadWordLong(indirect_address) + y_by_mode;
uint32_t effective_address = ReadWordLong(indirect_address) + y_by_mode;
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() {
uint8_t sr = FetchByte();
uint8_t sr = ReadOpcode();
uint16_t effective_address = SP() + sr;
return effective_address;
}
uint32_t Cpu::StackRelativeIndirectIndexedY() {
uint8_t sr = FetchByte();
return (DB << 0x10) | (memory.ReadWord(SP() + sr) + Y);
}
} // namespace emu
} // namespace app
} // 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"},
{0xF5, "SBC"}, {0xF6, "INC"}, {0xF7, "SBC"}, {0xF8, "SED"}, {0xF9, "SBC"},
{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 <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "app/emu/cpu/internal/opcodes.h"
namespace yaze {
namespace app {
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 {
public:
std::vector<uint8_t> Parse(const std::string& instruction) {
std::smatch match;
if (!std::regex_match(instruction, match, instruction_regex_)) {
CreateInternalOpcodeMap();
auto tokens = Tokenize(instruction);
if (tokens.size() < 1) {
throw std::runtime_error("Invalid instruction format: " + instruction);
}
std::string mnemonic = match[1];
std::string addressing_mode = match[2];
std::string operand = match[3];
size_t index = 0;
std::vector<uint8_t> bytes;
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);
if (opcode_entry == mnemonic_to_opcode_.end()) {
throw std::runtime_error(
"Unknown mnemonic or addressing mode: " + mnemonic + addressing_mode);
// Now we check for either the immediate mode
// symbol # or the address symbol $ to determine
// the next step
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;
}
void CreateInternalOpcodeMap() {
for (const auto& opcode_entry : opcode_to_mnemonic) {
std::string name = opcode_entry.second;
uint8_t opcode = opcode_entry.first;
mnemonic_to_opcode_[name] = opcode;
// Example: ADC.b #$01
// Returns: ["ADC", ".b", "#", "$", "01"]
std::vector<std::string> Tokenize(const std::string& instruction) {
std::vector<std::string> tokens;
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:
void AppendOperandBytes(std::vector<uint8_t>& bytes,
const std::string& operand,
const std::string& addressing_mode) {
if (addressing_mode == ".b") {
bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16)));
} else if (addressing_mode == ".w") {
uint16_t word_operand =
static_cast<uint16_t>(std::stoi(operand, nullptr, 16));
bytes.push_back(static_cast<uint8_t>(word_operand & 0xFF));
bytes.push_back(static_cast<uint8_t>((word_operand >> 8) & 0xFF));
} else if (addressing_mode == ".l") {
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));
const AddressingMode& addressing_mode) {
// Handle different addressing modes
switch (addressing_mode) {
case AddressingMode::kImmediate: {
bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16)));
break;
}
case AddressingMode::kAbsolute: {
uint16_t word_operand =
static_cast<uint16_t>(std::stoi(operand, nullptr, 16));
bytes.push_back(static_cast<uint8_t>(word_operand & 0xFF));
bytes.push_back(static_cast<uint8_t>((word_operand >> 8) & 0xFF));
break;
}
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 {
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,
};
AddressingMode InferAddressingModeFromOperand(const std::string& operand) {
if (operand[0] == '$') {
return AddressingMode::kAbsolute;
} else if (operand[0] == '#') {
AddressingMode DetermineMode(const std::vector<std::string>& tokens) {
const std::string& addressingMode = tokens[1];
if (addressingMode == ".b") {
return AddressingMode::kImmediate;
} else if (addressingMode == ".w") {
return AddressingMode::kAbsolute;
} else if (addressingMode == ".l") {
return AddressingMode::kAbsoluteLong;
} else {
return AddressingMode::kImplied;
}
}
const std::regex instruction_regex_{R"((\w+)\s*(\.\w)?\s*(\$\w+|\#\w+|\w+))"};
std::unordered_map<std::string, uint8_t> mnemonic_to_opcode_;
bool TryParseByte(const std::string& str, uint8_t& value) {
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

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 <imgui/imgui.h>
#include <imgui_memory_editor.h>
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
#include <cstdint>
#include <vector>
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/emu/snes.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/zeml.h"
#include "app/rom.h"
namespace yaze {
@@ -17,11 +19,26 @@ namespace app {
namespace emu {
namespace {
bool ShouldDisplay(const InstructionEntry& entry, const char* filter,
bool showAll) {
// Implement logic to determine if the entry should be displayed based on the
// filter and showAll flag
return true;
bool ShouldDisplay(const InstructionEntry& entry, const char* filter) {
if (filter[0] == '\0') {
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
@@ -33,35 +50,105 @@ using ImGui::TableNextColumn;
using ImGui::Text;
void Emulator::Run() {
static bool loaded = false;
if (!snes_.running() && rom()->is_loaded()) {
snes_.SetupMemory(*rom());
snes_.Init(*rom());
ppu_texture_ =
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();
if (running_) {
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() {
MENU_BAR()
if (ImGui::BeginMenu("Options")) {
MENU_ITEM("Input") {}
MENU_ITEM("Audio") {}
MENU_ITEM("Video") {}
ImGui::EndMenu();
}
END_MENU_BAR()
std::string navbar_layout = R"(
BeginMenuBar {
BeginMenu title="Options" {
MenuItem title="Input" {}
MenuItem title="Audio" {}
MenuItem title="Video" {}
}
}
)";
static auto navbar_node = gui::zeml::Parse(navbar_layout);
gui::zeml::Render(navbar_node);
if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
loading_ = true;
running_ = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Start Emulation");
@@ -69,7 +156,7 @@ void Emulator::RenderNavBar() {
SameLine();
if (ImGui::Button(ICON_MD_PAUSE)) {
snes_.SetCpuMode(1);
running_ = false;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Pause Emulation");
@@ -78,7 +165,7 @@ void Emulator::RenderNavBar() {
if (ImGui::Button(ICON_MD_SKIP_NEXT)) {
// Step through Code logic
snes_.StepRun();
snes_.cpu().RunOpcode();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Step Through Code");
@@ -87,6 +174,7 @@ void Emulator::RenderNavBar() {
if (ImGui::Button(ICON_MD_REFRESH)) {
// Reset Emulator logic
snes_.Reset(true);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reset Emulator");
@@ -125,13 +213,22 @@ void Emulator::RenderNavBar() {
ImGui::SetTooltip("Settings");
}
static bool open_file = false;
SameLine();
if (ImGui::Button(ICON_MD_INFO)) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("About Debugger");
}
open_file = true;
// 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;
SameLine();
@@ -147,6 +244,18 @@ void Emulator::RenderNavBar() {
RenderMemoryViewer();
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() {
@@ -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() {
if (ImGui::Button("Set SPC PC")) {
snes_.apu().spc700().PC = 0xFFEF;
}
Separator();
Text("Breakpoints");
Separator();
static char breakpoint_input[10] = "";
@@ -245,64 +311,31 @@ void Emulator::RenderBreakpointList() {
for (auto breakpoint : breakpoints) {
if (ImGui::Selectable(absl::StrFormat("0x%04X", breakpoint).c_str())) {
// Jump to breakpoint
// snes_.Cpu().JumpToBreakpoint(breakpoint);
// snes_.cpu().JumpToBreakpoint(breakpoint);
}
}
ImGui::EndChild();
}
Separator();
gui::InputHexByte("PB", &manual_pb_, 1, 25.f);
gui::InputHexWord("PC", &manual_pc_, 25.f);
gui::InputHexByte("PB", &manual_pb_, 50.f);
gui::InputHexWord("PC", &manual_pc_, 75.f);
if (ImGui::Button("Set Current Address")) {
snes_.cpu().PC = manual_pc_;
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() {
static MemoryEditor ram_edit;
static MemoryEditor aram_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)) {
ImGui::TableSetupColumn("Bookmarks");
ImGui::TableSetupColumn("Memory");
ImGui::TableSetupColumn("RAM");
ImGui::TableSetupColumn("ARAM");
ImGui::TableSetupColumn("ROM");
ImGui::TableHeadersRow();
TableNextColumn();
@@ -343,41 +376,70 @@ void Emulator::RenderMemoryViewer() {
}
TableNextColumn();
mem_edit.DrawContents((void*)snes_.Memory()->data(),
snes_.Memory()->size());
if (ImGui::BeginChild("RAM", ImVec2(0, 0), true,
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();
}
}
void Emulator::RenderCpuInstructionLog(
const std::vector<InstructionEntry>& instructionLog) {
if (ImGui::CollapsingHeader("CPU Instruction Log")) {
const std::vector<InstructionEntry>& instruction_log) {
if (ImGui::CollapsingHeader("Instruction Log",
ImGuiTreeNodeFlags_DefaultOpen)) {
// Filtering options
static char filterBuf[256];
ImGui::InputText("Filter", filterBuf, IM_ARRAYSIZE(filterBuf));
SameLine();
if (ImGui::Button("Clear")) { /* Clear filter logic */
}
// Toggle for showing all opcodes
static bool showAllOpcodes = true;
ImGui::Checkbox("Show All Opcodes", &showAllOpcodes);
static char filter[256];
ImGui::InputText("Filter", filter, IM_ARRAYSIZE(filter));
// Instruction list
ImGui::BeginChild("InstructionList", ImVec2(0, 0),
ImGuiChildFlags_None);
for (const auto& entry : instructionLog) {
if (ShouldDisplay(entry, filterBuf, showAllOpcodes)) {
ImGui::BeginChild("InstructionList", ImVec2(0, 0), ImGuiChildFlags_None);
for (const auto& entry : instruction_log) {
if (ShouldDisplay(entry, filter)) {
if (ImGui::Selectable(
absl::StrFormat("%06X: %s %s", entry.address,
opcode_to_mnemonic.at(entry.opcode),
entry.operands)
.c_str())) {
absl::StrFormat("%06X:", entry.address).c_str())) {
// 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();
}
}

View File

@@ -1,12 +1,13 @@
#ifndef YAZE_APP_CORE_EMULATOR_H
#define YAZE_APP_CORE_EMULATOR_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <cstdint>
#include <vector>
#include "app/emu/snes.h"
#include "app/gui/zeml.h"
#include "app/rom.h"
namespace yaze {
@@ -24,16 +25,90 @@ namespace emu {
*/
class Emulator : public SharedRom {
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();
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:
void RenderNavBar();
void HandleEvents();
void RenderEmulator();
void RenderSnesPpu();
void RenderBreakpointList();
void RenderCpuState(Cpu& cpu);
void RenderMemoryViewer();
struct Bookmark {
@@ -45,14 +120,32 @@ class Emulator : public SharedRom {
void RenderCpuInstructionLog(
const std::vector<InstructionEntry>& instructionLog);
SNES snes_;
uint16_t manual_pc_ = 0;
uint8_t manual_pb_ = 0;
bool step_ = true;
bool power_ = false;
bool loading_ = 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

View File

@@ -6,72 +6,370 @@ namespace yaze {
namespace app {
namespace emu {
namespace memory {
namespace dma {
void DirectMemoryAccess::StartDMATransfer(uint8_t channelMask) {
for (int i = 0; i < 8; ++i) {
if ((channelMask & (1 << i)) != 0) {
Channel& ch = channels[i];
static const int bAdrOffsets[8][4] = {{0, 0, 0, 0}, {0, 1, 0, 1}, {0, 0, 0, 0},
{0, 0, 1, 1}, {0, 1, 2, 3}, {0, 1, 0, 1},
{0, 0, 0, 0}, {0, 0, 1, 1}};
// 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
bool fromMemory = (ch.DMAPn & 0x80) != 0;
void Reset(MemoryImpl* memory) {
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
bool transferTwoBytes = (ch.DMAPn & 0x40) != 0;
uint8_t Read(MemoryImpl* memory, uint16_t adr) {
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
std::cout << "Starting DirectMemoryAccess transfer for channel " << i
<< std::endl;
void Write(MemoryImpl* memory, uint16_t adr, uint8_t val) {
auto channel = memory->dma_channels();
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) {
// Read a byte or two bytes from memory based on the transfer size
// ...
void DoDma(SNES* snes, MemoryImpl* memory, int cpuCycles) {
auto channel = memory->dma_channels();
snes->cpu().set_int_delay(true);
// Write the data to the B-bus address (BBADn) if transferring from
// memory
// ...
// align to multiple of 8
snes->SyncCycles(true, 8);
// Update the A1Tn register based on the transfer direction
if (fromMemory) {
ch.A1Tn += transferTwoBytes ? 2 : 1;
} else {
ch.A1Tn -= transferTwoBytes ? 2 : 1;
// full transfer overhead
WaitCycle(snes, memory);
for (int i = 0; i < 8; i++) {
if (!channel[i].dmaActive) continue;
// 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
}
void DirectMemoryAccess::EnableHDMATransfers(uint8_t channelMask) {
for (int i = 0; i < 8; ++i) {
if ((channelMask & (1 << i)) != 0) {
Channel& ch = channels[i];
// Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, A2An, NLTRn)
// ...
// Perform the HDMA setup based on the channel parameters
std::cout << "Enabling HDMA transfer for channel " << i << std::endl;
// Read the HDMA table from memory starting at A1Tn
// ...
// Update the A2An register based on the HDMA table
// ...
// Update the NLTRn register based on the HDMA table
// ...
// do all updates
for (int i = 0; i < 8; i++) {
if (channel[i].hdmaActive && !channel[i].terminated) {
channel[i].repCount--;
channel[i].doTransfer = channel[i].repCount & 0x80;
snes->RunCycles(8);
uint8_t newRepCount =
snes->Read((channel[i].aBank << 16) | channel[i].tableAdr);
if ((channel[i].repCount & 0x7f) == 0) {
channel[i].repCount = newRepCount;
channel[i].tableAdr++;
if (channel[i].indirect) {
if (channel[i].repCount == 0 && i == lastActive) {
// if this is the last active channel, only fetch high, and use 0
// for low
channel[i].size = 0;
} else {
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;
}
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 emu
} // namespace app

View File

@@ -3,56 +3,32 @@
#include <cstdint>
#include "app/emu/memory/memory.h"
#include "app/emu/snes.h"
namespace yaze {
namespace app {
namespace emu {
namespace memory {
namespace dma {
class DirectMemoryAccess {
public:
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;
}
}
void Reset(MemoryImpl* memory);
void HandleDma(SNES* snes, MemoryImpl* memory, int cpu_cycles);
// DMA Transfer Modes
enum class DMA_TRANSFER_TYPE {
OAM,
PPUDATA,
CGDATA,
FILL_VRAM,
CLEAR_VRAM,
RESET_VRAM
};
void WaitCycle(SNES* snes, MemoryImpl* memory);
// Functions for handling DMA and HDMA transfers
void StartDMATransfer(uint8_t channels);
void EnableHDMATransfers(uint8_t channels);
void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles);
void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles);
// Structure for DMA and HDMA channel registers
struct Channel {
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];
void TransferByte(SNES* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank,
uint8_t bAdr, bool fromB);
uint8_t MDMAEN = 0; // Start DMA transfer
uint8_t HDMAEN = 0; // Enable HDMA transfers
};
uint8_t Read(MemoryImpl* memory, uint16_t address);
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 emu
} // 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