backend-infra-engineer: Pre-0.2.2 2024 Q3 snapshot
This commit is contained in:
3
.github/workflows/cmake.yml
vendored
3
.github/workflows/cmake.yml
vendored
@@ -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
12
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
2
Doxyfile
2
Doxyfile
@@ -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.
|
||||
|
||||
@@ -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
|
||||
--------
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
25
assets/layouts/overworld.zeml
Normal file
25
assets/layouts/overworld.zeml
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
find_package(absl)
|
||||
set(ABSL_PROPAGATE_CXX_STD ON)
|
||||
set(ABSL_CXX_STANDARD 17)
|
||||
set(ABSL_USE_GOOGLETEST_HEAD ON)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
@@ -136,3 +108,5 @@ set_target_properties(yaze
|
||||
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
|
||||
)
|
||||
endif()
|
||||
|
||||
add_subdirectory(test)
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,10 +216,16 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
|
||||
uint16_t ldle16b(uint8_t const *const p_arr);
|
||||
|
||||
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
|
||||
|
||||
@@ -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,43 +204,11 @@ 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",
|
||||
static const std::string RoomEffect[] = {"Nothing",
|
||||
"Nothing",
|
||||
"Moving Floor",
|
||||
"Moving Water",
|
||||
@@ -258,7 +217,7 @@ static const absl::string_view RoomEffect[] = {"Nothing",
|
||||
"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",
|
||||
@@ -328,7 +287,7 @@ static const absl::string_view RoomTag[] = {"Nothing",
|
||||
"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",
|
||||
|
||||
@@ -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::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
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
@@ -216,3 +237,7 @@ extern "C" void InitializeCocoa() {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef TARGET_OS_MAC
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
|
||||
@@ -40,3 +41,5 @@ void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& he
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
||||
CGContextRelease(context);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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 {
|
||||
@@ -63,3 +92,5 @@ class FileDialogWrapper {
|
||||
#else
|
||||
#error "Unsupported platform."
|
||||
#endif
|
||||
|
||||
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
|
||||
|
||||
@@ -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__
|
||||
|
||||
6
src/app/core/platform/file_path.h
Normal file
6
src/app/core/platform/file_path.h
Normal 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
|
||||
26
src/app/core/platform/file_path.mm
Normal file
26
src/app/core/platform/file_path.mm
Normal 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
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "app/core/platform/font_loader.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" ];
|
||||
@@ -48,3 +67,8 @@ void LoadSystemFonts() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Unsupported platform
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
138
src/app/core/project.h
Normal file
138
src/app/core/project.h
Normal 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
19
src/app/core/testable.h
Normal 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
|
||||
21
src/app/editor/CMakeLists.txt
Normal file
21
src/app/editor/CMakeLists.txt
Normal 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
|
||||
)
|
||||
367
src/app/editor/code/assembly_editor.cc
Normal file
367
src/app/editor/code/assembly_editor.cc
Normal 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
|
||||
79
src/app/editor/code/assembly_editor.h
Normal file
79
src/app/editor/code/assembly_editor.h
Normal 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
|
||||
88
src/app/editor/code/memory_editor.h
Normal file
88
src/app/editor/code/memory_editor.h
Normal 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
|
||||
@@ -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_
|
||||
@@ -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,7 +215,7 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() {
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit,
|
||||
if (BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("#undoTool");
|
||||
TableSetupColumn("#redoTool");
|
||||
@@ -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,7 +314,7 @@ void DungeonEditor::DrawRoomSelector() {
|
||||
gui::InputHex("Palette ID", ¤t_palette_id_);
|
||||
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
int i = 0;
|
||||
for (const auto each_room_name : zelda3::dungeon::kRoomNames) {
|
||||
@@ -318,85 +331,65 @@ 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", ¤t_entrance.entrance_id_);
|
||||
gui::InputHexWord("Room ID", ¤t_entrance.room_, 50.f, true);
|
||||
SameLine();
|
||||
|
||||
gui::InputHexWord("Room ID", &entrances_[current_entrance_id_].room_, 50.f,
|
||||
gui::InputHexByte("Dungeon ID", ¤t_entrance.dungeon_id_, 50.f, true);
|
||||
gui::InputHexByte("Blockset", ¤t_entrance.blockset_, 50.f, true);
|
||||
SameLine();
|
||||
|
||||
gui::InputHexByte("Music", ¤t_entrance.music_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("Floor", ¤t_entrance.floor_);
|
||||
Separator();
|
||||
|
||||
gui::InputHexWord("Player X ", ¤t_entrance.x_position_);
|
||||
SameLine();
|
||||
gui::InputHexWord("Player Y ", ¤t_entrance.y_position_);
|
||||
|
||||
gui::InputHexWord("Camera X", ¤t_entrance.camera_trigger_x_);
|
||||
SameLine();
|
||||
gui::InputHexWord("Camera Y", ¤t_entrance.camera_trigger_y_);
|
||||
|
||||
gui::InputHexWord("Scroll X ", ¤t_entrance.camera_x_);
|
||||
SameLine();
|
||||
gui::InputHexWord("Scroll Y ", ¤t_entrance.camera_y_);
|
||||
|
||||
gui::InputHexWord("Exit", ¤t_entrance.exit_, 50.f, true);
|
||||
|
||||
Separator();
|
||||
Text("Camera Boundaries");
|
||||
Separator();
|
||||
Text("\t\t\t\t\tNorth East South West");
|
||||
gui::InputHexByte("Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f,
|
||||
true);
|
||||
ImGui::SameLine();
|
||||
gui::InputHexByte("Dungeon ID",
|
||||
&entrances_[current_entrance_id_].dungeon_id_, 50.f,
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_qe_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_qs_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_qw_, 50.f, true);
|
||||
|
||||
gui::InputHexByte("Full room", ¤t_entrance.camera_boundary_fn_, 50.f,
|
||||
true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_fe_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_fs_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_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,
|
||||
if (BeginChild("EntranceSelector", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
for (int i = 0; i < 0x85 + 7; i++) {
|
||||
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||
@@ -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,
|
||||
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,
|
||||
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,7 +666,7 @@ void DungeonEditor::DrawUsageStats() {
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
if (ImGui::BeginTable("DungeonUsageStatsTable", 8,
|
||||
if (BeginTable("DungeonUsageStatsTable", 8,
|
||||
kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit,
|
||||
ImGui::GetContentRegionAvail())) {
|
||||
TableSetupColumn("Blockset Usage");
|
||||
@@ -699,51 +681,50 @@ void DungeonEditor::DrawUsageStats() {
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true,
|
||||
BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderSetUsage(blockset_usage_, selected_blockset_);
|
||||
ImGui::EndChild();
|
||||
EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true,
|
||||
BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderUnusedSets(blockset_usage_, 0x25);
|
||||
ImGui::EndChild();
|
||||
EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("PaletteUsageScroll", ImVec2(0, 0), true,
|
||||
BeginChild("PaletteUsageScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderSetUsage(palette_usage_, selected_palette_);
|
||||
ImGui::EndChild();
|
||||
EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true,
|
||||
BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderUnusedSets(palette_usage_, 0x48);
|
||||
ImGui::EndChild();
|
||||
EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
|
||||
ImGui::BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true,
|
||||
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,
|
||||
BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderUnusedSets(spriteset_usage_, 0x90, 0x40);
|
||||
ImGui::EndChild();
|
||||
EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("UsageGrid", ImVec2(0, 0), true,
|
||||
BeginChild("UsageGrid", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
ImGui::Text("%s",
|
||||
absl::StrFormat("Total size of all rooms: %d hex format: %#06x",
|
||||
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,8 +782,8 @@ 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())
|
||||
if (Button(absl::StrFormat("%#x",
|
||||
rooms_[row * squaresWide + col].room_size())
|
||||
.c_str(),
|
||||
ImVec2(55, 30))) {
|
||||
// Switch over to the room editor tab
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
311
src/app/editor/graphics/gfx_group_editor.cc
Normal file
311
src/app/editor/graphics/gfx_group_editor.cc
Normal 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
|
||||
@@ -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_;
|
||||
@@ -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() {
|
||||
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_);
|
||||
|
||||
if (rom()->is_loaded()) {
|
||||
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(
|
||||
rom()->bitmap_manager()[current_sheet_].ApplyPaletteWithTransparent(
|
||||
palette, edit_palette_sub_index_));
|
||||
rom()->UpdateBitmap(
|
||||
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get(),
|
||||
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_),
|
||||
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", ¤t_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]);
|
||||
@@ -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_;
|
||||
469
src/app/editor/graphics/palette_editor.cc
Normal file
469
src/app/editor/graphics/palette_editor.cc
Normal 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", ¤t_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
|
||||
@@ -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,7 +46,7 @@ class PaletteEditorHistory {
|
||||
}
|
||||
|
||||
// Restore the original color
|
||||
gfx::SnesColor GetOriginalColor(const std::string& groupName,
|
||||
gfx::SnesColor RestoreOriginalColor(const std::string& groupName,
|
||||
size_t paletteIndex,
|
||||
size_t colorIndex) const {
|
||||
for (const auto& change : recentChanges) {
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,6 +95,8 @@ class ScreenEditor : public SharedRom {
|
||||
gfx::BitmapTable sheets_;
|
||||
|
||||
gfx::Tilesheet tile16_sheet_;
|
||||
|
||||
absl::Status status_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
@@ -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,20 +25,37 @@ 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::Update() {
|
||||
if (rom()->is_loaded() && !map_blockset_loaded_) {
|
||||
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) {
|
||||
@@ -49,27 +65,33 @@ absl::Status Tile16Editor::Update() {
|
||||
|
||||
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
|
||||
*tile8_source_canvas_.custom_labels_enabled() = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::Update() {
|
||||
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",
|
||||
if (BeginMenuBar()) {
|
||||
if (BeginMenu("View")) {
|
||||
Checkbox("Show Collision Types",
|
||||
tile8_source_canvas_.custom_labels_enabled());
|
||||
ImGui::EndMenu();
|
||||
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(¤t_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,8 +189,7 @@ 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),
|
||||
if (BeginChild("Tile8 Selector", ImVec2(GetContentRegionAvail().x, 0x175),
|
||||
true)) {
|
||||
tile8_source_canvas_.DrawBackground();
|
||||
tile8_source_canvas_.DrawContextMenu(¤t_gfx_bmp_);
|
||||
@@ -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(¤t_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(¤t_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(¤t_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", ¬ify_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(¤t_gfx_bmp_);
|
||||
rom()->UpdateBitmap(¤t_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,8 +371,8 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() {
|
||||
palette_ = transfer_overworld_.AreaPalette();
|
||||
|
||||
// Create the tile16 blockset image
|
||||
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x80,
|
||||
transfer_overworld_.Tile16Blockset(),
|
||||
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
|
||||
@@ -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(¤t_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
@@ -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,8 +125,13 @@ 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
|
||||
|
||||
10
src/app/editor/master_editor_test.cc
Normal file
10
src/app/editor/master_editor_test.cc
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "master_editor.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
177
src/app/editor/message/message_data.h
Normal file
177
src/app/editor/message/message_data.h
Normal 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
|
||||
770
src/app/editor/message/message_editor.cc
Normal file
770
src/app/editor/message/message_editor.cc
Normal 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(¤t_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
|
||||
366
src/app/editor/message/message_editor.h
Normal file
366
src/app/editor/message/message_editor.h
Normal 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
|
||||
7
src/app/editor/message/message_editor_test.cc
Normal file
7
src/app/editor/message/message_editor_test.cc
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "message_editor.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
490
src/app/editor/overworld/entity.cc
Normal file
490
src/app/editor/overworld/entity.cc
Normal 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 *¤t_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", ¢erX);
|
||||
|
||||
gui::InputHex("Center Y", ¢erY);
|
||||
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
|
||||
88
src/app/editor/overworld/entity.h
Normal file
88
src/app/editor/overworld/entity.h
Normal 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 *¤t_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
|
||||
157
src/app/editor/overworld/refresh.cc
Normal file
157
src/app/editor/overworld/refresh.cc
Normal 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 ¤t_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
@@ -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
|
||||
|
||||
85
src/app/editor/settings_editor.cc
Normal file
85
src/app/editor/settings_editor.cc
Normal 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
|
||||
232
src/app/editor/settings_editor.h
Normal file
232
src/app/editor/settings_editor.h
Normal 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_
|
||||
278
src/app/editor/sprite/sprite_editor.cc
Normal file
278
src/app/editor/sprite/sprite_editor.cc
Normal 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(), ¤t_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
|
||||
117
src/app/editor/sprite/sprite_editor.h
Normal file
117
src/app/editor/sprite/sprite_editor.h
Normal 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
|
||||
396
src/app/editor/sprite/zsprite.h
Normal file
396
src/app/editor/sprite/zsprite.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
68
src/app/editor/utils/flags.h
Normal file
68
src/app/editor/utils/flags.h
Normal 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_
|
||||
@@ -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 {
|
||||
@@ -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_;
|
||||
25
src/app/editor/utils/keyboard_shortcuts.h
Normal file
25
src/app/editor/utils/keyboard_shortcuts.h
Normal 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_
|
||||
64
src/app/editor/utils/recent_files.h
Normal file
64
src/app/editor/utils/recent_files.h
Normal 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
|
||||
23
src/app/emu/CMakeLists.txt
Normal file
23
src/app/emu/CMakeLists.txt
Normal 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)
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "app/emu/audio/apu.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
@@ -15,125 +17,189 @@ 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
|
||||
}
|
||||
}
|
||||
timer_[i].cycles--;
|
||||
}
|
||||
|
||||
cycles_++;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (rom_readable_ && adr >= 0xffc0) {
|
||||
return bootRom[adr - 0xffc0];
|
||||
}
|
||||
return ram[adr];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
case 0xf2: {
|
||||
dsp_adr_ = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
case 0xf3: {
|
||||
if (dsp_adr_ < 0x80) dsp_.Write(dsp_adr_, val);
|
||||
break;
|
||||
}
|
||||
return 0; // TODO: Return the last sample instead of 0.
|
||||
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;
|
||||
}
|
||||
|
||||
const std::vector<int16_t>& Apu::GetAudioSamples() const {
|
||||
return audio_samples_;
|
||||
uint8_t Apu::SpcRead(uint16_t adr) {
|
||||
Cycle();
|
||||
return Read(adr);
|
||||
}
|
||||
|
||||
void Apu::UpdateChannelSettings() {
|
||||
// TODO: Implement this method to update the channel settings.
|
||||
void Apu::SpcWrite(uint16_t adr, uint8_t val) {
|
||||
Cycle();
|
||||
Write(adr, val);
|
||||
}
|
||||
|
||||
int16_t Apu::GenerateSample(int channel) {
|
||||
// TODO: Implement this method to generate a sample for the specified channel.
|
||||
}
|
||||
|
||||
void Apu::ApplyEnvelope(int channel) {
|
||||
// TODO: Implement this method to apply an envelope to the specified channel.
|
||||
}
|
||||
|
||||
uint8_t Apu::ReadDspMemory(uint16_t address) {
|
||||
return dsp_.ReadGlobalReg(address);
|
||||
}
|
||||
|
||||
void Apu::WriteDspMemory(uint16_t address, uint8_t value) {
|
||||
dsp_.WriteGlobalReg(address, value);
|
||||
}
|
||||
void Apu::SpcIdle(bool waiting) { Cycle(); }
|
||||
|
||||
} // namespace audio
|
||||
} // namespace emu
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
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 Dsp::NewFrame() {
|
||||
lastFrameBoundary = sampleOffset;
|
||||
}
|
||||
|
||||
void Dsp::Cycle() {
|
||||
sampleOutL = 0;
|
||||
sampleOutR = 0;
|
||||
echoOutL = 0;
|
||||
echoOutR = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
CycleChannel(i);
|
||||
}
|
||||
HandleEcho(); // also applies master volume
|
||||
counter = counter == 0 ? 30720 : counter - 1;
|
||||
HandleNoise();
|
||||
evenCycle = !evenCycle;
|
||||
// handle mute flag
|
||||
if (mute) {
|
||||
sampleOutL = 0;
|
||||
sampleOutR = 0;
|
||||
}
|
||||
// put final sample in the samplebuffer
|
||||
sampleBuffer[(sampleOffset & 0x3ff) * 2] = sampleOutL;
|
||||
sampleBuffer[(sampleOffset++ & 0x3ff) * 2 + 1] = sampleOutR;
|
||||
}
|
||||
|
||||
static int clamp16(int val) {
|
||||
return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val);
|
||||
}
|
||||
|
||||
static int clip16(int val) { return (int16_t)(val & 0xffff); }
|
||||
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
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 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:
|
||||
return voices_[voice].vol_left;
|
||||
newGain += rate == 31 ? 1024 : 32;
|
||||
break; // attack
|
||||
case 1:
|
||||
return voices_[voice].vol_right;
|
||||
newGain -= ((newGain - 1) >> 8) + 1;
|
||||
break; // decay
|
||||
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
|
||||
newGain -= ((newGain - 1) >> 8) + 1;
|
||||
break; // sustain
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg,
|
||||
uint8_t value) {
|
||||
voice %= kNumVoices;
|
||||
switch (reg % kNumVoiceRegs) {
|
||||
} else {
|
||||
if (!channel[ch].directGain) {
|
||||
rate = channel[ch].adsrRates[3];
|
||||
switch (channel[ch].gainMode) {
|
||||
case 0:
|
||||
voices_[voice].vol_left = static_cast<int8_t>(value);
|
||||
break;
|
||||
newGain -= 32;
|
||||
break; // linear decrease
|
||||
case 1:
|
||||
voices_[voice].vol_right = static_cast<int8_t>(value);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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:
|
||||
voices_[voice].pitch_low = value;
|
||||
s += 2 * old + ((3 * -old) >> 5) - older + (older >> 4);
|
||||
break;
|
||||
case 3:
|
||||
voices_[voice].pitch_high = value;
|
||||
s += 2 * old + ((13 * -old) >> 6) - older + ((3 * older) >> 4);
|
||||
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
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
void Dsp::HandleNoise() {
|
||||
if (CheckCounter(noiseRate)) {
|
||||
int bit = (noiseSample & 1) ^ ((noiseSample >> 1) & 1);
|
||||
noiseSample = ((noiseSample >> 1) & 0x3fff) | (bit << 14);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the callbacks
|
||||
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) {
|
||||
sample_fetcher_ = fetcher;
|
||||
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 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;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) {
|
||||
uint8_t adsr1 = ReadVoiceReg(voice, 0x05);
|
||||
uint8_t adsr2 = ReadVoiceReg(voice, 0x06);
|
||||
uint8_t gain = ReadVoiceReg(voice, 0x07);
|
||||
|
||||
uint8_t enableADSR = (adsr1 & 0x80) >> 7;
|
||||
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::process_envelope(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
|
||||
// Update the voice state first (based on keys, etc.)
|
||||
update_voice_state(voice_num);
|
||||
|
||||
// Calculate the envelope value based on the current amplitude
|
||||
voice.envx = calculate_envelope_value(voice.current_amplitude);
|
||||
|
||||
// Apply the envelope value to the audio output
|
||||
apply_envelope_to_output(voice_num);
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -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.
|
||||
@@ -36,275 +69,90 @@ using SamplePusher = std::function<void(int16_t)>;
|
||||
*
|
||||
* | 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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
// Setting flags in the status register
|
||||
bool m() { return GetAccumulatorSize() ? 1 : 0; }
|
||||
int GetAccumulatorSize() const { return status & 0x20; }
|
||||
int GetIndexSize() const { return status & 0x10; }
|
||||
void set_16_bit_mode() {
|
||||
void SetFlags(uint8_t val) {
|
||||
status = val;
|
||||
if (E) {
|
||||
SetAccumulatorSize(true);
|
||||
SetIndexSize(true);
|
||||
SetSP(SP() & 0xFF | 0x100);
|
||||
}
|
||||
void set_8_bit_mode() {
|
||||
SetAccumulatorSize(false);
|
||||
SetIndexSize(false);
|
||||
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 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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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}};
|
||||
@@ -8,67 +8,15 @@
|
||||
#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 {
|
||||
|
||||
class AsmParser {
|
||||
public:
|
||||
std::vector<uint8_t> Parse(const std::string& instruction) {
|
||||
std::smatch match;
|
||||
if (!std::regex_match(instruction, match, instruction_regex_)) {
|
||||
throw std::runtime_error("Invalid instruction format: " + instruction);
|
||||
}
|
||||
|
||||
std::string mnemonic = match[1];
|
||||
std::string addressing_mode = match[2];
|
||||
std::string operand = match[3];
|
||||
|
||||
std::string lookup_string = mnemonic.substr(0, 3);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
enum class AddressingMode {
|
||||
enum class AddressingMode {
|
||||
kAbsolute,
|
||||
kAbsoluteLong,
|
||||
kAbsoluteIndexedIndirect,
|
||||
@@ -97,20 +45,402 @@ class AsmParser {
|
||||
kStackRelativeIndirectIndexedYLong,
|
||||
kStack,
|
||||
kStackRelativeIndexedY,
|
||||
};
|
||||
};
|
||||
|
||||
AddressingMode InferAddressingModeFromOperand(const std::string& operand) {
|
||||
if (operand[0] == '$') {
|
||||
return AddressingMode::kAbsolute;
|
||||
} else if (operand[0] == '#') {
|
||||
// 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) {
|
||||
CreateInternalOpcodeMap();
|
||||
auto tokens = Tokenize(instruction);
|
||||
if (tokens.size() < 1) {
|
||||
throw std::runtime_error("Invalid instruction format: " + instruction);
|
||||
}
|
||||
|
||||
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++;
|
||||
|
||||
// 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++;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// 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 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)));
|
||||
}
|
||||
}
|
||||
|
||||
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
215
src/app/emu/debug/emu.cc
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
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();
|
||||
std::string navbar_layout = R"(
|
||||
BeginMenuBar {
|
||||
BeginMenu title="Options" {
|
||||
MenuItem title="Input" {}
|
||||
MenuItem title="Audio" {}
|
||||
MenuItem title="Video" {}
|
||||
}
|
||||
END_MENU_BAR()
|
||||
}
|
||||
)";
|
||||
|
||||
static auto navbar_node = gui::zeml::Parse(navbar_layout);
|
||||
gui::zeml::Render(navbar_node);
|
||||
|
||||
if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
|
||||
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)) {
|
||||
open_file = true;
|
||||
|
||||
// About Debugger logic
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("About Debugger");
|
||||
}
|
||||
// About Debugger logic
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
// 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 {
|
||||
ch.A1Tn -= transferTwoBytes ? 2 : 1;
|
||||
TransferByte(snes, memory, channel[i].tableAdr++, channel[i].aBank,
|
||||
channel[i].bAdr + bAdrOffsets[channel[i].mode][j],
|
||||
channel[i].fromB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the channel registers after the transfer (e.g., A1Tn, DASn)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
MDMAEN = channelMask; // Set the MDMAEN register to the channel mask
|
||||
if (do_sync) snes->SyncCycles(false, cycles);
|
||||
}
|
||||
|
||||
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
|
||||
// ...
|
||||
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);
|
||||
}
|
||||
}
|
||||
HDMAEN = channelMask; // Set the HDMAEN register to the channel mask
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
37
src/app/emu/memory/dma_channel.h
Normal file
37
src/app/emu/memory/dma_channel.h
Normal 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
Reference in New Issue
Block a user