diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 73b2469c..80384621 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -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 diff --git a/.gitmodules b/.gitmodules index f610c45d..bf88cd5f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c4e3205..d3428b8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) \ No newline at end of file +add_subdirectory(src) \ No newline at end of file diff --git a/Doxyfile b/Doxyfile index b9612636..22d0cd5a 100644 --- a/Doxyfile +++ b/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. diff --git a/README.md b/README.md index 7dde2535..ed1eae11 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Building and installation ## Documentation - For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze. -- For developers, please refer to [infrastructure.md](docs/infrastructure.md) for information on the project's infrastructure. +- For developers, please refer to the [documentation](https://scawful.github.io/yaze/index.html) for information on the project's infrastructure. License -------- @@ -44,9 +44,10 @@ SDL2, ImGui and Abseil are subject to respective licenses. Screenshots -------- -![image](https://user-images.githubusercontent.com/47263509/194669806-2b0da68d-9d38-4f52-bcce-c60ee861092c.png) +![image](https://github.com/scawful/yaze/assets/47263509/8b62b142-1de4-4ca4-8c49-d50c08ba4c8e) -![image](https://github.com/scawful/yaze/assets/47263509/8913f7ff-6345-4295-ae05-782fd3949eb5) +![image](https://github.com/scawful/yaze/assets/47263509/d8f0039d-d2e4-47d7-b420-554b20ac626f) + +![image](https://github.com/scawful/yaze/assets/47263509/34b36666-cbea-420b-af90-626099470ae4) -![image](https://github.com/scawful/yaze/assets/47263509/e1cf3edb-a59e-4f0a-b4e0-d68925803e58) diff --git a/assets/layouts/overworld.zeml b/assets/layouts/overworld.zeml new file mode 100644 index 00000000..3e8af78a --- /dev/null +++ b/assets/layouts/overworld.zeml @@ -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" + } +} \ No newline at end of file diff --git a/cmake/absl.cmake b/cmake/absl.cmake index 912ce128..42fed470 100644 --- a/cmake/absl.cmake +++ b/cmake/absl.cmake @@ -1,3 +1,4 @@ +find_package(absl) set(ABSL_PROPAGATE_CXX_STD ON) set(ABSL_CXX_STANDARD 17) set(ABSL_USE_GOOGLETEST_HEAD ON) diff --git a/cmake/imgui.cmake b/cmake/imgui.cmake index b594b913..2e7cf648 100644 --- a/cmake/imgui.cmake +++ b/cmake/imgui.cmake @@ -21,6 +21,14 @@ target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH}) target_compile_definitions(ImGuiColorTextEdit PUBLIC IMGUI_IMPL_OPENGL_LOADER_CUSTOM= 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= GL_GLEXT_PROTOTYPES=1) + set( IMGUI_SRC ${IMGUI_PATH}/imgui.cpp diff --git a/cmake/openssl.cmake b/cmake/openssl.cmake deleted file mode 100644 index d2da52c0..00000000 --- a/cmake/openssl.cmake +++ /dev/null @@ -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() diff --git a/cmake/sdl2.cmake b/cmake/sdl2.cmake index 3b2299e2..d3464c60 100644 --- a/cmake/sdl2.cmake +++ b/cmake/sdl2.cmake @@ -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) \ No newline at end of file +endif() \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f9648c5b..81fd937b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,22 +3,7 @@ set( app/core/common.cc app/core/controller.cc app/core/labeling.cc -) - -set( - YAZE_APP_EDITOR_SRC - app/editor/dungeon_editor.cc - app/editor/graphics_editor.cc - app/editor/master_editor.cc - app/editor/overworld_editor.cc - app/editor/screen_editor.cc - app/editor/sprite_editor.cc - app/editor/modules/music_editor.cc - app/editor/modules/palette_editor.cc - app/editor/modules/assembly_editor.cc - app/editor/modules/tile16_editor.cc - app/editor/modules/gfx_group_editor.cc - app/editor/context/gfx_context.cc + app/emu/emulator.cc ) set( @@ -32,32 +17,18 @@ set( app/gfx/tilesheet.cc ) -set( - YAZE_APP_ZELDA3_SRC - app/zelda3/overworld/overworld_map.cc - app/zelda3/overworld/overworld.cc - app/zelda3/screen/inventory.cc - app/zelda3/screen/title_screen.cc - app/zelda3/sprite/sprite.cc - app/zelda3/music/tracker.cc - app/zelda3/dungeon/room.cc - app/zelda3/dungeon/room_object.cc - app/zelda3/dungeon/object_renderer.cc -) - set( YAZE_GUI_SRC + app/gui/asset_browser.cc app/gui/canvas.cc app/gui/input.cc app/gui/style.cc - app/gui/widgets.cc app/gui/color.cc - app/gui/pipeline.cc + app/gui/zeml.cc ) set( YAZE_APP_EMU_SRC - app/emu/emulator.cc app/emu/audio/apu.cc app/emu/audio/spc700.cc app/emu/audio/dsp.cc @@ -92,6 +63,7 @@ if(APPLE) app/core/platform/app_delegate.mm app/core/platform/font_loader.mm app/core/platform/clipboard.mm + app/core/platform/file_path.mm ) find_library(COCOA_LIBRARY Cocoa) @@ -102,7 +74,7 @@ if(APPLE) endif() include(app/CMakeLists.txt) -include(cli/CMakeLists.txt) +# include(cli/CMakeLists.txt) Excluded for now, macOS include breaks action build if (UNIX) target_compile_definitions(yaze PRIVATE "linux") @@ -135,4 +107,6 @@ set_target_properties(yaze RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res" ) -endif() \ No newline at end of file +endif() + +add_subdirectory(test) \ No newline at end of file diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 4e9dd430..6f6dd4eb 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 ) diff --git a/src/app/core/common.cc b/src/app/core/common.cc index ec79a9bf..d751994d 100644 --- a/src/app/core/common.cc +++ b/src/app/core/common.cc @@ -1,6 +1,6 @@ #include "common.h" -#include +#include "imgui/imgui.h" #include #include @@ -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 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 diff --git a/src/app/core/common.h b/src/app/core/common.h index 9feb1964..0fd56f7b 100644 --- a/src/app/core/common.h +++ b/src/app/core/common.h @@ -1,7 +1,7 @@ #ifndef YAZE_CORE_COMMON_H #define YAZE_CORE_COMMON_H -#include +#include "imgui/imgui.h" #include #include @@ -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 logs; }; std::string UppercaseHexByte(uint8_t byte, bool leading = false); @@ -205,13 +216,19 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index); uint16_t ldle16b(uint8_t const *const p_arr); void stle16b(uint8_t *const p_arr, uint16_t const p_val); -void stle32b(uint8_t *const p_arr, uint32_t const p_val); -void stle32b_i(uint8_t *const p_arr, size_t const p_index, - uint32_t const p_val); +struct FolderItem { + std::string name; + std::vector subfolders; + std::vector files; +}; + +typedef struct FolderItem FolderItem; + +uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true); } // namespace core } // namespace app } // namespace yaze -#endif \ No newline at end of file +#endif diff --git a/src/app/core/constants.h b/src/app/core/constants.h index d7ae0002..a5e37676 100644 --- a/src/app/core/constants.h +++ b/src/app/core/constants.h @@ -5,10 +5,6 @@ #include "absl/strings/string_view.h" -#define IMGUI_DEFINE_MATH_OPERATORS - -#define BASIC_BUTTON(w) if (ImGui::Button(w)) - #define TAB_BAR(w) if (ImGui::BeginTabBar(w)) { #define END_TAB_BAR() \ ImGui::EndTabBar(); \ @@ -19,11 +15,6 @@ ImGui::EndTabItem(); \ } -#define MENU_BAR() if (ImGui::BeginMenuBar()) { -#define END_MENU_BAR() \ - ImGui::EndMenuBar(); \ - } - #define MENU_ITEM(w) if (ImGui::MenuItem(w)) #define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v)) @@ -145,7 +136,7 @@ namespace app { namespace core { constexpr uint32_t kRedPen = 0xFF0000FF; -constexpr float kYazeVersion = 0.07; +constexpr float kYazeVersion = 0.2; // ============================================================================ // Magic numbers @@ -213,122 +204,90 @@ constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries constexpr int GraveLinkSpecialHole = 0x46DD9; // short constexpr int GraveLinkSpecialStairs = 0x46DE0; // short -// ============================================================================ -// Palettes Related Variables - This contain all the palettes of the game -// ============================================================================ -constexpr int overworldPaletteMain = 0xDE6C8; -constexpr int overworldPaletteAuxialiary = 0xDE86C; -constexpr int overworldPaletteAnimated = 0xDE604; -constexpr int globalSpritePalettesLW = 0xDD218; -constexpr int globalSpritePalettesDW = 0xDD290; -// Green, Blue, Red, Bunny, Electrocuted (15 colors each) -constexpr int armorPalettes = 0xDD308; -constexpr int spritePalettesAux1 = 0xDD39E; // 7 colors each -constexpr int spritePalettesAux2 = 0xDD446; // 7 colors each -constexpr int spritePalettesAux3 = 0xDD4E0; // 7 colors each -constexpr int swordPalettes = 0xDD630; // 3 colors each - 4 entries -constexpr int shieldPalettes = 0xDD648; // 4 colors each - 3 entries -constexpr int hudPalettes = 0xDD660; -constexpr int dungeonMapPalettes = 0xDD70A; // 21 colors -constexpr int dungeonMainPalettes = 0xDD734; //(15*6) colors each - 20 entries -constexpr int dungeonMapBgPalettes = 0xDE544; // 16*6 -// Mirrored Value at 0x75645 : 0x75625 -constexpr int hardcodedGrassLW = 0x5FEA9; -constexpr int hardcodedGrassDW = 0x05FEB3; // 0x7564F -constexpr int hardcodedGrassSpecial = 0x75640; -constexpr int overworldMiniMapPalettes = 0x55B27; -constexpr int triforcePalette = 0x64425; -constexpr int crystalPalette = 0xF4CD3; -constexpr int customAreaSpecificBGPalette = - 0x140000; // 2 bytes for each overworld area (320) -constexpr int customAreaSpecificBGASM = 0x140150; -constexpr int customAreaSpecificBGEnabled = - 0x140140; // 1 byte, not 0 if enabled - // ============================================================================ // Names // ============================================================================ -static const absl::string_view RoomEffect[] = {"Nothing", - "Nothing", - "Moving Floor", - "Moving Water", - "Trinexx Shell", - "Red Flashes", - "Light Torch to See Floor", - "Ganon's Darkness"}; +static const std::string RoomEffect[] = {"Nothing", + "Nothing", + "Moving Floor", + "Moving Water", + "Trinexx Shell", + "Red Flashes", + "Light Torch to See Floor", + "Ganon's Darkness"}; -static const absl::string_view RoomTag[] = {"Nothing", +static const std::string RoomTag[] = {"Nothing", - "NW Kill Enemy to Open", - "NE Kill Enemy to Open", - "SW Kill Enemy to Open", - "SE Kill Enemy to Open", - "W Kill Enemy to Open", - "E Kill Enemy to Open", - "N Kill Enemy to Open", - "S Kill Enemy to Open", - "Clear Quadrant to Open", - "Clear Full Tile to Open", + "NW Kill Enemy to Open", + "NE Kill Enemy to Open", + "SW Kill Enemy to Open", + "SE Kill Enemy to Open", + "W Kill Enemy to Open", + "E Kill Enemy to Open", + "N Kill Enemy to Open", + "S Kill Enemy to Open", + "Clear Quadrant to Open", + "Clear Full Tile to Open", - "NW Push Block to Open", - "NE Push Block to Open", - "SW Push Block to Open", - "SE Push Block to Open", - "W Push Block to Open", - "E Push Block to Open", - "N Push Block to Open", - "S Push Block to Open", - "Push Block to Open", - "Pull Lever to Open", - "Collect Prize to Open", + "NW Push Block to Open", + "NE Push Block to Open", + "SW Push Block to Open", + "SE Push Block to Open", + "W Push Block to Open", + "E Push Block to Open", + "N Push Block to Open", + "S Push Block to Open", + "Push Block to Open", + "Pull Lever to Open", + "Collect Prize to Open", - "Hold Switch Open Door", - "Toggle Switch to Open Door", - "Turn off Water", - "Turn on Water", - "Water Gate", - "Water Twin", - "Moving Wall Right", - "Moving Wall Left", - "Crash", - "Crash", - "Push Switch Exploding Wall", - "Holes 0", - "Open Chest (Holes 0)", - "Holes 1", - "Holes 2", - "Defeat Boss for Dungeon Prize", + "Hold Switch Open Door", + "Toggle Switch to Open Door", + "Turn off Water", + "Turn on Water", + "Water Gate", + "Water Twin", + "Moving Wall Right", + "Moving Wall Left", + "Crash", + "Crash", + "Push Switch Exploding Wall", + "Holes 0", + "Open Chest (Holes 0)", + "Holes 1", + "Holes 2", + "Defeat Boss for Dungeon Prize", - "SE Kill Enemy to Push Block", - "Trigger Switch Chest", - "Pull Lever Exploding Wall", - "NW Kill Enemy for Chest", - "NE Kill Enemy for Chest", - "SW Kill Enemy for Chest", - "SE Kill Enemy for Chest", - "W Kill Enemy for Chest", - "E Kill Enemy for Chest", - "N Kill Enemy for Chest", - "S Kill Enemy for Chest", - "Clear Quadrant for Chest", - "Clear Full Tile for Chest", + "SE Kill Enemy to Push Block", + "Trigger Switch Chest", + "Pull Lever Exploding Wall", + "NW Kill Enemy for Chest", + "NE Kill Enemy for Chest", + "SW Kill Enemy for Chest", + "SE Kill Enemy for Chest", + "W Kill Enemy for Chest", + "E Kill Enemy for Chest", + "N Kill Enemy for Chest", + "S Kill Enemy for Chest", + "Clear Quadrant for Chest", + "Clear Full Tile for Chest", - "Light Torches to Open", - "Holes 3", - "Holes 4", - "Holes 5", - "Holes 6", - "Agahnim Room", - "Holes 7", - "Holes 8", - "Open Chest for Holes 8", - "Push Block for Chest", - "Clear Room for Triforce Door", - "Light Torches for Chest", - "Kill Boss Again"}; + "Light Torches to Open", + "Holes 3", + "Holes 4", + "Holes 5", + "Holes 6", + "Agahnim Room", + "Holes 7", + "Holes 8", + "Open Chest for Holes 8", + "Push Block for Chest", + "Clear Room for Triforce Door", + "Light Torches for Chest", + "Kill Boss Again"}; -static const absl::string_view SecretItemNames[] = { +static const std::string SecretItemNames[] = { "Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack", "Bomb", "Heart ", "Blue Rupee", @@ -340,7 +299,7 @@ static const absl::string_view SecretItemNames[] = { "Hole", "Warp", "Staircase", "Bombable", "Switch"}; -static const absl::string_view TileTypeNames[] = { +static const std::string TileTypeNames[] = { "$00 Nothing (standard floor)", "$01 Collision", "$02 Collision", @@ -598,7 +557,7 @@ static const absl::string_view TileTypeNames[] = { "$FE Door X top? (unused?)", "$FF Door X top? (unused?)"}; -static const absl::string_view kSpriteDefaultNames[]{ +static const std::string kSpriteDefaultNames[]{ "00 Raven", "01 Vulture", "02 Flying Stalfos Head", @@ -857,7 +816,7 @@ static const absl::string_view kSpriteDefaultNames[]{ "FF", }; -static const absl::string_view overlordnames[] = { +static const std::string overlordnames[] = { "Overlord_SpritePositionTarget", "Overlord_AllDirectionMetalBallFactory", "Overlord_CascadeMetalBallFactory", diff --git a/src/app/core/controller.cc b/src/app/core/controller.cc index a2dd3423..12f843bf 100644 --- a/src/app/core/controller.cc +++ b/src/app/core/controller.cc @@ -1,11 +1,22 @@ #include "controller.h" #include -#include -#include -#include -#include -#include + +#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 +#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 @@ -22,8 +33,37 @@ namespace core { namespace { +constexpr ImGuiWindowFlags kMainEditorFlags = + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | + ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar; + +using ::ImVec2; +using ImGui::Begin; +using ImGui::End; +using ImGui::GetIO; +using ImGui::NewFrame; +using ImGui::SetNextWindowPos; +using ImGui::SetNextWindowSize; + +void NewMasterFrame() { + const ImGuiIO &io = GetIO(); + ImGui_ImplSDLRenderer2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + NewFrame(); + SetNextWindowPos(gui::kZeroPos); + ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y); + SetNextWindowSize(dimensions, ImGuiCond_Always); + + if (!Begin("##YazeMain", nullptr, kMainEditorFlags)) { + End(); + return; + } +} + void InitializeKeymap() { ImGuiIO &io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_LeftSuper] = SDL_GetScancodeFromKey(SDLK_LGUI); io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE); io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT); io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN); @@ -49,6 +89,44 @@ void InitializeKeymap() { io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8); io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9); io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0); + io.KeyMap[ImGuiKey_A] = SDL_GetScancodeFromKey(SDLK_a); + io.KeyMap[ImGuiKey_B] = SDL_GetScancodeFromKey(SDLK_b); + io.KeyMap[ImGuiKey_C] = SDL_GetScancodeFromKey(SDLK_c); + io.KeyMap[ImGuiKey_D] = SDL_GetScancodeFromKey(SDLK_d); + io.KeyMap[ImGuiKey_E] = SDL_GetScancodeFromKey(SDLK_e); + io.KeyMap[ImGuiKey_F] = SDL_GetScancodeFromKey(SDLK_f); + io.KeyMap[ImGuiKey_G] = SDL_GetScancodeFromKey(SDLK_g); + io.KeyMap[ImGuiKey_H] = SDL_GetScancodeFromKey(SDLK_h); + io.KeyMap[ImGuiKey_I] = SDL_GetScancodeFromKey(SDLK_i); + io.KeyMap[ImGuiKey_J] = SDL_GetScancodeFromKey(SDLK_j); + io.KeyMap[ImGuiKey_K] = SDL_GetScancodeFromKey(SDLK_k); + io.KeyMap[ImGuiKey_L] = SDL_GetScancodeFromKey(SDLK_l); + io.KeyMap[ImGuiKey_M] = SDL_GetScancodeFromKey(SDLK_m); + io.KeyMap[ImGuiKey_N] = SDL_GetScancodeFromKey(SDLK_n); + io.KeyMap[ImGuiKey_O] = SDL_GetScancodeFromKey(SDLK_o); + io.KeyMap[ImGuiKey_P] = SDL_GetScancodeFromKey(SDLK_p); + io.KeyMap[ImGuiKey_Q] = SDL_GetScancodeFromKey(SDLK_q); + io.KeyMap[ImGuiKey_R] = SDL_GetScancodeFromKey(SDLK_r); + io.KeyMap[ImGuiKey_S] = SDL_GetScancodeFromKey(SDLK_s); + io.KeyMap[ImGuiKey_T] = SDL_GetScancodeFromKey(SDLK_t); + io.KeyMap[ImGuiKey_U] = SDL_GetScancodeFromKey(SDLK_u); + io.KeyMap[ImGuiKey_V] = SDL_GetScancodeFromKey(SDLK_v); + io.KeyMap[ImGuiKey_W] = SDL_GetScancodeFromKey(SDLK_w); + io.KeyMap[ImGuiKey_X] = SDL_GetScancodeFromKey(SDLK_x); + io.KeyMap[ImGuiKey_Y] = SDL_GetScancodeFromKey(SDLK_y); + io.KeyMap[ImGuiKey_Z] = SDL_GetScancodeFromKey(SDLK_z); + io.KeyMap[ImGuiKey_F1] = SDL_GetScancodeFromKey(SDLK_F1); + io.KeyMap[ImGuiKey_F2] = SDL_GetScancodeFromKey(SDLK_F2); + io.KeyMap[ImGuiKey_F3] = SDL_GetScancodeFromKey(SDLK_F3); + io.KeyMap[ImGuiKey_F4] = SDL_GetScancodeFromKey(SDLK_F4); + io.KeyMap[ImGuiKey_F5] = SDL_GetScancodeFromKey(SDLK_F5); + io.KeyMap[ImGuiKey_F6] = SDL_GetScancodeFromKey(SDLK_F6); + io.KeyMap[ImGuiKey_F7] = SDL_GetScancodeFromKey(SDLK_F7); + io.KeyMap[ImGuiKey_F8] = SDL_GetScancodeFromKey(SDLK_F8); + io.KeyMap[ImGuiKey_F9] = SDL_GetScancodeFromKey(SDLK_F9); + io.KeyMap[ImGuiKey_F10] = SDL_GetScancodeFromKey(SDLK_F10); + io.KeyMap[ImGuiKey_F11] = SDL_GetScancodeFromKey(SDLK_F11); + io.KeyMap[ImGuiKey_F12] = SDL_GetScancodeFromKey(SDLK_F12); } void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) { @@ -66,25 +144,63 @@ void InitializeClipboard() { io.ClipboardUserData = nullptr; } -void HandleKeyDown(SDL_Event &event) { +void HandleKeyDown(SDL_Event &event, editor::MasterEditor &editor) { ImGuiIO &io = ImGui::GetIO(); io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN); + io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); + io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); + io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); + io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); + switch (event.key.keysym.sym) { - case SDLK_UP: - case SDLK_DOWN: - case SDLK_RETURN: case SDLK_BACKSPACE: case SDLK_LSHIFT: case SDLK_LCTRL: case SDLK_TAB: io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN); break; + case SDLK_z: + editor.emulator().snes().SetButtonState(1, 0, true); + break; + case SDLK_a: + editor.emulator().snes().SetButtonState(1, 1, true); + break; + case SDLK_RSHIFT: + editor.emulator().snes().SetButtonState(1, 2, true); + break; + case SDLK_RETURN: + editor.emulator().snes().SetButtonState(1, 3, true); + break; + case SDLK_UP: + editor.emulator().snes().SetButtonState(1, 4, true); + break; + case SDLK_DOWN: + editor.emulator().snes().SetButtonState(1, 5, true); + break; + case SDLK_LEFT: + editor.emulator().snes().SetButtonState(1, 6, true); + break; + case SDLK_RIGHT: + editor.emulator().snes().SetButtonState(1, 7, true); + break; + case SDLK_x: + editor.emulator().snes().SetButtonState(1, 8, true); + break; + case SDLK_s: + editor.emulator().snes().SetButtonState(1, 9, true); + break; + case SDLK_d: + editor.emulator().snes().SetButtonState(1, 10, true); + break; + case SDLK_c: + editor.emulator().snes().SetButtonState(1, 11, true); + break; default: break; } } -void HandleKeyUp(SDL_Event &event) { +void HandleKeyUp(SDL_Event &event, editor::MasterEditor &editor) { ImGuiIO &io = ImGui::GetIO(); int key = event.key.keysym.scancode; IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown)); @@ -93,6 +209,47 @@ void HandleKeyUp(SDL_Event &event) { io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); + + switch (event.key.keysym.sym) { + case SDLK_z: + editor.emulator().snes().SetButtonState(1, 0, false); + break; + case SDLK_a: + editor.emulator().snes().SetButtonState(1, 1, false); + break; + case SDLK_RSHIFT: + editor.emulator().snes().SetButtonState(1, 2, false); + break; + case SDLK_RETURN: + editor.emulator().snes().SetButtonState(1, 3, false); + break; + case SDLK_UP: + editor.emulator().snes().SetButtonState(1, 4, false); + break; + case SDLK_DOWN: + editor.emulator().snes().SetButtonState(1, 5, false); + break; + case SDLK_LEFT: + editor.emulator().snes().SetButtonState(1, 6, false); + break; + case SDLK_RIGHT: + editor.emulator().snes().SetButtonState(1, 7, false); + break; + case SDLK_x: + editor.emulator().snes().SetButtonState(1, 8, false); + break; + case SDLK_s: + editor.emulator().snes().SetButtonState(1, 9, false); + break; + case SDLK_d: + editor.emulator().snes().SetButtonState(1, 10, false); + break; + case SDLK_c: + editor.emulator().snes().SetButtonState(1, 11, false); + break; + default: + break; + } } void ChangeWindowSizeEvent(SDL_Event &event) { @@ -117,12 +274,36 @@ void HandleMouseMovement(int &wheel) { } // namespace -absl::Status Controller::OnEntry() { +absl::Status Controller::OnEntry(std::string filename) { +#if defined(__APPLE__) && defined(__MACH__) +#if TARGET_IPHONE_SIMULATOR == 1 + /* iOS in Xcode simulator */ + platform_ = Platform::kiOS; +#elif TARGET_OS_IPHONE == 1 + /* iOS */ + platform_ = Platform::kiOS; +#elif TARGET_OS_MAC == 1 + /* macOS */ + platform_ = Platform::kMacOS; +#endif +#elif defined(_WIN32) + platform_ = Platform::kWindows; +#elif defined(__linux__) + platform_ = Platform::kLinux; +#else + platform_ = Platform::kUnknown; +#endif + RETURN_IF_ERROR(CreateSDL_Window()) RETURN_IF_ERROR(CreateRenderer()) RETURN_IF_ERROR(CreateGuiContext()) + if (flags()->kLoadAudioDevice) { + RETURN_IF_ERROR(LoadAudioDevice()) + master_editor_.emulator().set_audio_buffer(audio_buffer_); + master_editor_.emulator().set_audio_device_id(audio_device_); + } InitializeKeymap(); - master_editor_.SetupScreen(renderer_); + master_editor_.SetupScreen(renderer_, filename); active_ = true; return absl::OkStatus(); } @@ -135,10 +316,10 @@ void Controller::OnInput() { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: - HandleKeyDown(event); + HandleKeyDown(event, master_editor_); break; case SDL_KEYUP: - HandleKeyUp(event); + HandleKeyUp(event, master_editor_); break; case SDL_TEXTINPUT: io.AddInputCharactersUTF8(event.text.text); @@ -166,26 +347,57 @@ void Controller::OnInput() { HandleMouseMovement(wheel); } -void Controller::OnLoad() { PRINT_IF_ERROR(master_editor_.Update()); } +absl::Status Controller::OnLoad() { + if (master_editor_.quit()) { + active_ = false; + } + NewMasterFrame(); + RETURN_IF_ERROR(master_editor_.Update()); + return absl::OkStatus(); +} void Controller::DoRender() const { ImGui::Render(); SDL_RenderClear(renderer_.get()); - ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); + ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get()); SDL_RenderPresent(renderer_.get()); } void Controller::OnExit() { - master_editor_.Shutdown(); - Mix_CloseAudio(); - ImGui_ImplSDLRenderer2_Shutdown(); - ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + if (flags()->kLoadAudioDevice) { + SDL_PauseAudioDevice(audio_device_, 1); + SDL_CloseAudioDevice(audio_device_); + delete audio_buffer_; + } + switch (platform_) { + case Platform::kMacOS: + case Platform::kWindows: + case Platform::kLinux: + ImGui_ImplSDLRenderer2_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + break; + case Platform::kiOS: + // Deferred + break; + default: + break; + } ImGui::DestroyContext(); SDL_Quit(); } absl::Status Controller::CreateSDL_Window() { - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_TIMER; + if (flags()->kUseNewImGuiInput) { + sdl_flags |= SDL_INIT_GAMECONTROLLER; + } + + if (flags()->kLoadAudioDevice) { + sdl_flags |= SDL_INIT_AUDIO; + } + + if (SDL_Init(sdl_flags) != 0) { return absl::InternalError( absl::StrFormat("SDL_Init: %s\n", SDL_GetError())); } else { @@ -206,11 +418,6 @@ absl::Status Controller::CreateSDL_Window() { return absl::InternalError( absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError())); } - // Initialize SDL_mixer - if (Mix_OpenAudio(32000, MIX_DEFAULT_FORMAT, 2, 1024) < 0) { - printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n", - Mix_GetError()); - } } return absl::OkStatus(); } @@ -264,13 +471,13 @@ absl::Status Controller::CreateGuiContext() { absl::Status Controller::LoadFontFamilies() const { ImGuiIO &io = ImGui::GetIO(); - // Define constants - static const char *KARLA_REGULAR = "assets/font/Karla-Regular.ttf"; - static const char *ROBOTO_MEDIUM = "assets/font/Roboto-Medium.ttf"; - static const char *COUSINE_REGULAR = "assets/font/Cousine-Regular.ttf"; - static const char *DROID_SANS = "assets/font/DroidSans.ttf"; - static const char *NOTO_SANS_JP = "assets/font/NotoSansJP.ttf"; - static const char *IBM_PLEX_JP = "assets/font/IBMPlexSansJP-Bold.ttf"; + const char *font_path = "assets/font/"; + static const char *KARLA_REGULAR = "Karla-Regular.ttf"; + static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf"; + static const char *COUSINE_REGULAR = "Cousine-Regular.ttf"; + static const char *DROID_SANS = "DroidSans.ttf"; + static const char *NOTO_SANS_JP = "NotoSansJP.ttf"; + static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf"; static const float FONT_SIZE_DEFAULT = 14.0f; static const float FONT_SIZE_DROID_SANS = 16.0f; static const float ICON_FONT_SIZE = 18.0f; @@ -299,23 +506,78 @@ absl::Status Controller::LoadFontFamilies() const { float font_size = (font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT; - if (!io.Fonts->AddFontFromFileTTF(font_path, font_size)) { + std::string actual_font_path; +#ifdef __APPLE__ +#if TARGET_OS_IOS == 1 + const std::string kBundlePath = GetBundleResourcePath(); + actual_font_path = kBundlePath + font_path; +#else + actual_font_path = std::filesystem::absolute(font_path).string(); +#endif +#else + actual_font_path = font_path; +#endif + + if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) { return absl::InternalError( - absl::StrFormat("Failed to load font from %s", font_path)); + absl::StrFormat("Failed to load font from %s", actual_font_path)); } // Merge icon set - io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE, + std::string actual_icon_font_path = ""; + const char *icon_font_path = FONT_ICON_FILE_NAME_MD; +#if defined(__APPLE__) && defined(__MACH__) +#if TARGET_OS_IOS == 1 + const std::string kIconBundlePath = GetBundleResourcePath(); + actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf"; +#else + actual_icon_font_path = std::filesystem::absolute(icon_font_path).string(); +#endif +#else +#endif + io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(), ICON_FONT_SIZE, &icons_config, icons_ranges); // Merge Japanese font - io.Fonts->AddFontFromFileTTF(NOTO_SANS_JP, 18.0f, &japanese_font_config, + std::string actual_japanese_font_path = ""; + const char *japanese_font_path = NOTO_SANS_JP; +#if defined(__APPLE__) && defined(__MACH__) +#if TARGET_OS_IOS == 1 + const std::string kJapaneseBundlePath = GetBundleResourcePath(); + actual_japanese_font_path = kJapaneseBundlePath + japanese_font_path; +#else + actual_japanese_font_path = + std::filesystem::absolute(japanese_font_path).string(); +#endif +#else +#endif + io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f, + &japanese_font_config, io.Fonts->GetGlyphRangesJapanese()); } return absl::OkStatus(); } +absl::Status Controller::LoadAudioDevice() { + SDL_AudioSpec want, have; + SDL_memset(&want, 0, sizeof(want)); + want.freq = audio_frequency_; + want.format = AUDIO_S16; + want.channels = 2; + want.samples = 2048; + want.callback = NULL; // Uses the queue + audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + if (audio_device_ == 0) { + return absl::InternalError( + absl::StrFormat("Failed to open audio: %s\n", SDL_GetError())); + } + audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4]; + master_editor_.emulator().set_audio_buffer(audio_buffer_); + SDL_PauseAudioDevice(audio_device_, 0); + return absl::OkStatus(); +} + } // namespace core } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/core/controller.h b/src/app/core/controller.h index 7a72cffc..6df680d0 100644 --- a/src/app/core/controller.h +++ b/src/app/core/controller.h @@ -2,10 +2,11 @@ #define YAZE_APP_CORE_CONTROLLER_H #include -#include -#include -#include -#include +#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 @@ -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 window_; std::shared_ptr renderer_; }; @@ -70,4 +82,4 @@ class Controller : public ExperimentFlags { } // namespace app } // namespace yaze -#endif // YAZE_APP_CORE_CONTROLLER_H \ No newline at end of file +#endif // YAZE_APP_CORE_CONTROLLER_H diff --git a/src/app/core/labeling.cc b/src/app/core/labeling.cc index de30023f..db464091 100644 --- a/src/app/core/labeling.cc +++ b/src/app/core/labeling.cc @@ -1,7 +1,7 @@ #include "app/core/labeling.h" -#include -#include +#include "imgui/imgui.h" +#include "imgui/misc/cpp/imgui_stdlib.h" #include #include @@ -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(); } diff --git a/src/app/core/labeling.h b/src/app/core/labeling.h index feeeafcf..95787bdf 100644 --- a/src/app/core/labeling.h +++ b/src/app/core/labeling.h @@ -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 { diff --git a/src/app/core/platform/app_delegate.h b/src/app/core/platform/app_delegate.h index 10d60f86..aab3df42 100644 --- a/src/app/core/platform/app_delegate.h +++ b/src/app/core/platform/app_delegate.h @@ -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 diff --git a/src/app/core/platform/app_delegate.mm b/src/app/core/platform/app_delegate.mm index 3b76351c..141df9e8 100644 --- a/src/app/core/platform/app_delegate.mm +++ b/src/app/core/platform/app_delegate.mm @@ -1,12 +1,26 @@ // AppDelegate.mm -#import - -#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 + +#import + +#if TARGET_IPHONE_SIMULATOR == 1 +/* iOS in Xcode simulator */ + +#elif TARGET_OS_IPHONE == 1 +/* iOS */ + +#elif TARGET_OS_MAC == 1 +/* macOS */ +#import + @interface AppDelegate : NSObject - (void)setupMenus; // - (void)changeApplicationIcon; @@ -195,7 +209,14 @@ } - (void)openFileAction:(id)sender { - yaze::app::SharedRom::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog()); + if (!yaze::app::SharedRom::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog()) + .ok()) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Error"]; + [alert setInformativeText:@"Failed to load file."]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + } } - (void)cutAction:(id)sender { @@ -215,4 +236,8 @@ extern "C" void InitializeCocoa() { } } -@end \ No newline at end of file +@end + +#endif + +#endif diff --git a/src/app/core/platform/clipboard.mm b/src/app/core/platform/clipboard.mm index b3dc6f9c..bdaba4a7 100644 --- a/src/app/core/platform/clipboard.mm +++ b/src/app/core/platform/clipboard.mm @@ -3,6 +3,7 @@ #include #include +#ifdef TARGET_OS_MAC #import void CopyImageToClipboard(const std::vector& pngData) { @@ -39,4 +40,6 @@ void GetImageFromClipboard(std::vector& pixel_data, int& width, int& he kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); CGContextRelease(context); -} \ No newline at end of file +} + +#endif diff --git a/src/app/core/platform/file_dialog.h b/src/app/core/platform/file_dialog.h index 45725bc9..8d7257c2 100644 --- a/src/app/core/platform/file_dialog.h +++ b/src/app/core/platform/file_dialog.h @@ -1,3 +1,6 @@ +#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H +#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H + #include #ifdef _WIN32 @@ -42,13 +45,39 @@ class FileDialogWrapper { #elif defined(__APPLE__) +#include "TargetConditionals.h" + #include +#include + +#ifdef TARGET_OS_MAC +// Other kinds of Mac OS class FileDialogWrapper { public: static std::string ShowOpenFileDialog(); + static std::string ShowOpenFolderDialog(); + static std::vector GetSubdirectoriesInFolder( + const std::string& folder_path); + static std::vector 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 GetSubdirectoriesInFolder( + const std::string& folder_path); + static std::vector GetFilesInFolder( + const std::string& folder_path); +}; + +#endif + #elif defined(__linux__) class FileDialogWrapper { @@ -62,4 +91,6 @@ class FileDialogWrapper { #else #error "Unsupported platform." -#endif \ No newline at end of file +#endif + +#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H diff --git a/src/app/core/platform/file_dialog.mm b/src/app/core/platform/file_dialog.mm index 91c3581d..a81a47cb 100644 --- a/src/app/core/platform/file_dialog.mm +++ b/src/app/core/platform/file_dialog.mm @@ -1,6 +1,49 @@ -#import + +#include +#include + #include "app/core/platform/file_dialog.h" +#if defined(__APPLE__) && defined(__MACH__) +/* Apple OSX and iOS (Darwin). */ +#include + +#import + +#if TARGET_IPHONE_SIMULATOR == 1 +/* iOS in Xcode simulator */ +std::string FileDialogWrapper::ShowOpenFileDialog() { return ""; } + +std::string FileDialogWrapper::ShowOpenFolderDialog() { return ""; } + +std::vector FileDialogWrapper::GetFilesInFolder(const std::string& folder) { + return {}; +} + +std::vector 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 FileDialogWrapper::GetFilesInFolder(const std::string& folder) { + return {}; +} + +std::vector FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) { + return {}; +} + +#elif TARGET_OS_MAC == 1 +/* macOS */ + +#import + 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 FileDialogWrapper::GetFilesInFolder(const std::string& folder) { + std::vector 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 FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) { + std::vector 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__ diff --git a/src/app/core/platform/file_path.h b/src/app/core/platform/file_path.h new file mode 100644 index 00000000..5cffc9de --- /dev/null +++ b/src/app/core/platform/file_path.h @@ -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 diff --git a/src/app/core/platform/file_path.mm b/src/app/core/platform/file_path.mm new file mode 100644 index 00000000..74dccef7 --- /dev/null +++ b/src/app/core/platform/file_path.mm @@ -0,0 +1,26 @@ +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +#include +#include + +#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 diff --git a/src/app/core/platform/font_loader.cc b/src/app/core/platform/font_loader.cc index a166aa55..0c87a33b 100644 --- a/src/app/core/platform/font_loader.cc +++ b/src/app/core/platform/font_loader.cc @@ -1,6 +1,6 @@ #include "app/core/platform/font_loader.h" -#include +#include "imgui/imgui.h" #include #include diff --git a/src/app/core/platform/font_loader.h b/src/app/core/platform/font_loader.h index c4686055..a0752dda 100644 --- a/src/app/core/platform/font_loader.h +++ b/src/app/core/platform/font_loader.h @@ -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 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 diff --git a/src/app/core/platform/font_loader.mm b/src/app/core/platform/font_loader.mm index 9dede77b..3b8e0a7d 100644 --- a/src/app/core/platform/font_loader.mm +++ b/src/app/core/platform/font_loader.mm @@ -1,11 +1,30 @@ // FontLoader.mm #include "app/core/platform/font_loader.h" -#import -#import -#include + +#include "imgui/imgui.h" #include "app/gui/icons.h" +#if defined(__APPLE__) && defined(__MACH__) +/* Apple OSX and iOS (Darwin). */ +#include + +#import + +#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 + +// MacOS Implementation void LoadSystemFonts() { // List of common macOS system fonts NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ]; @@ -47,4 +66,9 @@ void LoadSystemFonts() { CFRelease(fontURL); } } -} \ No newline at end of file +} +#else +// Unsupported platform +#endif + +#endif diff --git a/src/app/core/project.h b/src/app/core/project.h new file mode 100644 index 00000000..113716f3 --- /dev/null +++ b/src/app/core/project.h @@ -0,0 +1,138 @@ +#ifndef YAZE_APP_CORE_PROJECT_H +#define YAZE_APP_CORE_PROJECT_H + +#include "absl/strings/match.h" + +#include +#include +#include +#include + +#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 previous_rom_filenames_; +}; + +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_CORE_PROJECT_H diff --git a/src/app/core/testable.h b/src/app/core/testable.h new file mode 100644 index 00000000..1199f3de --- /dev/null +++ b/src/app/core/testable.h @@ -0,0 +1,19 @@ +#ifndef YAZE_APP_CORE_TESTABLE_H +#define YAZE_APP_CORE_TESTABLE_H + +#include + +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 \ No newline at end of file diff --git a/src/app/editor/CMakeLists.txt b/src/app/editor/CMakeLists.txt new file mode 100644 index 00000000..30b76767 --- /dev/null +++ b/src/app/editor/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/app/editor/code/assembly_editor.cc b/src/app/editor/code/assembly_editor.cc new file mode 100644 index 00000000..2f783b8e --- /dev/null +++ b/src/app/editor/code/assembly_editor.cc @@ -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 RemoveIgnoredFiles( + const std::vector& files, + const std::vector& ignored_files) { + std::vector 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 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(t)), + std::istreambuf_iterator()); + 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(t)), + std::istreambuf_iterator()); + 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 diff --git a/src/app/editor/code/assembly_editor.h b/src/app/editor/code/assembly_editor.h new file mode 100644 index 00000000..35022c99 --- /dev/null +++ b/src/app/editor/code/assembly_editor.h @@ -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 +#include +#include + +#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 files_; + std::vector open_files_; + ImVector 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 diff --git a/src/app/editor/code/memory_editor.h b/src/app/editor/code/memory_editor.h new file mode 100644 index 00000000..7b49cbea --- /dev/null +++ b/src/app/editor/code/memory_editor.h @@ -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 diff --git a/src/app/editor/context/entrance_context.h b/src/app/editor/context/entrance_context.h deleted file mode 100644 index 9a57ffe5..00000000 --- a/src/app/editor/context/entrance_context.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_ -#define YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_ - -#include -#include - -#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 entrance_tile_types_low_; - std::vector entrance_tile_types_high_; -}; - -} // namespace context -} // namespace editor -} // namespace app -} // namespace yaze - -#endif // YAZE_APP_EDITOR_CONTEXT_ENTRANCE_CONTEXT_H_ \ No newline at end of file diff --git a/src/app/editor/dungeon_editor.cc b/src/app/editor/dungeon/dungeon_editor.cc similarity index 69% rename from src/app/editor/dungeon_editor.cc rename to src/app/editor/dungeon/dungeon_editor.cc index bd3d2b1a..c23e5d47 100644 --- a/src/app/editor/dungeon_editor.cc +++ b/src/app/editor/dungeon/dungeon_editor.cc @@ -1,14 +1,14 @@ #include "dungeon_editor.h" -#include +#include "imgui/imgui.h" #include "app/core/common.h" #include "app/core/labeling.h" #include "app/gfx/snes_palette.h" #include "app/gui/canvas.h" +#include "app/gui/color.h" #include "app/gui/icons.h" #include "app/gui/input.h" -#include "app/gui/pipeline.h" #include "app/rom.h" #include "app/zelda3/dungeon/object_names.h" #include "app/zelda3/dungeon/room_names.h" @@ -18,15 +18,26 @@ namespace yaze { namespace app { namespace editor { -constexpr ImGuiTableFlags kDungeonObjectTableFlags = - ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | - ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | - ImGuiTableFlags_BordersV; - +using ImGui::BeginChild; +using ImGui::BeginTabBar; +using ImGui::BeginTabItem; +using ImGui::BeginTable; +using ImGui::Button; +using ImGui::EndChild; +using ImGui::EndTabBar; +using ImGui::EndTabItem; +using ImGui::RadioButton; +using ImGui::SameLine; using ImGui::TableHeadersRow; using ImGui::TableNextColumn; using ImGui::TableNextRow; using ImGui::TableSetupColumn; +using ImGui::Text; + +constexpr ImGuiTableFlags kDungeonObjectTableFlags = + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | + ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | + ImGuiTableFlags_BordersV; absl::Status DungeonEditor::Update() { if (!is_loaded_ && rom()->is_loaded()) { @@ -41,7 +52,7 @@ absl::Status DungeonEditor::Update() { TAB_BAR("##DungeonEditorTabBar") TAB_ITEM("Room Editor") - UpdateDungeonRoomView(); + status_ = UpdateDungeonRoomView(); END_TAB_ITEM() TAB_ITEM("Usage Statistics") if (is_loaded_) { @@ -82,7 +93,14 @@ absl::Status DungeonEditor::Initialize() { } LoadDungeonRoomSize(); - LoadRoomEntrances(); + // LoadRoomEntrances + for (int i = 0; i < 0x07; ++i) { + entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, true)); + } + + for (int i = 0; i < 0x85; ++i) { + entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, false)); + } // Load the palette group and palette for the dungeon full_palette_ = dungeon_man_pal_group[current_palette_group_id_]; @@ -92,7 +110,7 @@ absl::Status DungeonEditor::Initialize() { graphics_bin_ = *rom()->mutable_bitmap_manager(); // Create a vector of pointers to the current block bitmaps for (int block : rooms_[current_room_id_].blocks()) { - room_gfx_sheets_.emplace_back(graphics_bin_[block].get()); + room_gfx_sheets_.emplace_back(&graphics_bin_[block]); } return absl::OkStatus(); } @@ -100,16 +118,16 @@ absl::Status DungeonEditor::Initialize() { absl::Status DungeonEditor::RefreshGraphics() { for (int i = 0; i < 8; i++) { int block = rooms_[current_room_id_].blocks()[i]; - RETURN_IF_ERROR(graphics_bin_[block].get()->ApplyPaletteWithTransparent( + RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent( current_palette_group_[current_palette_id_], 0)); - rom()->UpdateBitmap(graphics_bin_[block].get(), true); + rom()->UpdateBitmap(&graphics_bin_[block], true); } auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1; for (int i = 9; i < 16; i++) { int block = rooms_[current_room_id_].blocks()[i]; - graphics_bin_[block].get()->ApplyPaletteWithTransparent( - sprites_aux1_pal_group[current_palette_id_], 0); - rom()->UpdateBitmap(graphics_bin_[block].get(), true); + RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent( + sprites_aux1_pal_group[current_palette_id_], 0)); + rom()->UpdateBitmap(&graphics_bin_[block], true); } return absl::OkStatus(); } @@ -168,8 +186,7 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() { ImGui::End(); } - if (ImGui::BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, - ImVec2(0, 0))) { + if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, ImVec2(0, 0))) { TableSetupColumn("Room Selector"); TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch, ImGui::GetContentRegionAvail().x); @@ -198,8 +215,8 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() { } void DungeonEditor::DrawToolset() { - if (ImGui::BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit, - ImVec2(0, 0))) { + if (BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit, + ImVec2(0, 0))) { TableSetupColumn("#undoTool"); TableSetupColumn("#redoTool"); TableSetupColumn("#separator"); @@ -215,47 +232,43 @@ void DungeonEditor::DrawToolset() { TableSetupColumn("#blockTool"); TableNextColumn(); - if (ImGui::Button(ICON_MD_UNDO)) { + if (Button(ICON_MD_UNDO)) { PRINT_IF_ERROR(Undo()); } TableNextColumn(); - if (ImGui::Button(ICON_MD_REDO)) { + if (Button(ICON_MD_REDO)) { PRINT_IF_ERROR(Redo()); } TableNextColumn(); - ImGui::Text(ICON_MD_MORE_VERT); + Text(ICON_MD_MORE_VERT); TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_FILTER_NONE, - background_type_ == kBackgroundAny)) { + if (RadioButton(ICON_MD_FILTER_NONE, background_type_ == kBackgroundAny)) { background_type_ = kBackgroundAny; } TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_FILTER_1, - background_type_ == kBackground1)) { + if (RadioButton(ICON_MD_FILTER_1, background_type_ == kBackground1)) { background_type_ = kBackground1; } TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_FILTER_2, - background_type_ == kBackground2)) { + if (RadioButton(ICON_MD_FILTER_2, background_type_ == kBackground2)) { background_type_ = kBackground2; } TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_FILTER_3, - background_type_ == kBackground3)) { + if (RadioButton(ICON_MD_FILTER_3, background_type_ == kBackground3)) { background_type_ = kBackground3; } TableNextColumn(); - ImGui::Text(ICON_MD_MORE_VERT); + Text(ICON_MD_MORE_VERT); TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) { + if (RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) { placement_type_ = kSprite; } if (ImGui::IsItemHovered()) { @@ -263,7 +276,7 @@ void DungeonEditor::DrawToolset() { } TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) { + if (RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) { placement_type_ = kItem; } if (ImGui::IsItemHovered()) { @@ -271,7 +284,7 @@ void DungeonEditor::DrawToolset() { } TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) { + if (RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) { placement_type_ = kDoor; } if (ImGui::IsItemHovered()) { @@ -279,7 +292,7 @@ void DungeonEditor::DrawToolset() { } TableNextColumn(); - if (ImGui::RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) { + if (RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) { placement_type_ = kBlock; } if (ImGui::IsItemHovered()) { @@ -287,7 +300,7 @@ void DungeonEditor::DrawToolset() { } TableNextColumn(); - if (ImGui::Button(ICON_MD_PALETTE)) { + if (Button(ICON_MD_PALETTE)) { palette_showing_ = !palette_showing_; } @@ -301,8 +314,8 @@ void DungeonEditor::DrawRoomSelector() { gui::InputHex("Palette ID", ¤t_palette_id_); if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9); - ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + BeginChild(child_id, ImGui::GetContentRegionAvail(), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar)) { int i = 0; for (const auto each_room_name : zelda3::dungeon::kRoomNames) { rom()->resource_label()->SelectableLabelWithNameEdit( @@ -318,86 +331,66 @@ void DungeonEditor::DrawRoomSelector() { i += 1; } } - ImGui::EndChild(); + EndChild(); } } +using ImGui::Separator; + void DungeonEditor::DrawEntranceSelector() { if (rom()->is_loaded()) { - gui::InputHexWord("Entrance ID", - &entrances_[current_entrance_id_].entrance_id_); + auto current_entrance = entrances_[current_entrance_id_]; + gui::InputHexWord("Entrance ID", ¤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, - ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + if (BeginChild("EntranceSelector", ImVec2(0, 0), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar)) { for (int i = 0; i < 0x85 + 7; i++) { rom()->resource_label()->SelectableLabelWithNameEdit( current_entrance_id_ == i, "Dungeon Entrance Names", @@ -412,15 +405,15 @@ void DungeonEditor::DrawEntranceSelector() { } } } - ImGui::EndChild(); + EndChild(); } } void DungeonEditor::DrawDungeonTabView() { static int next_tab_id = 0; - if (ImGui::BeginTabBar("MyTabBar", kDungeonTabBarFlags)) { - if (ImGui::TabItemButton("+", kDungeonTabFlags)) { + if (BeginTabBar("MyTabBar", kDungeonTabBarFlags)) { + if (ImGui::TabItemButton(ICON_MD_ADD, kDungeonTabFlags)) { if (std::find(active_rooms_.begin(), active_rooms_.end(), current_room_id_) != active_rooms_.end()) { // Room is already open @@ -438,11 +431,10 @@ void DungeonEditor::DrawDungeonTabView() { continue; } - if (ImGui::BeginTabItem( - zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open, - ImGuiTabItemFlags_None)) { + if (BeginTabItem(zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), + &open, ImGuiTabItemFlags_None)) { DrawDungeonCanvas(active_rooms_[n]); - ImGui::EndTabItem(); + EndTabItem(); } if (!open) @@ -451,33 +443,33 @@ void DungeonEditor::DrawDungeonTabView() { n++; } - ImGui::EndTabBar(); + EndTabBar(); } - ImGui::Separator(); + Separator(); } void DungeonEditor::DrawDungeonCanvas(int room_id) { ImGui::BeginGroup(); gui::InputHexByte("Layout", &rooms_[room_id].layout); - ImGui::SameLine(); + SameLine(); gui::InputHexByte("Blockset", &rooms_[room_id].blockset); - ImGui::SameLine(); + SameLine(); gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset); - ImGui::SameLine(); + SameLine(); gui::InputHexByte("Palette", &rooms_[room_id].palette); gui::InputHexByte("Floor1", &rooms_[room_id].floor1); - ImGui::SameLine(); + SameLine(); gui::InputHexByte("Floor2", &rooms_[room_id].floor2); - ImGui::SameLine(); + SameLine(); gui::InputHexWord("Message ID", &rooms_[room_id].message_id_); - ImGui::SameLine(); + SameLine(); ImGui::EndGroup(); @@ -492,7 +484,8 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { void DungeonEditor::DrawRoomGraphics() { const auto height = 0x40; - room_gfx_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x10 * 0x40 + 1)); + const int num_sheets = 0x10; + room_gfx_canvas_.DrawBackground(ImVec2(0x100 + 1, num_sheets * height + 1)); room_gfx_canvas_.DrawContextMenu(); room_gfx_canvas_.DrawTileSelector(32); if (is_loaded_) { @@ -505,7 +498,7 @@ void DungeonEditor::DrawRoomGraphics() { top_left_y = room_gfx_canvas_.zero_point().y + height * current_block; } room_gfx_canvas_.draw_list()->AddImage( - (void*)graphics_bin_[block].get()->texture(), + (void*)graphics_bin_[block].texture(), ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y), ImVec2(room_gfx_canvas_.zero_point().x + 0x100, room_gfx_canvas_.zero_point().y + offset)); @@ -517,34 +510,34 @@ void DungeonEditor::DrawRoomGraphics() { } void DungeonEditor::DrawTileSelector() { - if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) { - if (ImGui::BeginTabItem("Room Graphics")) { + if (BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) { + if (BeginTabItem("Room Graphics")) { if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3); - ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + BeginChild(child_id, ImGui::GetContentRegionAvail(), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar)) { DrawRoomGraphics(); } - ImGui::EndChild(); - ImGui::EndTabItem(); + EndChild(); + EndTabItem(); } - if (ImGui::BeginTabItem("Object Renderer")) { + if (BeginTabItem("Object Renderer")) { DrawObjectRenderer(); - ImGui::EndTabItem(); + EndTabItem(); } - ImGui::EndTabBar(); + EndTabBar(); } } void DungeonEditor::DrawObjectRenderer() { - if (ImGui::BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags, - ImVec2(0, 0))) { + if (BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags, + ImVec2(0, 0))) { TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch, ImGui::GetContentRegionAvail().x); TableSetupColumn("Canvas"); TableNextColumn(); - ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true); + BeginChild("DungeonObjectButtons", ImVec2(250, 0), true); int selected_object = 0; int i = 0; @@ -560,12 +553,11 @@ void DungeonEditor::DrawObjectRenderer() { i += 1; } - ImGui::EndChild(); + EndChild(); // Right side of the table - Canvas TableNextColumn(); - ImGui::BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), - true); + BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), true); object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1)); object_canvas_.DrawContextMenu(); @@ -576,7 +568,7 @@ void DungeonEditor::DrawObjectRenderer() { object_canvas_.DrawGrid(32.0f); object_canvas_.DrawOverlay(); - ImGui::EndChild(); + EndChild(); ImGui::EndTable(); } @@ -589,16 +581,6 @@ void DungeonEditor::DrawObjectRenderer() { } } -void DungeonEditor::LoadRoomEntrances() { - for (int i = 0; i < 0x07; ++i) { - entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, true)); - } - - for (int i = 0; i < 0x85; ++i) { - entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, false)); - } -} - // ============================================================================ void DungeonEditor::CalculateUsageStats() { @@ -663,16 +645,16 @@ void RenderUnusedSets(const absl::flat_hash_map& usage_map, int max_set, } for (const auto& set : unused_sets) { if (spriteset_offset != 0x00) { - ImGui::Text("%#02x, %#02x", set, (set + spriteset_offset)); + Text("%#02x, %#02x", set, (set + spriteset_offset)); } else { - ImGui::Text("%#02x", set); + Text("%#02x", set); } } } } // namespace void DungeonEditor::DrawUsageStats() { - if (ImGui::Button("Refresh")) { + if (Button("Refresh")) { selected_blockset_ = 0xFFFF; selected_spriteset_ = 0xFFFF; selected_palette_ = 0xFFFF; @@ -684,9 +666,9 @@ void DungeonEditor::DrawUsageStats() { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - if (ImGui::BeginTable("DungeonUsageStatsTable", 8, - kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit, - ImGui::GetContentRegionAvail())) { + if (BeginTable("DungeonUsageStatsTable", 8, + kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit, + ImGui::GetContentRegionAvail())) { TableSetupColumn("Blockset Usage"); TableSetupColumn("Unused Blockset"); TableSetupColumn("Palette Usage"); @@ -699,51 +681,50 @@ void DungeonEditor::DrawUsageStats() { ImGui::PopStyleVar(2); TableNextColumn(); - ImGui::BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); + BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); RenderSetUsage(blockset_usage_, selected_blockset_); - ImGui::EndChild(); + EndChild(); TableNextColumn(); - ImGui::BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); + BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); RenderUnusedSets(blockset_usage_, 0x25); - ImGui::EndChild(); + EndChild(); TableNextColumn(); - ImGui::BeginChild("PaletteUsageScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); + BeginChild("PaletteUsageScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); RenderSetUsage(palette_usage_, selected_palette_); - ImGui::EndChild(); + EndChild(); TableNextColumn(); - ImGui::BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); + BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); RenderUnusedSets(palette_usage_, 0x48); - ImGui::EndChild(); + EndChild(); TableNextColumn(); - ImGui::BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); + BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); RenderSetUsage(spriteset_usage_, selected_spriteset_, 0x40); - ImGui::EndChild(); + EndChild(); TableNextColumn(); - ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); + BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); RenderUnusedSets(spriteset_usage_, 0x90, 0x40); - ImGui::EndChild(); + EndChild(); TableNextColumn(); - ImGui::BeginChild("UsageGrid", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); - ImGui::Text("%s", - absl::StrFormat("Total size of all rooms: %d hex format: %#06x", - total_room_size_, total_room_size_) - .c_str()); + BeginChild("UsageGrid", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar); + Text("%s", absl::StrFormat("Total size of all rooms: %d hex format: %#06x", + total_room_size_, total_room_size_) + .c_str()); DrawUsageGrid(); - ImGui::EndChild(); + EndChild(); TableNextColumn(); if (selected_blockset_ < 0x25) { @@ -801,10 +782,10 @@ void DungeonEditor::DrawUsageGrid() { ImGuiCol_Button, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color } - if (ImGui::Button(absl::StrFormat( - "%#x", rooms_[row * squaresWide + col].room_size()) - .c_str(), - ImVec2(55, 30))) { + if (Button(absl::StrFormat("%#x", + rooms_[row * squaresWide + col].room_size()) + .c_str(), + ImVec2(55, 30))) { // Switch over to the room editor tab // and add a room tab by the ID of the square // that was clicked @@ -826,20 +807,20 @@ void DungeonEditor::DrawUsageGrid() { if (ImGui::IsItemHovered()) { // Display a tooltip with all the room properties ImGui::BeginTooltip(); - ImGui::Text("Room ID: %d", row * squaresWide + col); - ImGui::Text("Blockset: %#02x", room.blockset); - ImGui::Text("Spriteset: %#02x", room.spriteset); - ImGui::Text("Palette: %#02x", room.palette); - ImGui::Text("Floor1: %#02x", room.floor1); - ImGui::Text("Floor2: %#02x", room.floor2); - ImGui::Text("Message ID: %#04x", room.message_id_); - ImGui::Text("Size: %#06x", room.room_size()); - ImGui::Text("Size Pointer: %#06x", room.room_size_ptr()); + Text("Room ID: %d", row * squaresWide + col); + Text("Blockset: %#02x", room.blockset); + Text("Spriteset: %#02x", room.spriteset); + Text("Palette: %#02x", room.palette); + Text("Floor1: %#02x", room.floor1); + Text("Floor2: %#02x", room.floor2); + Text("Message ID: %#04x", room.message_id_); + Text("Size: %#016llx", room.room_size()); + Text("Size Pointer: %#016llx", room.room_size_ptr()); ImGui::EndTooltip(); } // Keep squares in the same line - ImGui::SameLine(); + SameLine(); } } } diff --git a/src/app/editor/dungeon_editor.h b/src/app/editor/dungeon/dungeon_editor.h similarity index 86% rename from src/app/editor/dungeon_editor.h rename to src/app/editor/dungeon/dungeon_editor.h index a2c748d0..fdf52529 100644 --- a/src/app/editor/dungeon_editor.h +++ b/src/app/editor/dungeon/dungeon_editor.h @@ -1,13 +1,13 @@ #ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H #define YAZE_APP_EDITOR_DUNGEONEDITOR_H -#include +#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 room_size_addresses_; std::unordered_map room_palette_; + + absl::Status status_; }; } // namespace editor diff --git a/src/app/editor/graphics/gfx_group_editor.cc b/src/app/editor/graphics/gfx_group_editor.cc new file mode 100644 index 00000000..c5905cf2 --- /dev/null +++ b/src/app/editor/graphics/gfx_group_editor.cc @@ -0,0 +1,311 @@ +#include "gfx_group_editor.h" + +#include "imgui/imgui.h" + +#include + +#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 diff --git a/src/app/editor/modules/gfx_group_editor.h b/src/app/editor/graphics/gfx_group_editor.h similarity index 86% rename from src/app/editor/modules/gfx_group_editor.h rename to src/app/editor/graphics/gfx_group_editor.h index 7c575b6d..fb4d8d40 100644 --- a/src/app/editor/modules/gfx_group_editor.h +++ b/src/app/editor/graphics/gfx_group_editor.h @@ -1,21 +1,19 @@ #ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H #define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H -#include +#include "imgui/imgui.h" #include #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 tile16_individual_data_; std::vector tile16_individual_; diff --git a/src/app/editor/graphics_editor.cc b/src/app/editor/graphics/graphics_editor.cc similarity index 85% rename from src/app/editor/graphics_editor.cc rename to src/app/editor/graphics/graphics_editor.cc index fba8bb4e..fe238599 100644 --- a/src/app/editor/graphics_editor.cc +++ b/src/app/editor/graphics/graphics_editor.cc @@ -1,22 +1,23 @@ -#include "app/editor/graphics_editor.h" +#include "graphics_editor.h" -#include -#include -#include -#include +#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 png_data = - rom()->bitmap_manager().shared_bitmap(current_sheet_)->GetPngData(); + rom()->bitmap_manager().shared_bitmap(current_sheet_).GetPngData(); CopyImageToClipboard(png_data); } HOVER_HINT("Copy to Clipboard"); @@ -124,10 +142,8 @@ void GraphicsEditor::DrawGfxEditToolset() { ->mutable_bitmap_manager() ->mutable_bitmap(current_sheet_) ->Create(width, height, 8, png_data); - rom()->UpdateBitmap(rom() - ->mutable_bitmap_manager() - ->mutable_bitmap(current_sheet_) - .get()); + rom()->UpdateBitmap( + rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_)); } } HOVER_HINT("Paste from Clipboard"); @@ -148,7 +164,7 @@ void GraphicsEditor::DrawGfxEditToolset() { TableNextColumn(); auto bitmap = rom()->bitmap_manager()[current_sheet_]; - auto palette = bitmap->palette(); + auto palette = bitmap.palette(); for (int i = 0; i < 8; i++) { ImGui::SameLine(); auto color = @@ -161,7 +177,7 @@ void GraphicsEditor::DrawGfxEditToolset() { } TableNextColumn(); - gui::InputHexByte("Tile Size", &tile_size_, 0x01); + gui::InputHexByte("Tile Size", &tile_size_); ImGui::EndTable(); } @@ -172,30 +188,38 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() { "##GfxEditChild", ImVec2(0, 0), true, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + // TODO: Update the interaction for multi select on sheets + static ImGuiSelectionBasicStorage selection; + ImGuiMultiSelectFlags flags = + ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect( + flags, selection.Size, rom()->bitmap_manager().size()); + selection.ApplyRequests(ms_io); + ImGuiListClipper clipper; + clipper.Begin(rom()->bitmap_manager().size()); + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex( + (int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + for (auto& [key, value] : rom()->bitmap_manager()) { ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(), ImVec2(0x100 + 1, 0x40 + 1), true, ImGuiWindowFlags_NoDecoration); ImGui::PopStyleVar(); gui::Canvas graphics_bin_canvas_; - // auto select_tile_event = [&]() { - // }; - // graphics_bin_canvas_.UpdateEvent( - // select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_, - // /*grid_size=*/16.0f); graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1)); graphics_bin_canvas_.DrawContextMenu(); - if (value.get()->is_active()) { - auto texture = value.get()->texture(); + if (value.is_active()) { + auto texture = value.texture(); graphics_bin_canvas_.draw_list()->AddImage( (void*)texture, ImVec2(graphics_bin_canvas_.zero_point().x + 2, graphics_bin_canvas_.zero_point().y + 2), ImVec2(graphics_bin_canvas_.zero_point().x + - value.get()->width() * sheet_scale_, + value.width() * sheet_scale_, graphics_bin_canvas_.zero_point().y + - value.get()->height() * sheet_scale_)); + value.height() * sheet_scale_)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { current_sheet_ = key; @@ -224,6 +248,8 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() { ImGui::EndChild(); } ImGui::PopStyleVar(); + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); ImGui::EndChild(); return absl::OkStatus(); } @@ -232,8 +258,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { static int next_tab_id = 0; if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) { - if (ImGui::TabItemButton( - "+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) { + if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing | + ImGuiTabItemFlags_NoTooltip)) { open_sheets_.insert(next_tab_id++); } @@ -269,7 +295,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { }; current_sheet_canvas_.UpdateColorPainter( - *rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event, + rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event, tile_size_, current_scale_); ImGui::EndChild(); @@ -302,7 +328,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { current_sheet_ = id; // ImVec2(0x100, 0x40), current_sheet_canvas_.UpdateColorPainter( - *rom()->bitmap_manager()[id], current_color_, + rom()->bitmap_manager()[id], current_color_, [&]() { }, @@ -322,12 +348,10 @@ absl::Status GraphicsEditor::UpdateGfxTabView() { } absl::Status GraphicsEditor::UpdatePaletteColumn() { - auto palette_group = *rom()->palette_group().get_group( - kPaletteGroupAddressesKeys[edit_palette_group_name_index_]); - - auto palette = palette_group.palette(edit_palette_index_); - if (rom()->is_loaded()) { + auto palette_group = *rom()->palette_group().get_group( + kPaletteGroupAddressesKeys[edit_palette_group_name_index_]); + auto palette = palette_group.palette(edit_palette_index_); gui::TextWithSeparators("ROM Palette"); ImGui::SetNextItemWidth(100.f); ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_, @@ -335,21 +359,20 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() { IM_ARRAYSIZE(kPaletteGroupAddressesKeys)); ImGui::SetNextItemWidth(100.f); gui::InputHex("Palette Group Index", &edit_palette_index_); + + gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_, + palette); + + if (refresh_graphics_ && !open_sheets_.empty()) { + RETURN_IF_ERROR( + rom()->bitmap_manager()[current_sheet_].ApplyPaletteWithTransparent( + palette, edit_palette_sub_index_)); + rom()->UpdateBitmap( + rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_), + true); + refresh_graphics_ = false; + } } - - gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_, - palette); - - if (refresh_graphics_ && !open_sheets_.empty()) { - RETURN_IF_ERROR( - rom()->bitmap_manager()[current_sheet_]->ApplyPaletteWithTransparent( - palette, edit_palette_sub_index_)); - rom()->UpdateBitmap( - rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get(), - true); - refresh_graphics_ = false; - } - return absl::OkStatus(); } @@ -366,10 +389,12 @@ absl::Status GraphicsEditor::UpdateLinkGfxView() { NEXT_COLUMN(); link_canvas_.DrawBackground(); link_canvas_.DrawGrid(16.0f); + int i = 0; - for (auto [key, link_sheet] : rom()->link_graphics()) { + for (auto [key, link_sheet] : *rom()->mutable_link_graphics()) { int x_offset = 0; int y_offset = core::kTilesheetHeight * i * 4; + link_canvas_.DrawContextMenu(&link_sheet); link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4); i++; } @@ -435,7 +460,7 @@ absl::Status GraphicsEditor::UpdateScadView() { if (super_donkey_) { if (refresh_graphics_) { for (int i = 0; i < graphics_bin_.size(); i++) { - graphics_bin_[i].ApplyPalette( + status_ = graphics_bin_[i].ApplyPalette( col_file_palette_group_[current_palette_index_]); rom()->UpdateBitmap(&graphics_bin_[i]); } @@ -498,19 +523,21 @@ absl::Status GraphicsEditor::DrawCgxImport() { is_open_ = true; cgx_loaded_ = true; }); - gui::ButtonPipe("Copy CGX Path", - [this]() { ImGui::SetClipboardText(cgx_file_path_); }); - gui::ButtonPipe("Load CGX Data", [this]() { + if (ImGui::Button("Copy CGX Path")) { + ImGui::SetClipboardText(cgx_file_path_); + } + + if (ImGui::Button("Load CGX Data")) { status_ = gfx::scad_format::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_, decoded_cgx_, extra_cgx_data_); - cgx_bitmap_.InitializeFromData(0x80, 0x200, 8, decoded_cgx_); + cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_); if (col_file_) { cgx_bitmap_.ApplyPalette(decoded_col_); rom()->RenderBitmap(&cgx_bitmap_); } - }); + } return absl::OkStatus(); } @@ -532,7 +559,7 @@ absl::Status GraphicsEditor::DrawScrImport() { InputInt("SCR Mod", &scr_mod_value_); - gui::ButtonPipe("Load Scr Data", [this]() { + if (ImGui::Button("Load Scr Data")) { status_ = gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_); @@ -540,12 +567,12 @@ absl::Status GraphicsEditor::DrawScrImport() { status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_, decoded_cgx_); - scr_bitmap_.InitializeFromData(0x100, 0x100, 8, decoded_scr_data_); + scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_); if (scr_loaded_) { scr_bitmap_.ApplyPalette(decoded_col_); rom()->RenderBitmap(&scr_bitmap_); } - }); + } return absl::OkStatus(); } @@ -582,8 +609,9 @@ absl::Status GraphicsEditor::DrawPaletteControls() { is_open_ = true; }); - gui::ButtonPipe("Copy COL Path", - [this]() { ImGui::SetClipboardText(col_file_path_); }); + if (ImGui::Button("Copy Col Path")) { + ImGui::SetClipboardText(col_file_path_); + } if (rom()->is_loaded()) { gui::TextWithSeparators("ROM Palette"); @@ -655,8 +683,9 @@ absl::Status GraphicsEditor::DrawFileImport() { is_open_ = true; }); - gui::ButtonPipe("Copy File Path", - [this]() { ImGui::SetClipboardText(file_path_); }); + if (Button("Copy File Path")) { + ImGui::SetClipboardText(file_path_); + } gui::InputHex("BIN Offset", ¤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]); @@ -812,4 +841,4 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() { } // namespace editor } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/editor/graphics_editor.h b/src/app/editor/graphics/graphics_editor.h similarity index 85% rename from src/app/editor/graphics_editor.h rename to src/app/editor/graphics/graphics_editor.h index 48502ad7..99974792 100644 --- a/src/app/editor/graphics_editor.h +++ b/src/app/editor/graphics/graphics_editor.h @@ -1,19 +1,20 @@ #ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H #define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H -#include -#include -#include -#include +#include "ImGuiFileDialog/ImGuiFileDialog.h" +#include "imgui/imgui.h" +#include "imgui/misc/cpp/imgui_stdlib.h" +#include "imgui_memory_editor.h" #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "app/editor/modules/palette_editor.h" +#include "app/editor/graphics/palette_editor.h" +#include "app/editor/utils/editor.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_tile.h" +#include "app/gui/asset_browser.h" #include "app/gui/canvas.h" #include "app/gui/input.h" -#include "app/gui/pipeline.h" #include "app/rom.h" #include "app/zelda3/overworld/overworld.h" @@ -43,22 +44,6 @@ const std::string kSuperDonkeySprites[] = { "BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D", "C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"}; -constexpr const char* kPaletteGroupAddressesKeys[] = { - "ow_main", "ow_aux", "ow_animated", "hud", - "global_sprites", "armors", "swords", "shields", - "sprites_aux1", "sprites_aux2", "sprites_aux3", "dungeon_main", - "grass", "3d_object", "ow_mini_map", -}; - -static constexpr absl::string_view kGfxToolsetColumnNames[] = { - "#memoryEditor", - "##separator_gfx1", -}; - -constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable | - ImGuiTableFlags_Resizable | - ImGuiTableFlags_SizingStretchSame; - /** * @class GraphicsEditor * @brief Allows the user to edit graphics sheets from the game or view @@ -73,9 +58,18 @@ constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable | * drawing toolsets, palette controls, clipboard imports, experimental features, * and memory editor. */ -class GraphicsEditor : public SharedRom { +class GraphicsEditor : public SharedRom, public Editor { public: - absl::Status Update(); + GraphicsEditor() { type_ = EditorType::kGraphics; } + + absl::Status Update() override; + + absl::Status Undo() override { return absl::UnimplementedError("Undo"); } + absl::Status Redo() override { return absl::UnimplementedError("Redo"); } + absl::Status Cut() override { return absl::UnimplementedError("Cut"); } + absl::Status Copy() override { return absl::UnimplementedError("Copy"); } + absl::Status Paste() override { return absl::UnimplementedError("Paste"); } + absl::Status Find() override { return absl::UnimplementedError("Find"); } private: enum class GfxEditMode { @@ -163,6 +157,8 @@ class GraphicsEditor : public SharedRom { char tilemap_file_path_[256] = ""; char tilemap_file_name_[256] = ""; + gui::GfxSheetAssetBrowser asset_browser_; + GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect; Rom temp_rom_; @@ -193,9 +189,10 @@ class GraphicsEditor : public SharedRom { gui::Canvas import_canvas_; gui::Canvas scr_canvas_; gui::Canvas super_donkey_canvas_; - gui::Canvas current_sheet_canvas_{ImVec2(0x80, 0x20), + gui::Canvas current_sheet_canvas_{"CurrentSheetCanvas", ImVec2(0x80, 0x20), gui::CanvasGridSize::k8x8}; gui::Canvas link_canvas_{ + "LinkCanvas", ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4), gui::CanvasGridSize::k16x16}; absl::Status status_; @@ -205,4 +202,4 @@ class GraphicsEditor : public SharedRom { } // namespace app } // namespace yaze -#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H \ No newline at end of file +#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H diff --git a/src/app/editor/graphics/palette_editor.cc b/src/app/editor/graphics/palette_editor.cc new file mode 100644 index 00000000..8f3a315f --- /dev/null +++ b/src/app/editor/graphics/palette_editor.cc @@ -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 diff --git a/src/app/editor/modules/palette_editor.h b/src/app/editor/graphics/palette_editor.h similarity index 65% rename from src/app/editor/modules/palette_editor.h rename to src/app/editor/graphics/palette_editor.h index cbc5aec3..9ee54cb8 100644 --- a/src/app/editor/modules/palette_editor.h +++ b/src/app/editor/graphics/palette_editor.h @@ -1,9 +1,11 @@ #ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H #define YAZE_APP_EDITOR_PALETTE_EDITOR_H -#include +#include "imgui/imgui.h" #include "absl/status/status.h" +#include "app/editor/graphics/gfx_group_editor.h" +#include "app/editor/utils/editor.h" #include "app/gfx/snes_palette.h" #include "app/gui/canvas.h" #include "app/gui/icons.h" @@ -13,18 +15,6 @@ namespace yaze { namespace app { namespace editor { -constexpr int kNumPalettes = 11; - -static constexpr absl::string_view kPaletteCategoryNames[] = { - "Sword", "Shield", "Clothes", "World Colors", - "Area Colors", "Enemies", "Dungeons", "World Map", - "Dungeon Map", "Triforce", "Crystal"}; - -static constexpr absl::string_view kPaletteGroupNames[] = { - "swords", "shields", "armors", "ow_main", - "ow_aux", "global_sprites", "dungeon_main", "ow_mini_map", - "ow_mini_map", "3d_object", "3d_object"}; - namespace palette_internal { struct PaletteChange { std::string group_name; @@ -56,9 +46,9 @@ class PaletteEditorHistory { } // Restore the original color - gfx::SnesColor GetOriginalColor(const std::string& groupName, - size_t paletteIndex, - size_t colorIndex) const { + gfx::SnesColor RestoreOriginalColor(const std::string& groupName, + size_t paletteIndex, + size_t colorIndex) const { for (const auto& change : recentChanges) { if (change.group_name == groupName && change.palette_index == paletteIndex && @@ -71,6 +61,15 @@ class PaletteEditorHistory { return gfx::SnesColor(); } + auto size() const { return recentChanges.size(); } + + gfx::SnesColor& GetModifiedColor(size_t index) { + return recentChanges[index].new_color; + } + gfx::SnesColor& GetOriginalColor(size_t index) { + return recentChanges[index].original_color; + } + private: std::deque 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 custom_palette_; + + ImVec4 saved_palette_[256] = {}; + + palette_internal::PaletteEditorHistory history_; }; } // namespace editor diff --git a/src/app/editor/screen_editor.cc b/src/app/editor/graphics/screen_editor.cc similarity index 95% rename from src/app/editor/screen_editor.cc rename to src/app/editor/graphics/screen_editor.cc index e4cac7c2..9680d968 100644 --- a/src/app/editor/screen_editor.cc +++ b/src/app/editor/graphics/screen_editor.cc @@ -1,6 +1,6 @@ -#include "app/editor/screen_editor.h" +#include "screen_editor.h" -#include +#include "imgui/imgui.h" #include #include @@ -25,9 +25,7 @@ namespace yaze { namespace app { namespace editor { -ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); } - -void ScreenEditor::Update() { +absl::Status ScreenEditor::Update() { TAB_BAR("##TabBar") TAB_ITEM("Dungeon Maps") if (rom()->is_loaded()) { @@ -39,6 +37,8 @@ void ScreenEditor::Update() { DrawTitleScreenEditor(); DrawNamingScreenEditor(); END_TAB_BAR() + + return absl::OkStatus(); } void ScreenEditor::DrawInventoryMenuEditor() { @@ -46,7 +46,7 @@ void ScreenEditor::DrawInventoryMenuEditor() { static bool create = false; if (!create && rom()->is_loaded()) { - inventory_.Create(); + status_ = inventory_.Create(); palette_ = inventory_.Palette(); create = true; } @@ -74,7 +74,7 @@ void ScreenEditor::DrawInventoryMenuEditor() { tilesheet_canvas_.DrawOverlay(); ImGui::TableNextColumn(); - gui::DisplayPalette(palette_, create); + status_ = gui::DisplayPalette(palette_, create); ImGui::EndTable(); } @@ -233,8 +233,8 @@ absl::Status ScreenEditor::LoadDungeonMapTile16() { tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4); } - tile16_sheet_.mutable_bitmap()->ApplyPalette( - *rom()->mutable_dungeon_palette(3)); + RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette( + *rom()->mutable_dungeon_palette(3))); rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get()); for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) { @@ -293,13 +293,7 @@ void ScreenEditor::DrawDungeonMapsTabs() { std::string label = dungeon_map_labels_[selected_dungeon][floor_number][j]; screen_canvas_.DrawText(label, (posX * 2), (posY * 2)); - // GFX.drawText( - // e.Graphics, 16 + ((i % 5) * 32), 20 + ((i / 5) * 32), } - - // if (dungmapSelectedTile == i) - // Constants.AzurePen2, - // 10 + ((i % 5) * 32), 12 + ((i / 5) * 32), 32, 32)); } screen_canvas_.DrawGrid(64.f, 5); @@ -369,6 +363,9 @@ void ScreenEditor::DrawDungeonMapsEditor() { rom()->resource_label()->SelectableLabelWithNameEdit( selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i), dungeon_names[i]); + if (ImGui::IsItemClicked()) { + selected_dungeon = i; + } } // Map column @@ -437,4 +434,4 @@ void ScreenEditor::DrawToolset() { } // namespace editor } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/editor/screen_editor.h b/src/app/editor/graphics/screen_editor.h similarity index 74% rename from src/app/editor/screen_editor.h rename to src/app/editor/graphics/screen_editor.h index 286c9230..9faddd0c 100644 --- a/src/app/editor/screen_editor.h +++ b/src/app/editor/graphics/screen_editor.h @@ -1,12 +1,13 @@ #ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H #define YAZE_APP_EDITOR_SCREEN_EDITOR_H -#include +#include "imgui/imgui.h" #include #include "absl/status/status.h" #include "app/core/constants.h" +#include "app/editor/utils/editor.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" #include "app/gfx/snes_tile.h" @@ -36,10 +37,21 @@ namespace editor { * * The class inherits from the SharedRom class. */ -class ScreenEditor : public SharedRom { +class ScreenEditor : public SharedRom, public Editor { public: - ScreenEditor(); - void Update(); + ScreenEditor() { + screen_canvas_.SetCanvasSize(ImVec2(512, 512)); + type_ = EditorType::kScreen; + } + + absl::Status Update() override; + + absl::Status Undo() override { return absl::UnimplementedError("Undo"); } + absl::Status Redo() override { return absl::UnimplementedError("Redo"); } + absl::Status Cut() override { return absl::UnimplementedError("Cut"); } + absl::Status Copy() override { return absl::UnimplementedError("Copy"); } + absl::Status Paste() override { return absl::UnimplementedError("Paste"); } + absl::Status Find() override { return absl::UnimplementedError("Find"); } absl::Status SaveDungeonMaps(); @@ -83,10 +95,12 @@ class ScreenEditor : public SharedRom { gfx::BitmapTable sheets_; gfx::Tilesheet tile16_sheet_; + + absl::Status status_; }; } // namespace editor } // namespace app } // namespace yaze -#endif \ No newline at end of file +#endif diff --git a/src/app/editor/modules/tile16_editor.cc b/src/app/editor/graphics/tile16_editor.cc similarity index 74% rename from src/app/editor/modules/tile16_editor.cc rename to src/app/editor/graphics/tile16_editor.cc index 38ab5c39..dc52f8ac 100644 --- a/src/app/editor/modules/tile16_editor.cc +++ b/src/app/editor/graphics/tile16_editor.cc @@ -1,12 +1,13 @@ #include "tile16_editor.h" -#include +#include "ImGuiFileDialog/ImGuiFileDialog.h" +#include "imgui/imgui.h" #include #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "app/editor/modules/palette_editor.h" +#include "app/editor/graphics/palette_editor.h" #include "app/editor/utils/editor.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" @@ -15,9 +16,7 @@ #include "app/gui/canvas.h" #include "app/gui/icons.h" #include "app/gui/input.h" -#include "app/gui/pipeline.h" #include "app/gui/style.h" -#include "app/gui/widgets.h" #include "app/rom.h" #include "app/zelda3/overworld/overworld.h" @@ -26,50 +25,73 @@ namespace app { namespace editor { using ImGui::BeginChild; +using ImGui::BeginMenu; +using ImGui::BeginMenuBar; using ImGui::BeginTabBar; using ImGui::BeginTabItem; using ImGui::BeginTable; +using ImGui::Button; +using ImGui::Checkbox; using ImGui::Combo; using ImGui::EndChild; +using ImGui::EndMenu; +using ImGui::EndMenuBar; using ImGui::EndTabBar; using ImGui::EndTabItem; +using ImGui::EndTable; +using ImGui::GetContentRegionAvail; +using ImGui::Separator; using ImGui::TableHeadersRow; using ImGui::TableNextColumn; using ImGui::TableNextRow; using ImGui::TableSetupColumn; +using ImGui::Text; + +absl::Status Tile16Editor::InitBlockset( + gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp, + const std::vector& 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 tile16_names; + for (int i = 0; i < 0x200; ++i) { + std::string str = core::UppercaseHexByte(all_tiles_types_[i]); + tile16_names.push_back(str); + } + + *tile8_source_canvas_.mutable_labels(0) = tile16_names; + *tile8_source_canvas_.custom_labels_enabled() = true; + return absl::OkStatus(); +} absl::Status Tile16Editor::Update() { - if (rom()->is_loaded() && !map_blockset_loaded_) { - RETURN_IF_ERROR(LoadTile8()); - ImVector tile16_names; - for (int i = 0; i < 0x200; ++i) { - std::string str = core::UppercaseHexByte(all_tiles_types_[i]); - tile16_names.push_back(str); - } - - *tile8_source_canvas_.mutable_labels(0) = tile16_names; - *tile8_source_canvas_.custom_labels_enabled() = true; + if (!map_blockset_loaded_) { + return absl::InvalidArgumentError("Blockset not initialized, open a ROM."); } RETURN_IF_ERROR(DrawMenu()); if (BeginTabBar("Tile16 Editor Tabs")) { RETURN_IF_ERROR(DrawTile16Editor()); RETURN_IF_ERROR(UpdateTile16Transfer()); - ImGui::EndTabBar(); + EndTabBar(); } return absl::OkStatus(); } absl::Status Tile16Editor::DrawMenu() { - if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu("View")) { - ImGui::Checkbox("Show Collision Types", - tile8_source_canvas_.custom_labels_enabled()); - ImGui::EndMenu(); + if (BeginMenuBar()) { + if (BeginMenu("View")) { + Checkbox("Show Collision Types", + tile8_source_canvas_.custom_labels_enabled()); + EndMenu(); } - ImGui::EndMenuBar(); + EndMenuBar(); } return absl::OkStatus(); @@ -80,9 +102,9 @@ absl::Status Tile16Editor::DrawTile16Editor() { if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE, ImVec2(0, 0))) { TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed, - ImGui::GetContentRegionAvail().x); + GetContentRegionAvail().x); TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch, - ImGui::GetContentRegionAvail().x); + GetContentRegionAvail().x); TableHeadersRow(); TableNextRow(); TableNextColumn(); @@ -92,10 +114,10 @@ absl::Status Tile16Editor::DrawTile16Editor() { RETURN_IF_ERROR(UpdateTile16Edit()); RETURN_IF_ERROR(DrawTileEditControls()); - ImGui::EndTable(); + EndTable(); } - ImGui::EndTabItem(); + EndTabItem(); } return absl::OkStatus(); } @@ -108,26 +130,23 @@ absl::Status Tile16Editor::UpdateBlockset() { { blockset_canvas_.DrawContextMenu(); blockset_canvas_.DrawTileSelector(32); - blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_); + blockset_canvas_.DrawBitmap(*tile16_blockset_bmp_, 0, map_blockset_loaded_); blockset_canvas_.DrawGrid(); blockset_canvas_.DrawOverlay(); - ImGui::EndChild(); + EndChild(); } if (!blockset_canvas_.points().empty()) { - uint16_t x = blockset_canvas_.points().front().x / 32; - uint16_t y = blockset_canvas_.points().front().y / 32; - - // notify_tile16.mutable_get() = x + (y * 8); notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos(); notify_tile16.apply_changes(); + if (notify_tile16.modified()) { current_tile16_ = notify_tile16.get(); - current_tile16_bmp_ = tile16_individual_[notify_tile16]; + current_tile16_bmp_ = &tile16_individual_[notify_tile16]; auto ow_main_pal_group = rom()->palette_group().overworld_main; - RETURN_IF_ERROR(current_tile16_bmp_.ApplyPalette( + RETURN_IF_ERROR(current_tile16_bmp_->ApplyPalette( ow_main_pal_group[current_palette_])); - rom()->RenderBitmap(¤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,9 +189,8 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) { absl::Status Tile16Editor::UpdateTile16Edit() { auto ow_main_pal_group = rom()->palette_group().overworld_main; - if (ImGui::BeginChild("Tile8 Selector", - ImVec2(ImGui::GetContentRegionAvail().x, 0x175), - true)) { + if (BeginChild("Tile8 Selector", ImVec2(GetContentRegionAvail().x, 0x175), + true)) { tile8_source_canvas_.DrawBackground(); tile8_source_canvas_.DrawContextMenu(¤t_gfx_bmp_); if (tile8_source_canvas_.DrawTileSelector(32)) { @@ -185,7 +203,7 @@ absl::Status Tile16Editor::UpdateTile16Edit() { tile8_source_canvas_.DrawGrid(); tile8_source_canvas_.DrawOverlay(); } - ImGui::EndChild(); + EndChild(); // The user selected a tile8 if (!tile8_source_canvas_.points().empty()) { @@ -199,31 +217,31 @@ absl::Status Tile16Editor::UpdateTile16Edit() { rom()->UpdateBitmap(¤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,9 +371,9 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() { palette_ = transfer_overworld_.AreaPalette(); // Create the tile16 blockset image - RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x80, - transfer_overworld_.Tile16Blockset(), - transfer_blockset_bmp_, palette_)); + RETURN_IF_ERROR(rom()->CreateAndRenderBitmap( + 0x80, 0x2000, 0x80, transfer_overworld_.Tile16Blockset(), + transfer_blockset_bmp_, palette_)); transfer_blockset_loaded_ = true; } @@ -367,6 +385,16 @@ absl::Status Tile16Editor::UpdateTransferTileCanvas() { return absl::OkStatus(); } +absl::Status Tile16Editor::SetCurrentTile(int id) { + current_tile16_ = id; + current_tile16_bmp_ = &tile16_individual_[id]; + auto ow_main_pal_group = rom()->palette_group().overworld_main; + RETURN_IF_ERROR( + current_tile16_bmp_->ApplyPalette(ow_main_pal_group[current_palette_])); + rom()->RenderBitmap(current_tile16_bmp_); + return absl::OkStatus(); +} + } // namespace editor } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/editor/modules/tile16_editor.h b/src/app/editor/graphics/tile16_editor.h similarity index 64% rename from src/app/editor/modules/tile16_editor.h rename to src/app/editor/graphics/tile16_editor.h index f554b8e1..d3eaaec3 100644 --- a/src/app/editor/modules/tile16_editor.h +++ b/src/app/editor/graphics/tile16_editor.h @@ -1,22 +1,21 @@ #ifndef YAZE_APP_EDITOR_TILE16EDITOR_H #define YAZE_APP_EDITOR_TILE16EDITOR_H -#include +#include "imgui/imgui.h" #include #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& 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& 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 notify_tile16; core::NotifyValue 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 current_tilesheets_; gui::Canvas transfer_canvas_; gfx::Bitmap transfer_blockset_bmp_; - gfx::Bitmap transfer_current_bmp_; std::vector tile16_individual_data_; std::vector tile16_individual_; diff --git a/src/app/editor/master_editor.cc b/src/app/editor/master_editor.cc index 87a6297e..94bce89d 100644 --- a/src/app/editor/master_editor.cc +++ b/src/app/editor/master_editor.cc @@ -1,148 +1,75 @@ #include "master_editor.h" -#include -#include -#include -#include -#include -#include +#include "ImGuiColorTextEdit/TextEditor.h" +#include "ImGuiFileDialog/ImGuiFileDialog.h" +#include "abseil-cpp/absl/strings/match.h" +#include "imgui/backends/imgui_impl_sdl2.h" +#include "imgui/backends/imgui_impl_sdlrenderer2.h" +#include "imgui/imgui.h" +#include "imgui/misc/cpp/imgui_stdlib.h" +#include "imgui_internal.h" +#include "imgui_memory_editor.h" #include "absl/status/status.h" #include "app/core/common.h" #include "app/core/constants.h" #include "app/core/platform/file_dialog.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/editor/code/assembly_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/screen_editor.h" -#include "app/editor/sprite_editor.h" +#include "app/editor/sprite/sprite_editor.h" +#include "app/editor/utils/flags.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/pipeline.h" #include "app/gui/style.h" -#include "app/gui/widgets.h" #include "app/rom.h" namespace yaze { namespace app { namespace editor { +using namespace ImGui; + namespace { - -constexpr ImGuiWindowFlags kMainEditorFlags = - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | - ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar; - -void NewMasterFrame() { - const ImGuiIO& io = ImGui::GetIO(); - ImGui::NewFrame(); - ImGui::SetNextWindowPos(gui::kZeroPos); - ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y); - ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always); - - if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) { - ImGui::End(); - return; - } -} - -bool BeginCentered(const char* name) { - ImGuiIO const& io = ImGui::GetIO(); + +bool BeginCentered(const char *name) { + ImGuiIO const &io = GetIO(); ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f); - ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings; - return ImGui::Begin(name, nullptr, flags); + return Begin(name, nullptr, flags); } -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& GetRecentFiles() const { - return recentFiles_; - } - - private: - std::string filename_; - std::vector recentFiles_; -}; +bool IsEditorActive(Editor* editor, std::vector& active_editors) { + return std::find(active_editors.begin(), active_editors.end(), editor) != + active_editors.end(); +} } // namespace -using ImGui::BeginMenu; -using ImGui::Checkbox; -using ImGui::MenuItem; -using ImGui::Text; - -void MasterEditor::SetupScreen(std::shared_ptr renderer) { +void MasterEditor::SetupScreen(std::shared_ptr renderer, + std::string filename) { sdl_renderer_ = renderer; rom()->SetupRenderer(renderer); + if (!filename.empty()) { + PRINT_IF_ERROR(rom()->LoadFromFile(filename)); + } + overworld_editor_.InitializeZeml(); } -namespace { -// Function to switch the active tab in a tab bar -void SetTabBarTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { - if (tab_bar == NULL) return; - - // Find the tab item with the specified tab_id - ImGuiTabItem* tab_item = &tab_bar->Tabs[tab_id]; - tab_item->LastFrameVisible = -1; - tab_item->LastFrameSelected = -1; - tab_bar->VisibleTabId = tab_id; - tab_bar->VisibleTabWasSubmitted = true; - tab_bar->SelectedTabId = tab_id; - tab_bar->NextSelectedTabId = tab_id; - tab_bar->ReorderRequestTabId = tab_id; - tab_bar->CurrFrameVisible = -1; -} -} // namespace - absl::Status MasterEditor::Update() { - NewMasterFrame(); + ManageKeyboardShortcuts(); DrawYazeMenu(); DrawFileDialog(); @@ -158,38 +85,232 @@ absl::Status MasterEditor::Update() { rom_assets_loaded_ = true; } - TAB_BAR("##TabBar") - auto current_tab_bar = ImGui::GetCurrentContext()->CurrentTabBar; - - if (overworld_editor_.jump_to_tab() == -1) { - gui::RenderTabItem("Overworld", [&]() { - current_editor_ = &overworld_editor_; - status_ = overworld_editor_.Update(); - }); - } - - gui::RenderTabItem("Dungeon", [&]() { - current_editor_ = &dungeon_editor_; - status_ = dungeon_editor_.Update(); - if (overworld_editor_.jump_to_tab() != -1) { - dungeon_editor_.add_room(overworld_editor_.jump_to_tab()); - overworld_editor_.jump_to_tab_ = -1; - } - }); - - gui::RenderTabItem("Graphics", - [&]() { status_ = graphics_editor_.Update(); }); - gui::RenderTabItem("Sprites", [&]() { status_ = sprite_editor_.Update(); }); - gui::RenderTabItem("Palettes", [&]() { status_ = palette_editor_.Update(); }); - gui::RenderTabItem("Screens", [&]() { screen_editor_.Update(); }); - gui::RenderTabItem("Music", [&]() { music_editor_.Update(); }); - END_TAB_BAR() - - ImGui::End(); + ManageActiveEditors(); return absl::OkStatus(); } +void MasterEditor::ManageActiveEditors() { + // Show popup pane to select an editor to add + static bool show_add_editor = false; + if (show_add_editor) OpenPopup("AddEditor"); + + if (BeginPopup("AddEditor", ImGuiWindowFlags_AlwaysAutoResize)) { + if (MenuItem("Overworld", nullptr, false, + !IsEditorActive(&overworld_editor_, active_editors_))) { + active_editors_.push_back(&overworld_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Dungeon", nullptr, false, + !IsEditorActive(&dungeon_editor_, active_editors_))) { + active_editors_.push_back(&dungeon_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Graphics", nullptr, false, + !IsEditorActive(&graphics_editor_, active_editors_))) { + active_editors_.push_back(&graphics_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Music", nullptr, false, + !IsEditorActive(&music_editor_, active_editors_))) { + active_editors_.push_back(&music_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Palette", nullptr, false, + !IsEditorActive(&palette_editor_, active_editors_))) { + active_editors_.push_back(&palette_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Screen", nullptr, false, + !IsEditorActive(&screen_editor_, active_editors_))) { + active_editors_.push_back(&screen_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Sprite", nullptr, false, + !IsEditorActive(&sprite_editor_, active_editors_))) { + active_editors_.push_back(&sprite_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Code", nullptr, false, + !IsEditorActive(&assembly_editor_, active_editors_))) { + active_editors_.push_back(&assembly_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Message", nullptr, false, + !IsEditorActive(&message_editor_, active_editors_))) { + active_editors_.push_back(&message_editor_); + CloseCurrentPopup(); + } + if (MenuItem("Settings", nullptr, false, + !IsEditorActive(&settings_editor_, active_editors_))) { + active_editors_.push_back(&settings_editor_); + CloseCurrentPopup(); + } + EndPopup(); + } + + if (!IsPopupOpen("AddEditor")) { + show_add_editor = false; + } + + if (BeginTabBar("##TabBar", ImGuiTabBarFlags_Reorderable | + ImGuiTabBarFlags_AutoSelectNewTabs)) { + for (auto editor : active_editors_) { + bool open = true; + switch (editor->type()) { + case EditorType::kOverworld: + if (overworld_editor_.jump_to_tab() == -1) { + if (BeginTabItem("Overworld", &open)) { + current_editor_ = &overworld_editor_; + status_ = overworld_editor_.Update(); + EndTabItem(); + } + } + break; + case EditorType::kDungeon: + if (BeginTabItem("Dungeon", &open)) { + current_editor_ = &dungeon_editor_; + status_ = dungeon_editor_.Update(); + if (overworld_editor_.jump_to_tab() != -1) { + dungeon_editor_.add_room(overworld_editor_.jump_to_tab()); + overworld_editor_.jump_to_tab_ = -1; + } + EndTabItem(); + } + break; + case EditorType::kGraphics: + if (BeginTabItem("Graphics", &open)) { + current_editor_ = &graphics_editor_; + status_ = graphics_editor_.Update(); + EndTabItem(); + } + break; + case EditorType::kMusic: + if (BeginTabItem("Music", &open)) { + current_editor_ = &music_editor_; + + status_ = music_editor_.Update(); + EndTabItem(); + } + break; + case EditorType::kPalette: + if (BeginTabItem("Palette", &open)) { + current_editor_ = &palette_editor_; + status_ = palette_editor_.Update(); + EndTabItem(); + } + break; + case EditorType::kScreen: + if (BeginTabItem("Screen", &open)) { + current_editor_ = &screen_editor_; + status_ = screen_editor_.Update(); + EndTabItem(); + } + break; + case EditorType::kSprite: + if (BeginTabItem("Sprite", &open)) { + current_editor_ = &sprite_editor_; + status_ = sprite_editor_.Update(); + EndTabItem(); + } + break; + case EditorType::kAssembly: + if (BeginTabItem("Code", &open)) { + current_editor_ = &assembly_editor_; + assembly_editor_.UpdateCodeView(); + EndTabItem(); + } + break; + case EditorType::kSettings: + if (BeginTabItem("Settings", &open)) { + current_editor_ = &settings_editor_; + status_ = settings_editor_.Update(); + EndTabItem(); + } + break; + case EditorType::kMessage: + if (BeginTabItem("Message", &open)) { + current_editor_ = &message_editor_; + status_ = message_editor_.Update(); + EndTabItem(); + } + break; + default: + break; + } + if (!open) { + active_editors_.erase( + std::remove(active_editors_.begin(), active_editors_.end(), editor), + active_editors_.end()); + } + } + + if (TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing)) { + show_add_editor = true; + } + + EndTabBar(); + } +} + +void MasterEditor::ManageKeyboardShortcuts() { + bool ctrl_or_super = (GetIO().KeyCtrl || GetIO().KeySuper); + + // If CMD + R is pressed, reload the top result of recent files + if (IsKeyDown(ImGuiKey_R) && ctrl_or_super) { + static RecentFilesManager manager("recent_files.txt"); + manager.Load(); + if (!manager.GetRecentFiles().empty()) { + auto front = manager.GetRecentFiles().front(); + std::cout << "Reloading: " << front << std::endl; + OpenRomOrProject(front); + } + } + + if (IsKeyDown(ImGuiKey_F1)) { + about_ = true; + } + + // If CMD + Q is pressed, quit the application + if (IsKeyDown(ImGuiKey_Q) && ctrl_or_super) { + quit_ = true; + } + + // If CMD + O is pressed, open a file dialog + if (IsKeyDown(ImGuiKey_O) && ctrl_or_super) { + LoadRom(); + } + + // If CMD + S is pressed, save the current ROM + if (IsKeyDown(ImGuiKey_S) && ctrl_or_super) { + SaveRom(); + } + + if (IsKeyDown(ImGuiKey_X) && ctrl_or_super) { + status_ = current_editor_->Cut(); + } + + if (IsKeyDown(ImGuiKey_C) && ctrl_or_super) { + status_ = current_editor_->Copy(); + } + + if (IsKeyDown(ImGuiKey_V) && ctrl_or_super) { + status_ = current_editor_->Paste(); + } + + if (IsKeyDown(ImGuiKey_Z) && ctrl_or_super) { + status_ = current_editor_->Undo(); + } + + if (IsKeyDown(ImGuiKey_Y) && ctrl_or_super) { + status_ = current_editor_->Redo(); + } + + if (IsKeyDown(ImGuiKey_F) && ctrl_or_super) { + status_ = current_editor_->Find(); + } +} + void MasterEditor::DrawFileDialog() { gui::FileDialogPipeline("ChooseFileDlgKey", ".sfc,.smc", std::nullopt, [&]() { std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); @@ -214,54 +335,57 @@ void MasterEditor::DrawStatusPopup() { } if (show_status_ && (BeginCentered("StatusWindow"))) { + Text("%s", ICON_MD_ERROR); Text("%s", prev_status_.ToString().c_str()); - ImGui::Spacing(); - ImGui::NextColumn(); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::NewLine(); - ImGui::SameLine(270); - if (ImGui::Button("OK", gui::kDefaultModalSize)) { + Spacing(); + NextColumn(); + Columns(1); + Separator(); + NewLine(); + SameLine(128); + if (Button("OK", gui::kDefaultModalSize) || + IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) { show_status_ = false; + status_ = absl::OkStatus(); } - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) { - ImGui::SetClipboardText(prev_status_.ToString().c_str()); + SameLine(); + if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) { + SetClipboardText(prev_status_.ToString().c_str()); } - ImGui::End(); + End(); } } void MasterEditor::DrawAboutPopup() { - if (about_) ImGui::OpenPopup("About"); - if (ImGui::BeginPopupModal("About", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { + if (about_) OpenPopup("About"); + if (BeginPopupModal("About", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { Text("Yet Another Zelda3 Editor - v%.2f", core::kYazeVersion); Text("Written by: scawful"); - ImGui::Spacing(); + Spacing(); Text("Special Thanks: Zarby89, JaredBrian"); - ImGui::Separator(); + Separator(); - if (ImGui::Button("Close", gui::kDefaultModalSize)) { + if (Button("Close", gui::kDefaultModalSize)) { about_ = false; - ImGui::CloseCurrentPopup(); + CloseCurrentPopup(); } - ImGui::EndPopup(); + EndPopup(); } } void MasterEditor::DrawInfoPopup() { - if (rom_info_) ImGui::OpenPopup("ROM Information"); - if (ImGui::BeginPopupModal("ROM Information", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { + if (rom_info_) OpenPopup("ROM Information"); + if (BeginPopupModal("ROM Information", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { Text("Title: %s", rom()->title()); Text("ROM Size: %ld", rom()->size()); - if (ImGui::Button("Close", gui::kDefaultModalSize)) { + if (Button("Close", gui::kDefaultModalSize) || + IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) { rom_info_ = false; - ImGui::CloseCurrentPopup(); + CloseCurrentPopup(); } - ImGui::EndPopup(); + EndPopup(); } } @@ -269,60 +393,64 @@ void MasterEditor::DrawYazeMenu() { static bool show_display_settings = false; static bool show_command_line_interface = false; - MENU_BAR() - DrawFileMenu(); - DrawEditMenu(); - DrawViewMenu(); - DrawHelpMenu(); + if (BeginMenuBar()) { + DrawFileMenu(); + DrawEditMenu(); + DrawViewMenu(); + DrawTestMenu(); + DrawProjectMenu(); + DrawHelpMenu(); - ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetStyle().ItemSpacing.x - - ImGui::CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150); - // Modify the style of the button to have no background color - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - if (ImGui::Button(ICON_MD_DISPLAY_SETTINGS)) { - show_display_settings = !show_display_settings; + SameLine(GetWindowWidth() - GetStyle().ItemSpacing.x - + CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150); + // Modify the style of the button to have no background color + PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + if (Button(ICON_MD_DISPLAY_SETTINGS)) { + show_display_settings = !show_display_settings; + } + + if (Button(ICON_MD_TERMINAL)) { + show_command_line_interface = !show_command_line_interface; + } + PopStyleColor(); + + Text("%s", absl::StrCat("yaze v", core::kYazeVersion).c_str()); + + EndMenuBar(); } - if (ImGui::Button(ICON_MD_TERMINAL)) { - show_command_line_interface = !show_command_line_interface; - } - ImGui::PopStyleColor(); - - Text("%s", absl::StrCat("yaze v", core::kYazeVersion).c_str()); - - END_MENU_BAR() - if (show_display_settings) { - ImGui::Begin("Display Settings", &show_display_settings, - ImGuiWindowFlags_None); + Begin("Display Settings", &show_display_settings, ImGuiWindowFlags_None); gui::DrawDisplaySettings(); - ImGui::End(); + End(); } if (show_command_line_interface) { - ImGui::Begin("Command Line Interface", &show_command_line_interface, - ImGuiWindowFlags_None); + Begin("Command Line Interface", &show_command_line_interface, + ImGuiWindowFlags_None); Text("Enter a command:"); - ImGui::End(); + End(); + } +} + +void MasterEditor::OpenRomOrProject(const std::string& filename) { + if (absl::StrContains(filename, ".yaze")) { + status_ = current_project_.Open(filename); + if (status_.ok()) { + status_ = OpenProject(); + } + } else { + status_ = rom()->LoadFromFile(filename); } } void MasterEditor::DrawFileMenu() { static bool save_as_menu = false; + static bool new_project_menu = false; if (BeginMenu("File")) { if (MenuItem("Open", "Ctrl+O")) { - if (flags()->kNewFileDialogWrapper) { - auto file_name = FileDialogWrapper::ShowOpenFileDialog(); - PRINT_IF_ERROR(rom()->LoadFromFile(file_name)); - static RecentFilesManager manager("recent_files.txt"); - manager.Load(); - manager.AddFile(file_name); - manager.Save(); - } else { - ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM", - ".sfc,.smc", "."); - } + LoadRom(); } if (BeginMenu("Open Recent")) { @@ -333,11 +461,11 @@ void MasterEditor::DrawFileMenu() { } else { for (const auto& filePath : manager.GetRecentFiles()) { if (MenuItem(filePath.c_str())) { - status_ = rom()->LoadFromFile(filePath); + OpenRomOrProject(filePath); } } } - ImGui::EndMenu(); + EndMenu(); } MENU_ITEM2("Save", "Ctrl+S") { @@ -355,76 +483,104 @@ void MasterEditor::DrawFileMenu() { } } - ImGui::Separator(); + Separator(); + + if (BeginMenu("Project")) { + if (MenuItem("Create New Project")) { + // Create a new project + new_project_menu = true; + } + if (MenuItem("Open Project")) { + // Open an existing project + status_ = + current_project_.Open(FileDialogWrapper::ShowOpenFileDialog()); + if (status_.ok()) { + status_ = OpenProject(); + } + } + if (MenuItem("Save Project")) { + // Save the current project + status_ = current_project_.Save(); + } + + EndMenu(); + } if (BeginMenu("Options")) { MenuItem("Backup ROM", "", &backup_rom_); MenuItem("Save New Auto", "", &save_new_auto_); - ImGui::Separator(); + Separator(); if (BeginMenu("Experiment Flags")) { - if (BeginMenu("Overworld Flags")) { - Checkbox("Enable Overworld Sprites", - &mutable_flags()->overworld.kDrawOverworldSprites); - ImGui::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); - ImGui::Separator(); - Checkbox("Save Dungeon Maps", &mutable_flags()->kSaveDungeonMaps); - ImGui::EndMenu(); - } - - 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 With Change Queue", - &mutable_flags()->kSaveWithChangeQueue); - Checkbox("Use New ImGui Input", &mutable_flags()->kUseNewImGuiInput); - ImGui::EndMenu(); + static FlagsMenu flags_menu; + flags_menu.Draw(); + EndMenu(); } - - ImGui::EndMenu(); + EndMenu(); } - ImGui::Separator(); + Separator(); if (MenuItem("Quit", "Ctrl+Q")) { - // TODO: Implement quit confirmation dialog. + quit_ = true; } - ImGui::EndMenu(); + EndMenu(); } if (save_as_menu) { static std::string save_as_filename = ""; - ImGui::Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::InputText("Filename", &save_as_filename); - if (ImGui::Button("Save", gui::kDefaultModalSize)) { + Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize); + InputText("Filename", &save_as_filename); + if (Button("Save", gui::kDefaultModalSize)) { SaveRom(); save_as_menu = false; } - ImGui::SameLine(); - if (ImGui::Button("Cancel", gui::kDefaultModalSize)) { + SameLine(); + if (Button("Cancel", gui::kDefaultModalSize)) { save_as_menu = false; } - ImGui::End(); + End(); + } + + if (new_project_menu) { + Begin("New Project", &new_project_menu, ImGuiWindowFlags_AlwaysAutoResize); + static std::string save_as_filename = ""; + InputText("Project Name", &save_as_filename); + if (Button("Destination Filepath", gui::kDefaultModalSize)) { + current_project_.filepath = FileDialogWrapper::ShowOpenFolderDialog(); + } + SameLine(); + Text("%s", current_project_.filepath.c_str()); + if (Button("ROM File", gui::kDefaultModalSize)) { + current_project_.rom_filename_ = FileDialogWrapper::ShowOpenFileDialog(); + } + SameLine(); + Text("%s", current_project_.rom_filename_.c_str()); + if (Button("Labels File", gui::kDefaultModalSize)) { + current_project_.labels_filename_ = + FileDialogWrapper::ShowOpenFileDialog(); + } + SameLine(); + Text("%s", current_project_.labels_filename_.c_str()); + if (Button("Code Folder", gui::kDefaultModalSize)) { + current_project_.code_folder_ = FileDialogWrapper::ShowOpenFolderDialog(); + } + SameLine(); + Text("%s", current_project_.code_folder_.c_str()); + + Separator(); + if (Button("Create", gui::kDefaultModalSize)) { + new_project_menu = false; + status_ = current_project_.Create(save_as_filename); + if (status_.ok()) { + status_ = current_project_.Save(); + } + } + SameLine(); + if (Button("Cancel", gui::kDefaultModalSize)) { + new_project_menu = false; + } + End(); } } @@ -432,15 +588,13 @@ void MasterEditor::DrawEditMenu() { if (BeginMenu("Edit")) { MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); } MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); } - ImGui::Separator(); + Separator(); MENU_ITEM2("Cut", "Ctrl+X") { status_ = current_editor_->Cut(); } MENU_ITEM2("Copy", "Ctrl+C") { status_ = current_editor_->Copy(); } MENU_ITEM2("Paste", "Ctrl+V") { status_ = current_editor_->Paste(); } - ImGui::Separator(); + Separator(); MENU_ITEM2("Find", "Ctrl+F") {} - ImGui::Separator(); - MENU_ITEM("ROM Information") rom_info_ = true; - ImGui::EndMenu(); + EndMenu(); } } @@ -449,64 +603,25 @@ void MasterEditor::DrawViewMenu() { static bool show_memory_editor = false; static bool show_asm_editor = false; static bool show_imgui_demo = false; - static bool show_memory_viewer = false; static bool show_palette_editor = false; static bool show_emulator = false; - static bool show_resource_label_manager = false; if (show_emulator) { - ImGui::Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar); + Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar); emulator_.Run(); - ImGui::End(); + End(); } if (show_imgui_metrics) { - ImGui::ShowMetricsWindow(&show_imgui_metrics); + ShowMetricsWindow(&show_imgui_metrics); } if (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); - ImGui::SameLine(); - ImGui::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() - ImGui::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"); - ImGui::Text("%s", comparison_rom.filename().data()); - comp_edit.DrawContents((void*)&(comparison_rom), comparison_rom.size()); - ImGui::EndChild(); - ImGui::EndGroup(); - } - END_TABLE() - - ImGui::End(); + memory_editor_.Update(show_memory_editor); } if (show_imgui_demo) { - ImGui::ShowDemoWindow(); + ShowDemoWindow(); } if (show_asm_editor) { @@ -514,85 +629,109 @@ void MasterEditor::DrawViewMenu() { } if (show_palette_editor) { - ImGui::Begin("Palette Editor", &show_palette_editor); + Begin("Palette Editor", &show_palette_editor); status_ = palette_editor_.Update(); - ImGui::End(); - } - - if (show_memory_viewer) { - ImGui::Begin("Memory Viewer (ImGui)", &show_memory_viewer); - - const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); - static ImGuiTableFlags flags = - ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | - ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg | - ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX; - if (auto outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f); - ImGui::BeginTable("table1", 3, flags, outer_size)) { - for (int row = 0; row < 10; row++) { - ImGui::TableNextRow(); - for (int column = 0; column < 3; column++) { - ImGui::TableNextColumn(); - Text("Cell %d,%d", column, row); - } - } - ImGui::EndTable(); - } - - ImGui::End(); - } - - if (show_resource_label_manager) { - rom()->resource_label()->DisplayLabels(&show_resource_label_manager); + End(); } if (BeginMenu("View")) { MenuItem("Emulator", nullptr, &show_emulator); - MenuItem("Memory Viewer", nullptr, &show_memory_viewer); - ImGui::Separator(); - MenuItem("Resource Label Manager", nullptr, &show_resource_label_manager); - ImGui::Separator(); - MenuItem("Hex Editor", nullptr, &show_memory_editor); + Separator(); + MenuItem("Memory Editor", nullptr, &show_memory_editor); MenuItem("Assembly Editor", nullptr, &show_asm_editor); MenuItem("Palette Editor", nullptr, &show_palette_editor); - ImGui::Separator(); + Separator(); + MENU_ITEM("ROM Information") rom_info_ = true; + Separator(); MenuItem("ImGui Demo", nullptr, &show_imgui_demo); MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics); - ImGui::EndMenu(); + EndMenu(); + } +} + +void MasterEditor::DrawTestMenu() { + static bool show_tests_ = false; + + if (BeginMenu("Tests")) { + MenuItem("Run Tests", nullptr, &show_tests_); + EndMenu(); + } + +} + +void MasterEditor::DrawProjectMenu() { + static bool show_resource_label_manager = false; + + if (current_project_.project_opened_) { + // Project Menu + if (BeginMenu("Project")) { + Text("Name: %s", current_project_.name.c_str()); + Text("ROM: %s", current_project_.rom_filename_.c_str()); + Text("Labels: %s", current_project_.labels_filename_.c_str()); + Text("Code: %s", current_project_.code_folder_.c_str()); + Separator(); + MenuItem("Resource Labels", nullptr, &show_resource_label_manager); + EndMenu(); + } + } + if (show_resource_label_manager) { + rom()->resource_label()->DisplayLabels(&show_resource_label_manager); + if (current_project_.project_opened_ && + !current_project_.labels_filename_.empty()) { + current_project_.labels_filename_ = rom()->resource_label()->filename_; + } } } void MasterEditor::DrawHelpMenu() { static bool open_rom_help = false; static bool open_supported_features = false; + static bool open_manage_project = false; if (BeginMenu("Help")) { if (MenuItem("How to open a ROM")) open_rom_help = true; if (MenuItem("Supported Features")) open_supported_features = true; + if (MenuItem("How to manage a project")) open_manage_project = true; - if (MenuItem("About")) about_ = true; - ImGui::EndMenu(); + if (MenuItem("About", "F1")) about_ = true; + EndMenu(); } - if (open_supported_features) ImGui::OpenPopup("Supported Features"); - if (ImGui::BeginPopupModal("Supported Features", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - // TODO: Expand on details of what is currently implemented. - ImGui::BulletText("Overworld Editing"); - ImGui::BulletText("Dungeon Editing"); - ImGui::BulletText("Sprite Editing"); - ImGui::BulletText("Palette Editing"); - ImGui::BulletText("Screen Editing"); + if (open_supported_features) OpenPopup("Supported Features"); + if (BeginPopupModal("Supported Features", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + Text("Overworld"); + BulletText("LW/DW/SW Tilemap Editing"); + BulletText("LW/DW/SW Map Properties"); + BulletText("Create/Delete/Update Entrances"); + BulletText("Create/Delete/Update Exits"); + BulletText("Create/Delete/Update Sprites"); + BulletText("Create/Delete/Update Items"); - if (ImGui::Button("Close", gui::kDefaultModalSize)) { + Text("Dungeon"); + BulletText("View Room Header Properties"); + BulletText("View Entrance Properties"); + + Text("Graphics"); + BulletText("View Decompressed Graphics Sheets"); + BulletText("View/Update Graphics Groups"); + + Text("Palettes"); + BulletText("View Palette Groups"); + + Text("Saveable"); + BulletText("All Listed Overworld Features"); + BulletText("Hex Editor Changes"); + + if (Button("Close", gui::kDefaultModalSize)) { open_supported_features = false; - ImGui::CloseCurrentPopup(); + CloseCurrentPopup(); } - ImGui::EndPopup(); + EndPopup(); } - if (open_rom_help) ImGui::OpenPopup("Open a ROM"); - if (ImGui::BeginPopupModal("Open a ROM", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { + if (open_rom_help) OpenPopup("Open a ROM"); + if (BeginPopupModal("Open a ROM", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { Text("File -> Open"); Text("Select a ROM file to open"); Text("Supported ROMs (headered or unheadered):"); @@ -600,11 +739,45 @@ void MasterEditor::DrawHelpMenu() { Text("US Version 1.0"); Text("JP Version 1.0"); - if (ImGui::Button("Close", gui::kDefaultModalSize)) { + if (Button("Close", gui::kDefaultModalSize)) { open_rom_help = false; - ImGui::CloseCurrentPopup(); + CloseCurrentPopup(); } - ImGui::EndPopup(); + EndPopup(); + } + + if (open_manage_project) OpenPopup("Manage Project"); + if (BeginPopupModal("Manage Project", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + Text("Project Menu"); + Text("Create a new project or open an existing one."); + Text("Save the project to save the current state of the project."); + Text( + "To save a project, you need to first open a ROM and initialize your " + "code path and labels file. Label resource manager can be found in " + "the " + "View menu. Code path is set in the Code editor after opening a " + "folder."); + + if (Button("Close", gui::kDefaultModalSize)) { + open_manage_project = false; + CloseCurrentPopup(); + } + EndPopup(); + } +} + +void MasterEditor::LoadRom() { + if (flags()->kNewFileDialogWrapper) { + auto file_name = FileDialogWrapper::ShowOpenFileDialog(); + PRINT_IF_ERROR(rom()->LoadFromFile(file_name)); + static RecentFilesManager manager("recent_files.txt"); + manager.Load(); + manager.AddFile(file_name); + manager.Save(); + } else { + ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM", + ".sfc,.smc", "."); } } @@ -643,6 +816,27 @@ void MasterEditor::SaveRom() { status_ = rom()->SaveToFile(backup_rom_, save_new_auto_); } +absl::Status MasterEditor::OpenProject() { + RETURN_IF_ERROR(rom()->LoadFromFile(current_project_.rom_filename_)); + + if (!rom()->resource_label()->LoadLabels(current_project_.labels_filename_)) { + return absl::InternalError( + "Could not load labels file, update your project file."); + } + + static RecentFilesManager manager("recent_files.txt"); + manager.Load(); + manager.AddFile(current_project_.filepath + "/" + current_project_.name + + ".yaze"); + manager.Save(); + + assembly_editor_.OpenFolder(current_project_.code_folder_); + + current_project_.project_opened_ = true; + + return absl::OkStatus(); +} + } // namespace editor } // namespace app } // namespace yaze diff --git a/src/app/editor/master_editor.h b/src/app/editor/master_editor.h index cec820bc..cc5e495f 100644 --- a/src/app/editor/master_editor.h +++ b/src/app/editor/master_editor.h @@ -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 -#include -#include -#include -#include +#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 renderer); + void SetupScreen(std::shared_ptr renderer, + std::string filename = ""); absl::Status Update(); - void Shutdown() { overworld_editor_.Shutdown(); } + auto emulator() -> emu::Emulator& { return emulator_; } + auto quit() { return quit_; } + auto overworld_editor() -> OverworldEditor& { return overworld_editor_; } private: + void ManageActiveEditors(); + void ManageKeyboardShortcuts(); + void OpenRomOrProject(const std::string& filename); + void DrawFileDialog(); void DrawStatusPopup(); void DrawAboutPopup(); @@ -73,10 +91,16 @@ class MasterEditor : public SharedRom, void DrawFileMenu(); void DrawEditMenu(); void DrawViewMenu(); + void DrawTestMenu(); + void DrawProjectMenu(); void DrawHelpMenu(); + void LoadRom(); void SaveRom(); + absl::Status OpenProject(); + + bool quit_ = false; bool about_ = false; bool rom_info_ = false; bool backup_rom_ = false; @@ -91,6 +115,8 @@ class MasterEditor : public SharedRom, emu::Emulator emulator_; + Project current_project_; + AssemblyEditor assembly_editor_; DungeonEditor dungeon_editor_; GraphicsEditor graphics_editor_; @@ -99,12 +125,17 @@ class MasterEditor : public SharedRom, PaletteEditor palette_editor_; ScreenEditor screen_editor_; SpriteEditor sprite_editor_; + SettingsEditor settings_editor_; + MessageEditor message_editor_; + MemoryEditorWithDiffChecker memory_editor_; - Editor *current_editor_ = nullptr; + ImVector active_tabs_; + std::vector active_editors_; + Editor* current_editor_ = nullptr; }; } // namespace editor } // namespace app } // namespace yaze -#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H \ No newline at end of file +#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H diff --git a/src/app/editor/master_editor_test.cc b/src/app/editor/master_editor_test.cc new file mode 100644 index 00000000..69cd54b5 --- /dev/null +++ b/src/app/editor/master_editor_test.cc @@ -0,0 +1,10 @@ +#include "master_editor.h" + +namespace yaze { +namespace app { +namespace editor { + + +} // namespace editor +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/editor/message/message_data.h b/src/app/editor/message/message_data.h new file mode 100644 index 00000000..1f67134b --- /dev/null +++ b/src/app/editor/message/message_data.h @@ -0,0 +1,177 @@ +#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H +#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H + +#include +#include + +#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 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 Data; + std::vector DataParsed; + + MessageData() = default; + MessageData(int id, int address, const std::string& rawString, + const std::vector& rawData, + const std::string& parsedString, + const std::vector& 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 \ No newline at end of file diff --git a/src/app/editor/message/message_editor.cc b/src/app/editor/message/message_editor.cc new file mode 100644 index 00000000..fb3691a8 --- /dev/null +++ b/src/app/editor/message/message_editor.cc @@ -0,0 +1,770 @@ +#include "message_editor.h" + +#include +#include +#include +#include +#include + +#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 ParseMessageToData(string str) { + std::vector 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 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 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 temp_bytes_raw; + std::vector 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& 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 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 diff --git a/src/app/editor/message/message_editor.h b/src/app/editor/message/message_editor.h new file mode 100644 index 00000000..9d1f5842 --- /dev/null +++ b/src/app/editor/message/message_editor.h @@ -0,0 +1,366 @@ +#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H +#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H + +#include +#include +#include +#include +#include +#include + +#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 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 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 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 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& 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 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 ListOfTexts; + std::vector DisplayedMessages; + std::vector 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 AllDictionaries; + +} // namespace editor +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_EDITOR_MESSAGE_EDITOR_H diff --git a/src/app/editor/message/message_editor_test.cc b/src/app/editor/message/message_editor_test.cc new file mode 100644 index 00000000..67c52ae9 --- /dev/null +++ b/src/app/editor/message/message_editor_test.cc @@ -0,0 +1,7 @@ +#include "message_editor.h" + +namespace yaze { +namespace app { +namespace editor {} // namespace editor +} // namespace app +} // namespace yaze diff --git a/src/app/editor/modules/assembly_editor.cc b/src/app/editor/modules/assembly_editor.cc deleted file mode 100644 index b1a55fd5..00000000 --- a/src/app/editor/modules/assembly_editor.cc +++ /dev/null @@ -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(t)), - std::istreambuf_iterator()); - text_editor_.SetText(str); - file_is_loaded_ = true; - } - } -} - -} // namespace editor -} // namespace app -} // namespace yaze \ No newline at end of file diff --git a/src/app/editor/modules/assembly_editor.h b/src/app/editor/modules/assembly_editor.h deleted file mode 100644 index c34a0396..00000000 --- a/src/app/editor/modules/assembly_editor.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H -#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H - -#include -#include - -#include -#include -#include - -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 \ No newline at end of file diff --git a/src/app/editor/modules/gfx_group_editor.cc b/src/app/editor/modules/gfx_group_editor.cc deleted file mode 100644 index 634a5a84..00000000 --- a/src/app/editor/modules/gfx_group_editor.cc +++ /dev/null @@ -1,246 +0,0 @@ -#include "gfx_group_editor.h" - -#include - -#include - -#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 diff --git a/src/app/editor/modules/palette_editor.cc b/src/app/editor/modules/palette_editor.cc deleted file mode 100644 index 9816388b..00000000 --- a/src/app/editor/modules/palette_editor.cc +++ /dev/null @@ -1,300 +0,0 @@ -#include "palette_editor.h" - -#include - -#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 \ No newline at end of file diff --git a/src/app/editor/modules/music_editor.cc b/src/app/editor/music/music_editor.cc similarity index 76% rename from src/app/editor/modules/music_editor.cc rename to src/app/editor/music/music_editor.cc index e29f2c7f..47001c94 100644 --- a/src/app/editor/modules/music_editor.cc +++ b/src/app/editor/music/music_editor.cc @@ -1,71 +1,18 @@ #include "music_editor.h" -#include -#include +#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; diff --git a/src/app/editor/modules/music_editor.h b/src/app/editor/music/music_editor.h similarity index 81% rename from src/app/editor/modules/music_editor.h rename to src/app/editor/music/music_editor.h index 579d9089..64cf360a 100644 --- a/src/app/editor/modules/music_editor.h +++ b/src/app/editor/music/music_editor.h @@ -1,11 +1,11 @@ #ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H #define YAZE_APP_EDITOR_MUSIC_EDITOR_H -#include -#include +#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(); diff --git a/src/app/editor/overworld/entity.cc b/src/app/editor/overworld/entity.cc new file mode 100644 index 00000000..3d5227fd --- /dev/null +++ b/src/app/editor/overworld/entity.cc @@ -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(mouse_pos.x) / 16 * 16; + int new_y = static_cast(mouse_pos.y) / 16 * 16; + if (free_movement) { + new_x = static_cast(mouse_pos.x) / 8 * 8; + new_y = static_cast(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 onSpriteSelect) { + static ImGuiTextFilter filter; + static int selected_id = 0; + static std::vector 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 diff --git a/src/app/editor/overworld/entity.h b/src/app/editor/overworld/entity.h new file mode 100644 index 00000000..3697972a --- /dev/null +++ b/src/app/editor/overworld/entity.h @@ -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 &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 onSpriteSelect); +void DrawSpriteInserterPopup(); +bool DrawSpriteEditorPopup(zelda3::Sprite &sprite); + +} // namespace editor +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H \ No newline at end of file diff --git a/src/app/editor/overworld/refresh.cc b/src/app/editor/overworld/refresh.cc new file mode 100644 index 00000000..bdfaa566 --- /dev/null +++ b/src/app/editor/overworld/refresh.cc @@ -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> 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> 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 \ No newline at end of file diff --git a/src/app/editor/overworld_editor.cc b/src/app/editor/overworld_editor.cc index d04e9da7..64871849 100644 --- a/src/app/editor/overworld_editor.cc +++ b/src/app/editor/overworld_editor.cc @@ -1,6 +1,7 @@ #include "overworld_editor.h" -#include +#include "ImGuiFileDialog/ImGuiFileDialog.h" +#include "imgui/imgui.h" #include #include @@ -12,16 +13,16 @@ #include "app/core/common.h" #include "app/core/constants.h" #include "app/core/platform/clipboard.h" -#include "app/editor/modules/palette_editor.h" +#include "app/editor/graphics/palette_editor.h" +#include "app/editor/overworld/entity.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/input.h" -#include "app/gui/pipeline.h" #include "app/gui/style.h" -#include "app/gui/widgets.h" +#include "app/gui/zeml.h" #include "app/rom.h" #include "app/zelda3/overworld/overworld.h" @@ -29,12 +30,24 @@ namespace yaze { namespace app { namespace editor { +using ImGui::BeginChild; using ImGui::BeginTabBar; using ImGui::BeginTabItem; using ImGui::BeginTable; +using ImGui::BeginTooltip; using ImGui::Button; +using ImGui::Checkbox; +using ImGui::EndChild; using ImGui::EndTabBar; using ImGui::EndTabItem; +using ImGui::EndTable; +using ImGui::EndTooltip; +using ImGui::IsItemHovered; +using ImGui::NewLine; +using ImGui::PopStyleColor; +using ImGui::PushStyleColor; +using ImGui::SameLine; +using ImGui::Selectable; using ImGui::Separator; using ImGui::TableHeadersRow; using ImGui::TableNextColumn; @@ -42,50 +55,53 @@ using ImGui::TableNextRow; using ImGui::TableSetupColumn; using ImGui::Text; +constexpr int kTile16Size = 0x10; +constexpr int kOverworldMapSize = 0x200; + +void OverworldEditor::InitializeZeml() { + // Load zeml string from layouts/overworld.zeml + std::string layout = gui::zeml::LoadFile("overworld.zeml"); + // Parse the zeml string into a Node object + layout_node_ = gui::zeml::Parse(layout); + + gui::zeml::Bind(&*layout_node_.GetNode("OverworldCanvas"), + [this]() { DrawOverworldCanvas(); }); + gui::zeml::Bind(&*layout_node_.GetNode("OverworldTileSelector"), + [this]() { status_ = DrawTileSelector(); }); + gui::zeml::Bind(&*layout_node_.GetNode("OwUsageStats"), [this]() { + if (rom()->is_loaded()) { + status_ = UpdateUsageStats(); + } + }); + gui::zeml::Bind(&*layout_node_.GetNode("owToolset"), + [this]() { status_ = DrawToolset(); }); + gui::zeml::Bind(&*layout_node_.GetNode("OwTile16Editor"), [this]() { + if (rom()->is_loaded()) { + status_ = tile16_editor_.Update(); + } + }); + gui::zeml::Bind(&*layout_node_.GetNode("OwGfxGroupEditor"), [this]() { + if (rom()->is_loaded()) { + status_ = gfx_group_editor_.Update(); + } + }); +} + absl::Status OverworldEditor::Update() { + status_ = absl::OkStatus(); if (rom()->is_loaded() && !all_gfx_loaded_) { - tile16_editor_.InitBlockset(tile16_blockset_bmp_, current_gfx_bmp_, - tile16_individual_, - *overworld_.mutable_all_tiles_types()); - gfx_group_editor_.InitBlockset(tile16_blockset_bmp_); + RETURN_IF_ERROR(tile16_editor_.InitBlockset( + &tile16_blockset_bmp_, current_gfx_bmp_, tile16_individual_, + *overworld_.mutable_all_tiles_types())); + gfx_group_editor_.InitBlockset(&tile16_blockset_bmp_); RETURN_IF_ERROR(LoadEntranceTileTypes(*rom())); all_gfx_loaded_ = true; - } else if (!rom()->is_loaded() && all_gfx_loaded_) { - Shutdown(); } RETURN_IF_ERROR(UpdateFullscreenCanvas()); - TAB_BAR("##OWEditorTabBar") - TAB_ITEM("Map Editor") - status_ = UpdateOverworldEdit(); - END_TAB_ITEM() - TAB_ITEM("Usage Statistics") - if (rom()->is_loaded()) { - status_ = UpdateUsageStats(); - } - END_TAB_ITEM() - END_TAB_BAR() - - CLEAR_AND_RETURN_STATUS(status_); - return absl::OkStatus(); -} - -absl::Status OverworldEditor::UpdateOverworldEdit() { - RETURN_IF_ERROR(DrawToolset()) - if (BeginTable(kOWEditTable.data(), 2, kOWEditFlags, ImVec2(0, 0))) { - TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch, - ImGui::GetContentRegionAvail().x); - TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256); - TableHeadersRow(); - TableNextRow(); - TableNextColumn(); - DrawOverworldCanvas(); - TableNextColumn(); - RETURN_IF_ERROR(DrawTileSelector()); - ImGui::EndTable(); - } - return absl::OkStatus(); + gui::zeml::Render(layout_node_); + return status_; } absl::Status OverworldEditor::UpdateFullscreenCanvas() { @@ -99,14 +115,13 @@ absl::Status OverworldEditor::UpdateFullscreenCanvas() { ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size); - if (ImGui::Begin("Example: Fullscreen window", + if (ImGui::Begin("Fullscreen Overworld Editor", &overworld_canvas_fullscreen_, flags)) { // Draws the toolset for editing the Overworld. RETURN_IF_ERROR(DrawToolset()) DrawOverworldCanvas(); } ImGui::End(); - return absl::OkStatus(); } return absl::OkStatus(); } @@ -120,29 +135,29 @@ absl::Status OverworldEditor::DrawToolset() { ImGui::TableSetupColumn(name.data()); NEXT_COLUMN() - if (ImGui::Button(ICON_MD_UNDO)) { + if (Button(ICON_MD_UNDO)) { status_ = Undo(); } NEXT_COLUMN() - if (ImGui::Button(ICON_MD_REDO)) { + if (Button(ICON_MD_REDO)) { status_ = Redo(); } TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator NEXT_COLUMN() - if (ImGui::Button(ICON_MD_ZOOM_OUT)) { + if (Button(ICON_MD_ZOOM_OUT)) { ow_map_canvas_.ZoomOut(); } NEXT_COLUMN() - if (ImGui::Button(ICON_MD_ZOOM_IN)) { + if (Button(ICON_MD_ZOOM_IN)) { ow_map_canvas_.ZoomIn(); } NEXT_COLUMN() - if (ImGui::Button(ICON_MD_OPEN_IN_FULL)) { + if (Button(ICON_MD_OPEN_IN_FULL)) { overworld_canvas_fullscreen_ = !overworld_canvas_fullscreen_; } HOVER_HINT("Fullscreen Canvas") @@ -150,63 +165,58 @@ absl::Status OverworldEditor::DrawToolset() { TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_PAN_TOOL_ALT, - current_mode == EditingMode::PAN)) { + if (Selectable(ICON_MD_PAN_TOOL_ALT, current_mode == EditingMode::PAN)) { current_mode = EditingMode::PAN; ow_map_canvas_.set_draggable(true); } HOVER_HINT("Pan (Right click and drag)") NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_DRAW, - current_mode == EditingMode::DRAW_TILE)) { + if (Selectable(ICON_MD_DRAW, current_mode == EditingMode::DRAW_TILE)) { current_mode = EditingMode::DRAW_TILE; } HOVER_HINT("Draw Tile") NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_DOOR_FRONT, - current_mode == EditingMode::ENTRANCES)) + if (Selectable(ICON_MD_DOOR_FRONT, current_mode == EditingMode::ENTRANCES)) current_mode = EditingMode::ENTRANCES; HOVER_HINT("Entrances") NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_DOOR_BACK, - current_mode == EditingMode::EXITS)) + if (Selectable(ICON_MD_DOOR_BACK, current_mode == EditingMode::EXITS)) current_mode = EditingMode::EXITS; HOVER_HINT("Exits") NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_GRASS, current_mode == EditingMode::ITEMS)) + if (Selectable(ICON_MD_GRASS, current_mode == EditingMode::ITEMS)) current_mode = EditingMode::ITEMS; HOVER_HINT("Items") NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_PEST_CONTROL_RODENT, - current_mode == EditingMode::SPRITES)) + if (Selectable(ICON_MD_PEST_CONTROL_RODENT, + current_mode == EditingMode::SPRITES)) current_mode = EditingMode::SPRITES; HOVER_HINT("Sprites") NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_ADD_LOCATION, - current_mode == EditingMode::TRANSPORTS)) + if (Selectable(ICON_MD_ADD_LOCATION, + current_mode == EditingMode::TRANSPORTS)) current_mode = EditingMode::TRANSPORTS; HOVER_HINT("Transports") NEXT_COLUMN() - if (ImGui::Selectable(ICON_MD_MUSIC_NOTE, - current_mode == EditingMode::MUSIC)) + if (Selectable(ICON_MD_MUSIC_NOTE, current_mode == EditingMode::MUSIC)) current_mode = EditingMode::MUSIC; HOVER_HINT("Music") TableNextColumn(); - if (ImGui::Button(ICON_MD_GRID_VIEW)) { + if (Button(ICON_MD_GRID_VIEW)) { show_tile16_editor_ = !show_tile16_editor_; } HOVER_HINT("Tile16 Editor") TableNextColumn(); - if (ImGui::Button(ICON_MD_TABLE_CHART)) { + if (Button(ICON_MD_TABLE_CHART)) { show_gfx_group = !show_gfx_group; } HOVER_HINT("Gfx Group Editor") @@ -232,10 +242,10 @@ absl::Status OverworldEditor::DrawToolset() { TEXT_COLUMN(ICON_MD_MORE_VERT) // Separator TableNextColumn(); // Experimental - ImGui::Checkbox("Experimental", &show_experimental); + Checkbox("Experimental", &show_experimental); TableNextColumn(); - ImGui::Checkbox("Properties", &show_properties); + Checkbox("Properties", &show_properties); ImGui::EndTable(); } @@ -287,112 +297,11 @@ absl::Status OverworldEditor::DrawToolset() { return absl::OkStatus(); } -// ---------------------------------------------------------------------------- - -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_); - OWBlockset blockset; - if (current_world_ == 0) { - blockset = overworld_.map_tiles().light_world; - } else if (current_world_ == 1) { - blockset = overworld_.map_tiles().dark_world; - } else { - blockset = overworld_.map_tiles().special_world; - } - status_ = overworld_.mutable_overworld_map(map_index)->BuildBitmap(blockset); - 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> 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]]); - } -} - -// TODO: Palette throws out of bounds error unexpectedly. -absl::Status OverworldEditor::RefreshMapPalette() { - const auto current_map_palette = - overworld_.overworld_map(current_map_)->current_palette(); - - 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()); - } - } -} - -// TODO: Add @JaredBrian per map mosaic feature void OverworldEditor::DrawOverworldMapSettings() { - if (BeginTable(kOWMapTable.data(), 7, kOWMapFlags, ImVec2(0, 0), -1)) { - for (const auto &name : {"##1stCol", "##gfxCol", "##palCol", "##sprgfxCol", - "##sprpalCol", "##msgidCol", "##2ndCol"}) + if (BeginTable(kOWMapTable.data(), 8, kOWMapFlags, ImVec2(0, 0), -1)) { + for (const auto &name : + {"##1stCol", "##gfxCol", "##palCol", "##sprgfxCol", "##sprpalCol", + "##msgidCol", "##2ndCol", "##mosaic"}) ImGui::TableSetupColumn(name); TableNextColumn(); @@ -450,19 +359,23 @@ void OverworldEditor::DrawOverworldMapSettings() { ImGui::SetNextItemWidth(100.f); ImGui::Combo("##World", &game_state_, kGamePartComboString.data(), 3); + TableNextColumn(); + ImGui::Checkbox( + "##mosaic", + overworld_.mutable_overworld_map(current_map_)->mutable_mosaic()); + HOVER_HINT("Enable Mosaic effect for the current map"); + ImGui::EndTable(); } } -// ---------------------------------------------------------------------------- - void OverworldEditor::DrawOverworldMaps() { int xx = 0; int yy = 0; for (int i = 0; i < 0x40; i++) { int world_index = i + (current_world_ * 0x40); - int map_x = (xx * 0x200 * ow_map_canvas_.global_scale()); - int map_y = (yy * 0x200 * ow_map_canvas_.global_scale()); + int map_x = (xx * kOverworldMapSize * ow_map_canvas_.global_scale()); + int map_y = (yy * kOverworldMapSize * ow_map_canvas_.global_scale()); ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y, ow_map_canvas_.global_scale()); xx++; @@ -475,10 +388,9 @@ void OverworldEditor::DrawOverworldMaps() { void OverworldEditor::DrawOverworldEdits() { // Determine which overworld map the user is currently editing. - constexpr int small_map_size = 512; auto mouse_position = ow_map_canvas_.drawn_tile_position(); - int map_x = mouse_position.x / small_map_size; - int map_y = mouse_position.y / small_map_size; + int map_x = mouse_position.x / kOverworldMapSize; + int map_y = mouse_position.y / kOverworldMapSize; current_map_ = map_x + map_y * 8; if (current_world_ == 1) { current_map_ += 0x40; @@ -496,8 +408,8 @@ void OverworldEditor::DrawOverworldEdits() { int mouse_x = mouse_position.x; int mouse_y = mouse_position.y; // Calculate the correct tile16_x and tile16_y positions - int tile16_x = (mouse_x % small_map_size) / (small_map_size / 32); - int tile16_y = (mouse_y % small_map_size) / (small_map_size / 32); + int tile16_x = (mouse_x % kOverworldMapSize) / (kOverworldMapSize / 32); + int tile16_y = (mouse_y % kOverworldMapSize) / (kOverworldMapSize / 32); // Update the overworld_.map_tiles() based on tile16 ID and current world auto &selected_world = @@ -517,8 +429,10 @@ void OverworldEditor::RenderUpdatedMapBitmap(const ImVec2 &click_position, constexpr int tile_size = 16; // Tile size is 16x16 pixels // Calculate the tile index for x and y based on the click_position - int tile_index_x = (static_cast(click_position.x) % 512) / tile_size; - int tile_index_y = (static_cast(click_position.y) % 512) / tile_size; + int tile_index_x = + (static_cast(click_position.x) % kOverworldMapSize) / tile_size; + int tile_index_y = + (static_cast(click_position.y) % kOverworldMapSize) / tile_size; // Calculate the pixel start position based on tile index and tile size ImVec2 start_position; @@ -531,7 +445,8 @@ void OverworldEditor::RenderUpdatedMapBitmap(const ImVec2 &click_position, // Update the bitmap's pixel data based on the start_position and tile_data for (int y = 0; y < tile_size; ++y) { for (int x = 0; x < tile_size; ++x) { - int pixel_index = (start_position.y + y) * 0x200 + (start_position.x + x); + int pixel_index = + (start_position.y + y) * kOverworldMapSize + (start_position.x + x); current_bitmap.WriteToPixel(pixel_index, tile_data[y * tile_size + x]); } } @@ -635,13 +550,12 @@ void OverworldEditor::CheckForSelectRectangle() { absl::Status OverworldEditor::CheckForCurrentMap() { // 4096x4096, 512x512 maps and some are larges maps 1024x1024 auto mouse_position = ImGui::GetIO().MousePos; - constexpr int small_map_size = 512; const auto large_map_size = 1024; const auto canvas_zero_point = ow_map_canvas_.zero_point(); // Calculate which small map the mouse is currently over - int map_x = (mouse_position.x - canvas_zero_point.x) / small_map_size; - int map_y = (mouse_position.y - canvas_zero_point.y) / small_map_size; + int map_x = (mouse_position.x - canvas_zero_point.x) / kOverworldMapSize; + int map_y = (mouse_position.y - canvas_zero_point.y) / kOverworldMapSize; // Calculate the index of the map in the `maps_bmp_` vector current_map_ = map_x + map_y * 8; @@ -663,13 +577,13 @@ absl::Status OverworldEditor::CheckForCurrentMap() { overworld_.overworld_map(current_highlighted_map)->parent(); auto parent_map_x = highlight_parent % 8; auto parent_map_y = highlight_parent / 8; - ow_map_canvas_.DrawOutline(parent_map_x * small_map_size, - parent_map_y * small_map_size, large_map_size, + ow_map_canvas_.DrawOutline(parent_map_x * kOverworldMapSize, + parent_map_y * kOverworldMapSize, large_map_size, large_map_size); } else { - ow_map_canvas_.DrawOutline(current_map_x * small_map_size, - current_map_y * small_map_size, small_map_size, - small_map_size); + ow_map_canvas_.DrawOutline(current_map_x * kOverworldMapSize, + current_map_y * kOverworldMapSize, + kOverworldMapSize, kOverworldMapSize); } if (maps_bmp_[current_map_].modified() || @@ -725,12 +639,12 @@ void OverworldEditor::DrawOverworldCanvas() { DrawOverworldItems(); DrawOverworldSprites(); CheckForOverworldEdits(); - if (ImGui::IsItemHovered()) status_ = CheckForCurrentMap(); + if (IsItemHovered()) status_ = CheckForCurrentMap(); } ow_map_canvas_.DrawGrid(); ow_map_canvas_.DrawOverlay(); - ImGui::EndChild(); + EndChild(); } absl::Status OverworldEditor::DrawTile16Selector() { @@ -750,7 +664,7 @@ absl::Status OverworldEditor::DrawTile16Selector() { int grid_x = static_cast(tile_pos.x / 32); int grid_y = static_cast(tile_pos.y / 32); int id = grid_x + grid_y * 8; - RETURN_IF_ERROR(tile16_editor_.set_tile16(id)); + RETURN_IF_ERROR(tile16_editor_.SetCurrentTile(id)); show_tile16_editor_ = true; } @@ -763,7 +677,7 @@ absl::Status OverworldEditor::DrawTile16Selector() { blockset_canvas_.DrawGrid(); blockset_canvas_.DrawOverlay(); } - ImGui::EndChild(); + EndChild(); ImGui::EndGroup(); return absl::OkStatus(); } @@ -778,7 +692,7 @@ void OverworldEditor::DrawTile8Selector() { if (key >= 1) { top_left_y = graphics_bin_canvas_.zero_point().y + 0x40 * key; } - auto texture = value.get()->texture(); + auto texture = value.texture(); graphics_bin_canvas_.draw_list()->AddImage( (void *)texture, ImVec2(graphics_bin_canvas_.zero_point().x + 2, top_left_y), @@ -797,7 +711,8 @@ absl::Status OverworldEditor::DrawAreaGraphics() { palette_ = overworld_.AreaPalette(); gfx::Bitmap bmp; RETURN_IF_ERROR(rom()->CreateAndRenderBitmap( - 0x80, 0x200, 0x08, overworld_.current_graphics(), bmp, palette_)); + 0x80, kOverworldMapSize, 0x08, overworld_.current_graphics(), bmp, + palette_)); current_graphics_set_[current_map_] = bmp; } } @@ -815,7 +730,7 @@ absl::Status OverworldEditor::DrawAreaGraphics() { current_gfx_canvas_.DrawGrid(); current_gfx_canvas_.DrawOverlay(); } - ImGui::EndChild(); + EndChild(); ImGui::EndGroup(); return absl::OkStatus(); } @@ -824,19 +739,19 @@ absl::Status OverworldEditor::DrawTileSelector() { if (BeginTabBar(kTileSelectorTab.data(), ImGuiTabBarFlags_FittingPolicyScroll)) { if (BeginTabItem("Tile16")) { - RETURN_IF_ERROR(DrawTile16Selector()); + status_ = DrawTile16Selector(); EndTabItem(); } if (BeginTabItem("Tile8")) { gui::BeginPadding(3); gui::BeginChildWithScrollbar("##Tile8SelectorScrollRegion"); DrawTile8Selector(); - ImGui::EndChild(); + EndChild(); gui::EndNoPadding(); EndTabItem(); } if (BeginTabItem("Area Graphics")) { - DrawAreaGraphics(); + status_ = DrawAreaGraphics(); EndTabItem(); } EndTabBar(); @@ -844,156 +759,6 @@ absl::Status OverworldEditor::DrawTileSelector() { return absl::OkStatus(); } -// ---------------------------------------------------------------------------- - -namespace entity_internal { - -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 = false) { - // 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(mouse_pos.x) / 16 * 16; - int new_y = static_cast(mouse_pos.y) / 16 * 16; - if (free_movement) { - new_x = static_cast(mouse_pos.x) / 8 * 8; - new_y = static_cast(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 = false) { - 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_); - } -} - -} // namespace entity_internal - -namespace entrance_internal { - -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 (ImGui::Button(ICON_MD_DONE)) { - set_done = true; - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - if (ImGui::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 (ImGui::Button(ICON_MD_DONE)) { - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_CANCEL)) { - set_done = true; - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_DELETE)) { - entrance.deleted = true; - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - return set_done; -} - -} // namespace entrance_internal - void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, bool holes) { int i = 0; @@ -1009,18 +774,15 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, std::string str = core::UppercaseHexByte(each.entrance_id_); if (current_mode == EditingMode::ENTRANCES) { - entity_internal::HandleEntityDragging(&each, canvas_p0, scrolling, - is_dragging_entity_, - dragged_entity_, current_entity_); + HandleEntityDragging(&each, canvas_p0, scrolling, is_dragging_entity_, + dragged_entity_, current_entity_); - if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, - scrolling) && + if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { jump_to_tab_ = each.entrance_id_; } - if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, - scrolling) && + if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { current_entrance_id_ = i; current_entrance_ = each; @@ -1032,7 +794,7 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, i++; } - if (entrance_internal::DrawEntranceInserterPopup()) { + if (DrawEntranceInserterPopup()) { // Get the deleted entrance ID and insert it at the mouse position auto deleted_entrance_id = overworld_.deleted_entrances().back(); overworld_.deleted_entrances().pop_back(); @@ -1045,13 +807,13 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, } if (current_mode == EditingMode::ENTRANCES) { - const auto is_hovering = entity_internal::IsMouseHoveringOverEntity( - current_entrance_, canvas_p0, scrolling); + const auto is_hovering = + IsMouseHoveringOverEntity(current_entrance_, canvas_p0, scrolling); if (!is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { ImGui::OpenPopup("Entrance Inserter"); } else { - if (entrance_internal::DrawOverworldEntrancePopup( + if (DrawOverworldEntrancePopup( overworld_.entrances()[current_entrance_id_])) { overworld_.entrances()[current_entrance_id_] = current_entrance_; } @@ -1064,161 +826,6 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, } } -namespace exit_internal { - -// TODO: Implement deleting OverworldExit objects -void DrawExitInserterPopup() { - if (ImGui::BeginPopup("Exit Inserter")) { - static int exit_id = 0; - gui::InputHex("Exit ID", &exit_id); - - if (ImGui::Button(ICON_MD_DONE)) { - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - if (ImGui::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_); - ImGui::SameLine(); - gui::InputHex("Entity ID", &exit.entity_id_, 4); - gui::InputHex("Map", &exit.map_id_); - ImGui::SameLine(); - ImGui::Checkbox("Automatic", &exit.is_automatic_); - - gui::InputHex("X Positon", &exit.x_); - ImGui::SameLine(); - gui::InputHex("Y Position", &exit.y_); - - gui::InputHexByte("X Camera", &exit.x_camera_); - ImGui::SameLine(); - gui::InputHexByte("Y Camera", &exit.y_camera_); - - gui::InputHexWord("X Scroll", &exit.x_scroll_); - ImGui::SameLine(); - gui::InputHexWord("Y Scroll", &exit.y_scroll_); - - ImGui::Separator(); - - static bool show_properties = false; - ImGui::Checkbox("Show properties", &show_properties); - if (show_properties) { - ImGui::Text("Deleted? %s", exit.deleted_ ? "true" : "false"); - ImGui::Text("Hole? %s", exit.is_hole_ ? "true" : "false"); - ImGui::Text("Large Map? %s", exit.large_map_ ? "true" : "false"); - } - - gui::TextWithSeparators("Unimplemented below"); - - ImGui::RadioButton("None", &doorType, 0); - ImGui::SameLine(); - ImGui::RadioButton("Wooden", &doorType, 1); - ImGui::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); - ImGui::SameLine(); - ImGui::RadioButton("Sanctuary", &fancyDoorType, 1); - ImGui::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; - ImGui::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 (ImGui::Button(ICON_MD_DONE)) { - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - - if (ImGui::Button(ICON_MD_CANCEL)) { - set_done = true; - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_DELETE)) { - exit.deleted_ = true; - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - - return set_done; -} - -} // namespace exit_internal - void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { int i = 0; for (auto &each : *overworld_.mutable_exits()) { @@ -1228,18 +835,16 @@ void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { ImVec4(255, 255, 255, 150)); if (current_mode == EditingMode::EXITS) { each.entity_id_ = i; - entity_internal::HandleEntityDragging( - &each, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), - is_dragging_entity_, dragged_entity_, current_entity_, true); + HandleEntityDragging(&each, ow_map_canvas_.zero_point(), + ow_map_canvas_.scrolling(), is_dragging_entity_, + dragged_entity_, current_entity_, true); - if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, - scrolling) && + if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { jump_to_tab_ = each.room_id_; } - if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0, - scrolling) && + if (IsMouseHoveringOverEntity(each, canvas_p0, scrolling) && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { current_exit_id_ = i; current_exit_ = each; @@ -1255,16 +860,16 @@ void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { i++; } - exit_internal::DrawExitInserterPopup(); + DrawExitInserterPopup(); if (current_mode == EditingMode::EXITS) { - const auto hovering = entity_internal::IsMouseHoveringOverEntity( + const auto hovering = IsMouseHoveringOverEntity( overworld_.mutable_exits()->at(current_exit_id_), ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling()); if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { ImGui::OpenPopup("Exit Inserter"); } else { - if (exit_internal::DrawExitEditorPopup( + if (DrawExitEditorPopup( overworld_.mutable_exits()->at(current_exit_id_))) { overworld_.mutable_exits()->at(current_exit_id_) = current_exit_; } @@ -1272,77 +877,6 @@ void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { } } -namespace item_internal { - -void DrawItemInsertPopup() { - // Contents of the Context Menu - if (ImGui::BeginPopup("Item Inserter")) { - static int new_item_id = 0; - ImGui::Text("Add Item"); - ImGui::BeginChild("ScrollRegion", ImVec2(150, 150), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar); - for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) { - if (ImGui::Selectable(zelda3::overworld::kSecretItemNames[i].c_str(), - i == new_item_id)) { - new_item_id = i; - } - } - ImGui::EndChild(); - - if (ImGui::Button(ICON_MD_DONE)) { - // Add the new item to the overworld - new_item_id = 0; - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - - if (ImGui::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)) { - ImGui::BeginChild("ScrollRegion", ImVec2(150, 150), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar); - ImGui::BeginGroup(); - for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) { - if (ImGui::Selectable(zelda3::overworld::kSecretItemNames[i].c_str(), - item.id == i)) { - item.id = i; - } - } - ImGui::EndGroup(); - ImGui::EndChild(); - - if (ImGui::Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup(); - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_CLOSE)) { - set_done = true; - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_DELETE)) { - item.deleted = true; - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - return set_done; -} - -} // namespace item_internal - void OverworldEditor::DrawOverworldItems() { int i = 0; for (auto &item : *overworld_.mutable_all_items()) { @@ -1355,11 +889,11 @@ void OverworldEditor::DrawOverworldItems() { if (current_mode == EditingMode::ITEMS) { // Check if this item is being clicked and dragged - entity_internal::HandleEntityDragging( - &item, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), - is_dragging_entity_, dragged_entity_, current_entity_); + HandleEntityDragging(&item, ow_map_canvas_.zero_point(), + ow_map_canvas_.scrolling(), is_dragging_entity_, + dragged_entity_, current_entity_); - const auto hovering = entity_internal::IsMouseHoveringOverEntity( + const auto hovering = IsMouseHoveringOverEntity( item, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling()); if (hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { current_item_id_ = i; @@ -1372,16 +906,16 @@ void OverworldEditor::DrawOverworldItems() { i++; } - item_internal::DrawItemInsertPopup(); + DrawItemInsertPopup(); if (current_mode == EditingMode::ITEMS) { - const auto hovering = entity_internal::IsMouseHoveringOverEntity( + const auto hovering = IsMouseHoveringOverEntity( overworld_.mutable_all_items()->at(current_item_id_), ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling()); if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { ImGui::OpenPopup("Item Inserter"); } else { - if (item_internal::DrawItemEditorPopup( + if (DrawItemEditorPopup( overworld_.mutable_all_items()->at(current_item_id_))) { overworld_.mutable_all_items()->at(current_item_id_) = current_item_; } @@ -1389,165 +923,6 @@ void OverworldEditor::DrawOverworldItems() { } } -namespace sprite_internal { - -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 &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 - } -}; -const ImGuiTableSortSpecs *SpriteItem::s_current_sort_specs = nullptr; - -void DrawSpriteTable(std::function onSpriteSelect) { - static ImGuiTextFilter filter; - static int selected_id = 0; - static std::vector 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); - ImGui::Text("%d", item.id); - ImGui::TableSetColumnIndex(1); - - if (ImGui::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; - ImGui::Text("Add Sprite"); - ImGui::BeginChild("ScrollRegion", ImVec2(250, 250), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar); - sprite_internal::DrawSpriteTable( - [](int selected_id) { new_sprite_id = selected_id; }); - ImGui::EndChild(); - - if (ImGui::Button(ICON_MD_DONE)) { - // Add the new item to the overworld - new_sprite_id = 0; - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - - if (ImGui::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)) { - ImGui::BeginChild("ScrollRegion", ImVec2(350, 350), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar); - ImGui::BeginGroup(); - ImGui::Text("%s", sprite.Name().c_str()); - - sprite_internal::DrawSpriteTable([&sprite](int selected_id) { - sprite.set_id(selected_id); - sprite.UpdateMapProperties(sprite.map_id()); - }); - ImGui::EndGroup(); - ImGui::EndChild(); - - if (ImGui::Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup(); - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_CLOSE)) { - set_done = true; - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_DELETE)) { - sprite.set_deleted(true); - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); - } - return set_done; -} - -} // namespace sprite_internal - void OverworldEditor::DrawOverworldSprites() { int i = 0; for (auto &sprite : *overworld_.mutable_sprites(game_state_)) { @@ -1560,36 +935,34 @@ void OverworldEditor::DrawOverworldSprites() { ow_map_canvas_.DrawRect(map_x, map_y, 16, 16, /*magenta*/ ImVec4(255, 0, 255, 150)); if (current_mode == EditingMode::SPRITES) { - entity_internal::HandleEntityDragging( - &sprite, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), - is_dragging_entity_, dragged_entity_, current_entity_); - if (entity_internal::IsMouseHoveringOverEntity( - sprite, ow_map_canvas_.zero_point(), - ow_map_canvas_.scrolling()) && + HandleEntityDragging(&sprite, ow_map_canvas_.zero_point(), + ow_map_canvas_.scrolling(), is_dragging_entity_, + dragged_entity_, current_entity_); + if (IsMouseHoveringOverEntity(sprite, ow_map_canvas_.zero_point(), + ow_map_canvas_.scrolling()) && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { current_sprite_id_ = i; current_sprite_ = sprite; } } - ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.Name()), map_x, + ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.name()), map_x, map_y); } i++; } - sprite_internal::DrawSpriteInserterPopup(); + DrawSpriteInserterPopup(); if (current_mode == EditingMode::SPRITES) { - const auto hovering = entity_internal::IsMouseHoveringOverEntity( + const auto hovering = IsMouseHoveringOverEntity( overworld_.mutable_sprites(game_state_)->at(current_sprite_id_), ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling()); if (!hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { ImGui::OpenPopup("Sprite Inserter"); } else { - if (sprite_internal::DrawSpriteEditorPopup( - overworld_.mutable_sprites(game_state_) - ->at(current_sprite_id_))) { + if (DrawSpriteEditorPopup(overworld_.mutable_sprites(game_state_) + ->at(current_sprite_id_))) { overworld_.mutable_sprites(game_state_)->at(current_sprite_id_) = current_sprite_; } @@ -1600,19 +973,19 @@ void OverworldEditor::DrawOverworldSprites() { // ---------------------------------------------------------------------------- absl::Status OverworldEditor::LoadGraphics() { - graphics_bin_ = rom()->graphics_bin(); - // Load the Link to the Past overworld. RETURN_IF_ERROR(overworld_.Load(*rom())) palette_ = overworld_.AreaPalette(); // Create the area graphics image - rom()->CreateAndRenderBitmap(0x80, 0x200, 0x40, overworld_.current_graphics(), - current_gfx_bmp_, palette_); + RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, kOverworldMapSize, 0x40, + overworld_.current_graphics(), + current_gfx_bmp_, palette_)); // Create the tile16 blockset image - rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x08, overworld_.Tile16Blockset(), - tile16_blockset_bmp_, palette_); + RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x08, + overworld_.Tile16Blockset(), + tile16_blockset_bmp_, palette_)); map_blockset_loaded_ = true; // Copy the tile16 data into individual tiles. @@ -1650,7 +1023,8 @@ absl::Status OverworldEditor::LoadGraphics() { overworld_.set_current_map(i); auto palette = overworld_.AreaPalette(); RETURN_IF_ERROR(rom()->CreateAndRenderBitmap( - 0x200, 0x200, 0x200, overworld_.BitmapData(), maps_bmp_[i], palette)); + kOverworldMapSize, kOverworldMapSize, 0x200, overworld_.BitmapData(), + maps_bmp_[i], palette)); } if (flags()->overworld.kDrawOverworldSprites) { @@ -1660,63 +1034,6 @@ absl::Status OverworldEditor::LoadGraphics() { return absl::OkStatus(); } -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 - RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(0x80, 0x2000, 0x08, - overworld_.Tile16Blockset(), - tile16_blockset_bmp_, palette_)); - - // Copy the tile16 data into individual tiles. - auto tile16_data = overworld_.Tile16Blockset(); - - std::vector> 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(); -} - absl::Status OverworldEditor::LoadSpriteGraphics() { // Render the sprites for each Overworld map for (int i = 0; i < 3; i++) @@ -1738,26 +1055,93 @@ void OverworldEditor::DrawOverworldProperties() { if (!init_properties) { for (int i = 0; i < 0x40; i++) { std::string area_graphics_str = absl::StrFormat( - "0x%02hX", overworld_.overworld_map(i)->area_graphics()); + "%02hX", overworld_.overworld_map(i)->area_graphics()); properties_canvas_.mutable_labels(0)->push_back(area_graphics_str); + area_graphics_str = absl::StrFormat( + "%02hX", overworld_.overworld_map(i + 0x40)->area_graphics()); + properties_canvas_.mutable_labels(3)->push_back(area_graphics_str); } for (int i = 0; i < 0x40; i++) { - std::string area_palette_str = absl::StrFormat( - "0x%02hX", overworld_.overworld_map(i)->area_palette()); + std::string area_palette_str = + absl::StrFormat("%02hX", overworld_.overworld_map(i)->area_palette()); properties_canvas_.mutable_labels(1)->push_back(area_palette_str); + area_palette_str = absl::StrFormat( + "%02hX", overworld_.overworld_map(i + 0x40)->area_palette()); + properties_canvas_.mutable_labels(4)->push_back(area_palette_str); + } + for (int i = 0; i < 0x40; i++) { + std::string sprite_gfx_str = absl::StrFormat( + "%02hX", overworld_.overworld_map(i)->sprite_graphics(1)); + properties_canvas_.mutable_labels(6)->push_back(sprite_gfx_str); + sprite_gfx_str = absl::StrFormat( + "%02hX", overworld_.overworld_map(i + 0x40)->sprite_graphics(1)); + properties_canvas_.mutable_labels(7)->push_back(sprite_gfx_str); + } + for (int i = 0; i < 0x40; i++) { + std::string sprite_palette_str = absl::StrFormat( + "%02hX", overworld_.overworld_map(i)->sprite_palette(1)); + properties_canvas_.mutable_labels(2)->push_back(sprite_palette_str); + sprite_palette_str = absl::StrFormat( + "%02hX", overworld_.overworld_map(i + 0x40)->sprite_palette(1)); + properties_canvas_.mutable_labels(5)->push_back(sprite_palette_str); } init_properties = true; } - if (ImGui::Button("Area Graphics")) { - properties_canvas_.set_current_labels(0); + Text("Area Gfx LW/DW"); + properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 1.0f, 32, 0); + SameLine(); + properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 1.0f, 32, 1); + ImGui::Separator(); + + Text("Sprite Gfx LW/DW"); + properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 1.0f, 32, 6); + SameLine(); + properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 1.0f, 32, 7); + ImGui::Separator(); + + Text("Area Pal LW/DW"); + properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 1.0f, 32, 2); + SameLine(); + properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 8, 1.0f, 32, 3); + + static bool dark_world = false; + int world = dark_world ? 3 : 0; + static int current = 0; + + static bool show_gfx_group = false; + Checkbox("Show Gfx Group Editor", &show_gfx_group); + if (Checkbox("Dark World", &dark_world)) { + properties_canvas_.set_current_labels(current + world); + } + SameLine(); + if (Button("Area Graphics")) { + current = 0; + properties_canvas_.set_current_labels(current + world); + } + SameLine(); + if (Button("Area Palette")) { + current = 1; + properties_canvas_.set_current_labels(current + world); + } + if (Button("Sprite Graphics")) { + if (dark_world) { + properties_canvas_.set_current_labels(7); + } else { + properties_canvas_.set_current_labels(6); + } + } + SameLine(); + if (Button("Sprite Palette")) { + current = 2; + properties_canvas_.set_current_labels(current + world); } - if (ImGui::Button("Area Palette")) { - properties_canvas_.set_current_labels(1); + if (show_gfx_group) { + gui::BeginWindowWithDisplaySettings("Gfx Group Editor", &show_gfx_group); + status_ = gfx_group_editor_.Update(); + gui::EndWindowWithDisplaySettings(); } - - properties_canvas_.UpdateInfoGrid(ImVec2(512, 512), 16, 1.0f, 64); } absl::Status OverworldEditor::DrawExperimentalModal() { @@ -1776,7 +1160,7 @@ absl::Status OverworldEditor::DrawExperimentalModal() { "the tilemap into the editor"); ImGui::InputText("##TilemapFile", &ow_tilemap_filename_); - ImGui::SameLine(); + SameLine(); gui::FileDialogPipeline( "ImportTilemapsKey", ".DAT,.dat\0", "Tilemap Hex File", [this]() { ow_tilemap_filename_ = ImGuiFileDialog::Instance()->GetFilePathName(); @@ -1784,14 +1168,14 @@ absl::Status OverworldEditor::DrawExperimentalModal() { ImGui::InputText("##Tile32ConfigurationFile", &tile32_configuration_filename_); - ImGui::SameLine(); + SameLine(); gui::FileDialogPipeline("ImportTile32Key", ".DAT,.dat\0", "Tile32 Hex File", [this]() { tile32_configuration_filename_ = ImGuiFileDialog::Instance()->GetFilePathName(); }); - if (ImGui::Button("Load Prototype Overworld with ROM graphics")) { + if (Button("Load Prototype Overworld with ROM graphics")) { RETURN_IF_ERROR(LoadGraphics()) all_gfx_loaded_ = true; } @@ -1810,7 +1194,7 @@ absl::Status OverworldEditor::DrawExperimentalModal() { } absl::Status OverworldEditor::UpdateUsageStats() { - if (BeginTable("##UsageStatsTable", 3, kOWEditFlags, ImVec2(0, 0))) { + if (BeginTable("UsageStatsTable", 3, kOWEditFlags, ImVec2(0, 0))) { TableSetupColumn("Entrances"); TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch, ImGui::GetContentRegionAvail().x); @@ -1819,26 +1203,40 @@ absl::Status OverworldEditor::UpdateUsageStats() { TableNextRow(); TableNextColumn(); - ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true, - ImGuiWindowFlags_HorizontalScrollbar); - for (int i = 0; i < 0x81; i++) { - std::string str = absl::StrFormat("%#x", i); - if (ImGui::Selectable(str.c_str(), selected_entrance_ == i, - overworld_.entrances().at(i).deleted - ? ImGuiSelectableFlags_Disabled - : 0)) { - selected_entrance_ = i; - selected_usage_map_ = overworld_.entrances().at(i).map_id_; - properties_canvas_.set_highlight_tile_id(selected_usage_map_); + if (BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true, + ImGuiWindowFlags_HorizontalScrollbar)) { + for (int i = 0; i < 0x81; i++) { + std::string str = absl::StrFormat("%#x", i); + if (Selectable(str.c_str(), selected_entrance_ == i, + overworld_.entrances().at(i).deleted + ? ImGuiSelectableFlags_Disabled + : 0)) { + selected_entrance_ = i; + selected_usage_map_ = overworld_.entrances().at(i).map_id_; + properties_canvas_.set_highlight_tile_id(selected_usage_map_); + } + if (IsItemHovered()) { + BeginTooltip(); + Text("Entrance ID: %d", i); + Text("Map ID: %d", overworld_.entrances().at(i).map_id_); + Text("Entrance ID: %d", overworld_.entrances().at(i).entrance_id_); + Text("X: %d", overworld_.entrances().at(i).x_); + Text("Y: %d", overworld_.entrances().at(i).y_); + Text("Deleted? %s", + overworld_.entrances().at(i).deleted ? "Yes" : "No"); + EndTooltip(); + } } + EndChild(); } - ImGui::EndChild(); TableNextColumn(); DrawUsageGrid(); + TableNextColumn(); DrawOverworldProperties(); - ImGui::EndTable(); + + EndTable(); } return absl::OkStatus(); } @@ -1862,7 +1260,7 @@ void OverworldEditor::DrawUsageGrid() { // Loop through each row for (int row = 0; row < squaresTall; ++row) { - ImGui::NewLine(); + NewLine(); for (int col = 0; col < squaresWide; ++col) { if (row * squaresWide + col >= totalSquares) { @@ -1873,13 +1271,13 @@ void OverworldEditor::DrawUsageGrid() { // Set highlight color if needed if (highlight) { - ImGui::PushStyleColor( + PushStyleColor( ImGuiCol_Button, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color } // Create a button or selectable for each square - if (ImGui::Button("##square", ImVec2(20, 20))) { + if (Button("##square", ImVec2(20, 20))) { // Switch over to the room editor tab // and add a room tab by the ID of the square // that was clicked @@ -1887,16 +1285,16 @@ void OverworldEditor::DrawUsageGrid() { // Reset style if it was highlighted if (highlight) { - ImGui::PopStyleColor(); + PopStyleColor(); } // Check if the square is hovered - if (ImGui::IsItemHovered()) { + if (IsItemHovered()) { // Display a tooltip with all the room properties } // Keep squares in the same line - ImGui::SameLine(); + SameLine(); } } } @@ -1921,8 +1319,8 @@ absl::Status OverworldEditor::LoadAnimatedMaps() { RETURN_IF_ERROR(map.BuildBitmap(blockset)); RETURN_IF_ERROR(rom()->CreateAndRenderBitmap( - 0x200, 0x200, 0x200, map.bitmap_data(), animated_maps_[world_index], - *map.mutable_current_palette())); + kOverworldMapSize, kOverworldMapSize, 0x200, map.bitmap_data(), + animated_maps_[world_index], *map.mutable_current_palette())); animated_built[world_index] = true; } @@ -1933,25 +1331,25 @@ absl::Status OverworldEditor::LoadAnimatedMaps() { // ---------------------------------------------------------------------------- void OverworldEditor::DrawDebugWindow() { - ImGui::Text("Current Map: %d", current_map_); - ImGui::Text("Current Tile16: %d", current_tile16_); + Text("Current Map: %d", current_map_); + Text("Current Tile16: %d", current_tile16_); int relative_x = (int)ow_map_canvas_.drawn_tile_position().x % 512; int relative_y = (int)ow_map_canvas_.drawn_tile_position().y % 512; - ImGui::Text("Current Tile16 Drawn Position (Relative): %d, %d", relative_x, - relative_y); + Text("Current Tile16 Drawn Position (Relative): %d, %d", relative_x, + relative_y); // Print the size of the overworld map_tiles per world - ImGui::Text("Light World Map Tiles: %d", - (int)overworld_.mutable_map_tiles()->light_world.size()); - ImGui::Text("Dark World Map Tiles: %d", - (int)overworld_.mutable_map_tiles()->dark_world.size()); - ImGui::Text("Special World Map Tiles: %d", - (int)overworld_.mutable_map_tiles()->special_world.size()); + Text("Light World Map Tiles: %d", + (int)overworld_.mutable_map_tiles()->light_world.size()); + Text("Dark World Map Tiles: %d", + (int)overworld_.mutable_map_tiles()->dark_world.size()); + Text("Special World Map Tiles: %d", + (int)overworld_.mutable_map_tiles()->special_world.size()); static bool view_lw_map_tiles = false; static MemoryEditor mem_edit; // Let's create buttons which let me view containers in the memory editor - if (ImGui::Button("View Light World Map Tiles")) { + if (Button("View Light World Map Tiles")) { view_lw_map_tiles = !view_lw_map_tiles; } @@ -1964,4 +1362,4 @@ void OverworldEditor::DrawDebugWindow() { } // namespace editor } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/editor/overworld_editor.h b/src/app/editor/overworld_editor.h index 9008486f..bd45c76e 100644 --- a/src/app/editor/overworld_editor.h +++ b/src/app/editor/overworld_editor.h @@ -1,8 +1,7 @@ #ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H #define YAZE_APP_EDITOR_OVERWORLDEDITOR_H -#include -#include +#include "imgui/imgui.h" #include #include @@ -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 entrance_tile_types_low_; + std::vector 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 diff --git a/src/app/editor/settings_editor.cc b/src/app/editor/settings_editor.cc new file mode 100644 index 00000000..3539419a --- /dev/null +++ b/src/app/editor/settings_editor.cc @@ -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 diff --git a/src/app/editor/settings_editor.h b/src/app/editor/settings_editor.h new file mode 100644 index 00000000..32c6e382 --- /dev/null +++ b/src/app/editor/settings_editor.h @@ -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 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("", ++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_ diff --git a/src/app/editor/sprite/sprite_editor.cc b/src/app/editor/sprite/sprite_editor.cc new file mode 100644 index 00000000..bd03da0f --- /dev/null +++ b/src/app/editor/sprite/sprite_editor.cc @@ -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 diff --git a/src/app/editor/sprite/sprite_editor.h b/src/app/editor/sprite/sprite_editor.h new file mode 100644 index 00000000..0cc6e019 --- /dev/null +++ b/src/app/editor/sprite/sprite_editor.h @@ -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 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 custom_sprites_; /**< Sprites. */ + + absl::Status status_; /**< Status. */ +}; + +} // namespace editor +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H \ No newline at end of file diff --git a/src/app/editor/sprite/zsprite.h b/src/app/editor/sprite/zsprite.h new file mode 100644 index 00000000..19717403 --- /dev/null +++ b/src/app/editor/sprite/zsprite.h @@ -0,0 +1,396 @@ +#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H +#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H + +#include "imgui/imgui.h" + +#include +#include +#include +#include +#include + +#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 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 Frames; + std::vector 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 buffer(std::istreambuf_iterator(fs), {}); + + int animation_count = *reinterpret_cast(&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(&buffer[offset]); + offset += sizeof(uint8_t); + uint8_t afe = *reinterpret_cast(&buffer[offset]); + offset += sizeof(uint8_t); + uint8_t afspeed = *reinterpret_cast(&buffer[offset]); + offset += sizeof(uint8_t); + + animations.push_back(AnimationGroup(afs, afe, afspeed, aname)); + } + // RefreshAnimations(); + + int frame_count = *reinterpret_cast(&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(&buffer[offset]); + offset += sizeof(int); + + for (int j = 0; j < tCount; j++) { + ushort tid = *reinterpret_cast(&buffer[offset]); + offset += sizeof(ushort); + uint8_t tpal = *reinterpret_cast(&buffer[offset]); + offset += sizeof(uint8_t); + bool tmx = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + bool tmy = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + uint8_t tprior = *reinterpret_cast(&buffer[offset]); + offset += sizeof(uint8_t); + bool tsize = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + uint8_t tx = *reinterpret_cast(&buffer[offset]); + offset += sizeof(uint8_t); + uint8_t ty = *reinterpret_cast(&buffer[offset]); + offset += sizeof(uint8_t); + uint8_t tz = *reinterpret_cast(&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(&buffer[offset]); + offset += sizeof(bool); + property_canfall.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_collisionlayer.IsChecked = + *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_customdeath.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_damagesound.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_deflectarrows.IsChecked = + *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_deflectprojectiles.IsChecked = + *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_fast.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_harmless.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_impervious.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_imperviousarrow.IsChecked = + *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_imperviousmelee.IsChecked = + *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_interaction.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_isboss.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_persist.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_shadow.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_smallshadow.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_statis.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_statue.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + property_watersprite.IsChecked = *reinterpret_cast(&buffer[offset]); + offset += sizeof(bool); + + property_prize.Text = + std::to_string(*reinterpret_cast(&buffer[offset])); + offset += sizeof(uint8_t); + property_palette.Text = + std::to_string(*reinterpret_cast(&buffer[offset])); + offset += sizeof(uint8_t); + property_oamnbr.Text = + std::to_string(*reinterpret_cast(&buffer[offset])); + offset += sizeof(uint8_t); + property_hitbox.Text = + std::to_string(*reinterpret_cast(&buffer[offset])); + offset += sizeof(uint8_t); + property_health.Text = + std::to_string(*reinterpret_cast(&buffer[offset])); + offset += sizeof(uint8_t); + property_damage.Text = + std::to_string(*reinterpret_cast(&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(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(&anim.frame_start), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&anim.frame_end), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&anim.frame_speed), + sizeof(uint8_t)); + } + + fs.write(reinterpret_cast(editor.Frames.size()), + sizeof(int)); + for (int i = 0; i < editor.Frames.size(); i++) { + fs.write(reinterpret_cast(editor.Frames[i].Tiles.size()), + sizeof(int)); + + for (int j = 0; j < editor.Frames[i].Tiles.size(); j++) { + fs.write(reinterpret_cast(&editor.Frames[i].Tiles[j].id), + sizeof(ushort)); + fs.write( + reinterpret_cast(&editor.Frames[i].Tiles[j].palette), + sizeof(uint8_t)); + fs.write(reinterpret_cast( + &editor.Frames[i].Tiles[j].mirror_x), + sizeof(bool)); + fs.write(reinterpret_cast( + &editor.Frames[i].Tiles[j].mirror_y), + sizeof(bool)); + fs.write(reinterpret_cast( + &editor.Frames[i].Tiles[j].priority), + sizeof(uint8_t)); + fs.write( + reinterpret_cast(&editor.Frames[i].Tiles[j].size), + sizeof(bool)); + fs.write(reinterpret_cast(&editor.Frames[i].Tiles[j].x), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&editor.Frames[i].Tiles[j].y), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&editor.Frames[i].Tiles[j].z), + sizeof(uint8_t)); + } + } + + // Write other properties + fs.write(reinterpret_cast(&property_blockable.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_canfall.IsChecked), + sizeof(bool)); + fs.write( + reinterpret_cast(&property_collisionlayer.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_customdeath.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_damagesound.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_deflectarrows.IsChecked), + sizeof(bool)); + fs.write( + reinterpret_cast(&property_deflectprojectiles.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_fast.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_harmless.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_impervious.IsChecked), + sizeof(bool)); + fs.write( + reinterpret_cast(&property_imperviousarrow.IsChecked), + sizeof(bool)); + fs.write( + reinterpret_cast(&property_imperviousmelee.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_interaction.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_isboss.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_persist.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_shadow.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_smallshadow.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_statis.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_statue.IsChecked), + sizeof(bool)); + fs.write(reinterpret_cast(&property_watersprite.IsChecked), + sizeof(bool)); + + fs.write(reinterpret_cast(&property_prize.Text), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&property_palette.Text), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&property_oamnbr.Text), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&property_hitbox.Text), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&property_health.Text), + sizeof(uint8_t)); + fs.write(reinterpret_cast(&property_damage.Text), + sizeof(uint8_t)); + + fs.write(sprName.c_str(), sprName.size() + 1); + + fs.write(reinterpret_cast(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(&property_sprid.Text), + sizeof(property_sprid.Text)); + + fs.close(); + } + + return absl::OkStatus(); + } + + std::string sprName; + std::vector animations; + std::vector 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 diff --git a/src/app/editor/sprite_editor.cc b/src/app/editor/sprite_editor.cc deleted file mode 100644 index 7083f3b3..00000000 --- a/src/app/editor/sprite_editor.cc +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/app/editor/sprite_editor.h b/src/app/editor/sprite_editor.h deleted file mode 100644 index 3e6d24f1..00000000 --- a/src/app/editor/sprite_editor.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/app/editor/utils/editor.h b/src/app/editor/utils/editor.h index 805bc168..872388ed 100644 --- a/src/app/editor/utils/editor.h +++ b/src/app/editor/utils/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 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 diff --git a/src/app/editor/utils/flags.h b/src/app/editor/utils/flags.h new file mode 100644 index 00000000..bde5bc19 --- /dev/null +++ b/src/app/editor/utils/flags.h @@ -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_ diff --git a/src/app/editor/context/gfx_context.cc b/src/app/editor/utils/gfx_context.cc similarity index 74% rename from src/app/editor/context/gfx_context.cc rename to src/app/editor/utils/gfx_context.cc index 8c8665e1..8d829552 100644 --- a/src/app/editor/context/gfx_context.cc +++ b/src/app/editor/utils/gfx_context.cc @@ -1,17 +1,16 @@ -#include "app/editor/context/gfx_context.h" +#include "app/editor/utils/gfx_context.h" -#include +#include "imgui/imgui.h" #include +#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 { diff --git a/src/app/editor/context/gfx_context.h b/src/app/editor/utils/gfx_context.h similarity index 84% rename from src/app/editor/context/gfx_context.h rename to src/app/editor/utils/gfx_context.h index f644b233..d3f8992e 100644 --- a/src/app/editor/context/gfx_context.h +++ b/src/app/editor/utils/gfx_context.h @@ -1,19 +1,17 @@ #ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H #define YAZE_APP_EDITOR_VRAM_CONTEXT_H -#include +#include "imgui/imgui.h" #include #include #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 palettesets_; diff --git a/src/app/editor/utils/keyboard_shortcuts.h b/src/app/editor/utils/keyboard_shortcuts.h new file mode 100644 index 00000000..bb70ccf7 --- /dev/null +++ b/src/app/editor/utils/keyboard_shortcuts.h @@ -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_ diff --git a/src/app/editor/utils/recent_files.h b/src/app/editor/utils/recent_files.h new file mode 100644 index 00000000..2b0c7f6c --- /dev/null +++ b/src/app/editor/utils/recent_files.h @@ -0,0 +1,64 @@ +#ifndef YAZE_APP_EDITOR_UTILS_RECENT_FILES_H +#define YAZE_APP_EDITOR_UTILS_RECENT_FILES_H + +#include +#include +#include +#include + +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& GetRecentFiles() const { + return recentFiles_; + } + + private: + std::string filename_; + std::vector recentFiles_; +}; + +} // namespace editor +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_EDITOR_UTILS_RECENT_FILES_H \ No newline at end of file diff --git a/src/app/emu/CMakeLists.txt b/src/app/emu/CMakeLists.txt new file mode 100644 index 00000000..844e76f4 --- /dev/null +++ b/src/app/emu/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/src/app/emu/audio/apu.cc b/src/app/emu/audio/apu.cc index 6dbad24f..080fa422 100644 --- a/src/app/emu/audio/apu.cc +++ b/src/app/emu/audio/apu.cc @@ -1,5 +1,7 @@ #include "app/emu/audio/apu.h" +#include + #include #include #include @@ -15,127 +17,191 @@ namespace app { namespace emu { namespace audio { +static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0); +static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312 * 50.0); + +static const uint8_t bootRom[0x40] = { + 0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa, + 0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19, + 0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb, + 0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e, + 0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4, + 0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff}; + void Apu::Init() { - // Set the clock frequency - clock_.SetFrequency(kApuClockSpeed); - - // Initialize Digital Signal Processor Callbacks - dsp_.SetSampleFetcher([this](uint16_t address) -> uint8_t { - return this->FetchSampleFromRam(address); - }); - - dsp_.SetSamplePusher( - [this](int16_t sample) { this->PushToAudioBuffer(sample); }); + ram.resize(0x10000); + for (int i = 0; i < 0x10000; i++) { + ram[i] = 0; + } + // Copy the boot rom into the ram at ffc0 + for (int i = 0; i < 0x40; i++) { + ram[0xffc0 + i] = bootRom[i]; + } } void Apu::Reset() { - clock_.ResetAccumulatedTime(); - spc700_.Reset(); + spc700_.Reset(true); dsp_.Reset(); + for (int i = 0; i < 0x10000; i++) { + ram[i] = 0; + } + // Copy the boot rom into the ram at ffc0 + for (int i = 0; i < 0x40; i++) { + ram[0xffc0 + i] = bootRom[i]; + } + rom_readable_ = true; + dsp_adr_ = 0; + cycles_ = 0; + std::fill(in_ports_.begin(), in_ports_.end(), 0); + std::fill(out_ports_.begin(), out_ports_.end(), 0); + for (int i = 0; i < 3; i++) { + timer_[i].cycles = 0; + timer_[i].divider = 0; + timer_[i].target = 0; + timer_[i].counter = 0; + timer_[i].enabled = false; + } } -void Apu::Update() { - auto cycles_to_run = clock_.GetCycleCount(); +void Apu::RunCycles(uint64_t cycles) { + uint64_t sync_to = + (uint64_t)cycles * + (memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster); - for (auto i = 0; i < cycles_to_run; ++i) { - // Update the Apu - UpdateChannelSettings(); - - // Update the SPC700 - uint8_t opcode = spc700_.read(spc700_.PC); - spc700_.ExecuteInstructions(opcode); - spc700_.PC++; + while (cycles_ < sync_to) { + spc700_.RunOpcode(); } - - ProcessSamples(); } -void Apu::Notify(uint32_t address, uint8_t data) { - if (address < 0x2140 || address > 0x2143) { - return; +void Apu::Cycle() { + if ((cycles_ & 0x1f) == 0) { + // every 32 cycles + dsp_.Cycle(); } - auto offset = address - 0x2140; - spc700_.write(offset, data); - // HACK - This is a temporary solution to get the Apu to play audio - ports_[address - 0x2140] = data; - switch (address) { - case 0x2140: - if (data == BEGIN_SIGNAL) { - SignalReady(); + // handle timers + for (int i = 0; i < 3; i++) { + if (timer_[i].cycles == 0) { + timer_[i].cycles = i == 2 ? 16 : 128; + if (timer_[i].enabled) { + timer_[i].divider++; + if (timer_[i].divider == timer_[i].target) { + timer_[i].divider = 0; + timer_[i].counter++; + timer_[i].counter &= 0xf; + } } - break; - case 0x2141: - // TODO: Handle data byte transfer here - break; - case 0x2142: - // TODO: Handle the setup of destination address - break; - case 0x2143: - // TODO: Handle additional communication/commands - break; + } + timer_[i].cycles--; } + + cycles_++; } -void Apu::ProcessSamples() { - // Fetch sample data from AudioRam - // Iterate over all voices - for (uint8_t voice_num = 0; voice_num < 8; voice_num++) { - // Fetch the sample data for the current voice from AudioRam - uint8_t sample = FetchSampleForVoice(voice_num); - - // Process the sample through DSP - int16_t processed_sample = dsp_.ProcessSample(voice_num, sample); - - // Add the processed sample to the audio buffer - audio_samples_.push_back(processed_sample); +uint8_t Apu::Read(uint16_t adr) { + switch (adr) { + case 0xf0: + case 0xf1: + case 0xfa: + case 0xfb: + case 0xfc: { + return 0; + } + case 0xf2: { + return dsp_adr_; + } + case 0xf3: { + return dsp_.Read(dsp_adr_ & 0x7f); + } + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: + case 0xf8: + case 0xf9: { + return in_ports_[adr - 0xf4]; + } + case 0xfd: + case 0xfe: + case 0xff: { + uint8_t ret = timer_[adr - 0xfd].counter; + timer_[adr - 0xfd].counter = 0; + return ret; + } } -} - -uint8_t Apu::FetchSampleForVoice(uint8_t voice_num) { - uint16_t address = CalculateAddressForVoice(voice_num); - return aram_.read(address); -} - -uint16_t Apu::CalculateAddressForVoice(uint8_t voice_num) { - // TODO: Calculate the address for the specified voice - return voice_num; -} - -int16_t Apu::GetNextSample() { - if (!audio_samples_.empty()) { - int16_t sample = audio_samples_.front(); - audio_samples_.erase(audio_samples_.begin()); - return sample; + if (rom_readable_ && adr >= 0xffc0) { + return bootRom[adr - 0xffc0]; } - return 0; // TODO: Return the last sample instead of 0. + return ram[adr]; } -const std::vector& Apu::GetAudioSamples() const { - return audio_samples_; +void Apu::Write(uint16_t adr, uint8_t val) { + switch (adr) { + case 0xf0: { + break; // test register + } + case 0xf1: { + for (int i = 0; i < 3; i++) { + if (!timer_[i].enabled && (val & (1 << i))) { + timer_[i].divider = 0; + timer_[i].counter = 0; + } + timer_[i].enabled = val & (1 << i); + } + if (val & 0x10) { + in_ports_[0] = 0; + in_ports_[1] = 0; + } + if (val & 0x20) { + in_ports_[2] = 0; + in_ports_[3] = 0; + } + rom_readable_ = val & 0x80; + break; + } + case 0xf2: { + dsp_adr_ = val; + break; + } + case 0xf3: { + if (dsp_adr_ < 0x80) dsp_.Write(dsp_adr_, val); + break; + } + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: { + out_ports_[adr - 0xf4] = val; + break; + } + case 0xf8: + case 0xf9: { + in_ports_[adr - 0xf4] = val; + break; + } + case 0xfa: + case 0xfb: + case 0xfc: { + timer_[adr - 0xfa].target = val; + break; + } + } + ram[adr] = val; } -void Apu::UpdateChannelSettings() { - // TODO: Implement this method to update the channel settings. +uint8_t Apu::SpcRead(uint16_t adr) { + Cycle(); + return Read(adr); } -int16_t Apu::GenerateSample(int channel) { - // TODO: Implement this method to generate a sample for the specified channel. +void Apu::SpcWrite(uint16_t adr, uint8_t val) { + Cycle(); + Write(adr, val); } -void Apu::ApplyEnvelope(int channel) { - // TODO: Implement this method to apply an envelope to the specified channel. -} +void Apu::SpcIdle(bool waiting) { Cycle(); } -uint8_t Apu::ReadDspMemory(uint16_t address) { - return dsp_.ReadGlobalReg(address); -} - -void Apu::WriteDspMemory(uint16_t address, uint8_t value) { - dsp_.WriteGlobalReg(address, value); -} - -} // namespace audio +} // namespace audio } // namespace emu } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/emu/audio/apu.h b/src/app/emu/audio/apu.h index adea4552..157b9696 100644 --- a/src/app/emu/audio/apu.h +++ b/src/app/emu/audio/apu.h @@ -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 &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 in_ports_; // includes 2 bytes of ram + std::array out_ports_; + std::vector ram = std::vector(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_; - DigitalSignalProcessor dsp_; - Spc700 spc700_{aram_}; - std::vector audio_samples_; - - std::function 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 diff --git a/src/app/emu/audio/dsp.cc b/src/app/emu/audio/dsp.cc index 61e1426f..2bef50d5 100644 --- a/src/app/emu/audio/dsp.cc +++ b/src/app/emu/audio/dsp.cc @@ -1,5 +1,7 @@ #include "app/emu/audio/dsp.h" +#include + #include "app/emu/memory/memory.h" namespace yaze { @@ -7,280 +9,626 @@ namespace app { namespace emu { namespace audio { -void DigitalSignalProcessor::Reset() {} +static const int rateValues[32] = {0, 2048, 1536, 1280, 1024, 768, 640, 512, + 384, 320, 256, 192, 160, 128, 96, 80, + 64, 48, 40, 32, 24, 20, 16, 12, + 10, 8, 6, 5, 4, 3, 2, 1}; -uint8_t DigitalSignalProcessor::ReadVoiceReg(uint8_t voice, uint8_t reg) const { - voice %= kNumVoices; - switch (reg % kNumVoiceRegs) { - case 0: - return voices_[voice].vol_left; - case 1: - return voices_[voice].vol_right; - case 2: - return voices_[voice].pitch_low; - case 3: - return voices_[voice].pitch_high; - case 4: - return voices_[voice].source_number; - case 5: - return voices_[voice].adsr1; - case 6: - return voices_[voice].adsr2; - case 7: - return voices_[voice].gain; - case 8: - return voices_[voice].envx; - case 9: - return voices_[voice].outx; - default: - return 0; // This shouldn't happen, but it's good to have a default - // case +static const int rateOffsets[32] = {0, 0, 1040, 536, 0, 1040, 536, 0, 1040, + 536, 0, 1040, 536, 0, 1040, 536, 0, 1040, + 536, 0, 1040, 536, 0, 1040, 536, 0, 1040, + 536, 0, 1040, 536, 0}; + +static const int gaussValues[512] = { + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002, + 0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004, + 0x005, 0x005, 0x005, 0x005, 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007, + 0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00a, 0x00a, 0x00a, 0x00b, 0x00b, + 0x00b, 0x00c, 0x00c, 0x00d, 0x00d, 0x00e, 0x00e, 0x00f, 0x00f, 0x00f, 0x010, + 0x010, 0x011, 0x011, 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016, + 0x017, 0x017, 0x018, 0x018, 0x019, 0x01a, 0x01b, 0x01b, 0x01c, 0x01d, 0x01d, + 0x01e, 0x01f, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026, + 0x027, 0x028, 0x029, 0x02a, 0x02b, 0x02c, 0x02d, 0x02e, 0x02f, 0x030, 0x031, + 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x03a, 0x03b, 0x03c, 0x03d, + 0x03e, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04a, 0x04c, + 0x04d, 0x04e, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05a, 0x05c, + 0x05e, 0x05f, 0x061, 0x063, 0x064, 0x066, 0x068, 0x06a, 0x06b, 0x06d, 0x06f, + 0x071, 0x073, 0x075, 0x076, 0x078, 0x07a, 0x07c, 0x07e, 0x080, 0x082, 0x084, + 0x086, 0x089, 0x08b, 0x08d, 0x08f, 0x091, 0x093, 0x096, 0x098, 0x09a, 0x09c, + 0x09f, 0x0a1, 0x0a3, 0x0a6, 0x0a8, 0x0ab, 0x0ad, 0x0af, 0x0b2, 0x0b4, 0x0b7, + 0x0ba, 0x0bc, 0x0bf, 0x0c1, 0x0c4, 0x0c7, 0x0c9, 0x0cc, 0x0cf, 0x0d2, 0x0d4, + 0x0d7, 0x0da, 0x0dd, 0x0e0, 0x0e3, 0x0e6, 0x0e9, 0x0ec, 0x0ef, 0x0f2, 0x0f5, + 0x0f8, 0x0fb, 0x0fe, 0x101, 0x104, 0x107, 0x10b, 0x10e, 0x111, 0x114, 0x118, + 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, 0x130, 0x133, 0x137, 0x13a, 0x13e, + 0x141, 0x145, 0x148, 0x14c, 0x150, 0x153, 0x157, 0x15b, 0x15f, 0x162, 0x166, + 0x16a, 0x16e, 0x172, 0x176, 0x17a, 0x17d, 0x181, 0x185, 0x189, 0x18d, 0x191, + 0x195, 0x19a, 0x19e, 0x1a2, 0x1a6, 0x1aa, 0x1ae, 0x1b2, 0x1b7, 0x1bb, 0x1bf, + 0x1c3, 0x1c8, 0x1cc, 0x1d0, 0x1d5, 0x1d9, 0x1dd, 0x1e2, 0x1e6, 0x1eb, 0x1ef, + 0x1f3, 0x1f8, 0x1fc, 0x201, 0x205, 0x20a, 0x20f, 0x213, 0x218, 0x21c, 0x221, + 0x226, 0x22a, 0x22f, 0x233, 0x238, 0x23d, 0x241, 0x246, 0x24b, 0x250, 0x254, + 0x259, 0x25e, 0x263, 0x267, 0x26c, 0x271, 0x276, 0x27b, 0x280, 0x284, 0x289, + 0x28e, 0x293, 0x298, 0x29d, 0x2a2, 0x2a6, 0x2ab, 0x2b0, 0x2b5, 0x2ba, 0x2bf, + 0x2c4, 0x2c9, 0x2ce, 0x2d3, 0x2d8, 0x2dc, 0x2e1, 0x2e6, 0x2eb, 0x2f0, 0x2f5, + 0x2fa, 0x2ff, 0x304, 0x309, 0x30e, 0x313, 0x318, 0x31d, 0x322, 0x326, 0x32b, + 0x330, 0x335, 0x33a, 0x33f, 0x344, 0x349, 0x34e, 0x353, 0x357, 0x35c, 0x361, + 0x366, 0x36b, 0x370, 0x374, 0x379, 0x37e, 0x383, 0x388, 0x38c, 0x391, 0x396, + 0x39b, 0x39f, 0x3a4, 0x3a9, 0x3ad, 0x3b2, 0x3b7, 0x3bb, 0x3c0, 0x3c5, 0x3c9, + 0x3ce, 0x3d2, 0x3d7, 0x3dc, 0x3e0, 0x3e5, 0x3e9, 0x3ed, 0x3f2, 0x3f6, 0x3fb, + 0x3ff, 0x403, 0x408, 0x40c, 0x410, 0x415, 0x419, 0x41d, 0x421, 0x425, 0x42a, + 0x42e, 0x432, 0x436, 0x43a, 0x43e, 0x442, 0x446, 0x44a, 0x44e, 0x452, 0x455, + 0x459, 0x45d, 0x461, 0x465, 0x468, 0x46c, 0x470, 0x473, 0x477, 0x47a, 0x47e, + 0x481, 0x485, 0x488, 0x48c, 0x48f, 0x492, 0x496, 0x499, 0x49c, 0x49f, 0x4a2, + 0x4a6, 0x4a9, 0x4ac, 0x4af, 0x4b2, 0x4b5, 0x4b7, 0x4ba, 0x4bd, 0x4c0, 0x4c3, + 0x4c5, 0x4c8, 0x4cb, 0x4cd, 0x4d0, 0x4d2, 0x4d5, 0x4d7, 0x4d9, 0x4dc, 0x4de, + 0x4e0, 0x4e3, 0x4e5, 0x4e7, 0x4e9, 0x4eb, 0x4ed, 0x4ef, 0x4f1, 0x4f3, 0x4f5, + 0x4f6, 0x4f8, 0x4fa, 0x4fb, 0x4fd, 0x4ff, 0x500, 0x502, 0x503, 0x504, 0x506, + 0x507, 0x508, 0x50a, 0x50b, 0x50c, 0x50d, 0x50e, 0x50f, 0x510, 0x511, 0x511, + 0x512, 0x513, 0x514, 0x514, 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518, + 0x518, 0x518, 0x518, 0x518, 0x519, 0x519}; + +void Dsp::Reset() { + memset(ram, 0, sizeof(ram)); + ram[0x7c] = 0xff; // set ENDx + for (int i = 0; i < 8; i++) { + channel[i].pitch = 0; + channel[i].pitchCounter = 0; + channel[i].pitchModulation = false; + memset(channel[i].decodeBuffer, 0, sizeof(channel[i].decodeBuffer)); + channel[i].bufferOffset = 0; + channel[i].srcn = 0; + channel[i].decodeOffset = 0; + channel[i].blockOffset = 0; + channel[i].brrHeader = 0; + channel[i].useNoise = false; + channel[i].startDelay = 0; + memset(channel[i].adsrRates, 0, sizeof(channel[i].adsrRates)); + channel[i].adsrState = 0; + channel[i].sustainLevel = 0; + channel[i].gainSustainLevel = 0; + channel[i].useGain = false; + channel[i].gainMode = 0; + channel[i].directGain = false; + channel[i].gainValue = 0; + channel[i].preclampGain = 0; + channel[i].gain = 0; + channel[i].keyOn = false; + channel[i].keyOff = false; + channel[i].sampleOut = 0; + channel[i].volumeL = 0; + channel[i].volumeR = 0; + channel[i].echoEnable = false; } + counter = 0; + dirPage = 0; + evenCycle = true; + mute = true; + reset = true; + masterVolumeL = 0; + masterVolumeR = 0; + sampleOutL = 0; + sampleOutR = 0; + echoOutL = 0; + echoOutR = 0; + noiseSample = 0x4000; + noiseRate = 0; + echoWrites = false; + echoVolumeL = 0; + echoVolumeR = 0; + feedbackVolume = 0; + echoBufferAdr = 0; + echoDelay = 0; + echoLength = 0; + echoBufferIndex = 0; + firBufferIndex = 0; + memset(firValues, 0, sizeof(firValues)); + memset(firBufferL, 0, sizeof(firBufferL)); + memset(firBufferR, 0, sizeof(firBufferR)); + memset(sampleBuffer, 0, sizeof(sampleBuffer)); + sampleOffset = 0; } -void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, - uint8_t value) { - voice %= kNumVoices; - switch (reg % kNumVoiceRegs) { - case 0: - voices_[voice].vol_left = static_cast(value); - break; - case 1: - voices_[voice].vol_right = static_cast(value); - break; - case 2: - voices_[voice].pitch_low = value; - break; - case 3: - voices_[voice].pitch_high = value; - break; - case 4: - voices_[voice].source_number = value; - break; - case 5: - voices_[voice].adsr1 = value; - break; - case 6: - voices_[voice].adsr2 = value; - break; - case 7: - voices_[voice].gain = value; - break; - // Note: envx and outx are read-only, so they don't have cases here +void Dsp::NewFrame() { + lastFrameBoundary = sampleOffset; +} + +void Dsp::Cycle() { + sampleOutL = 0; + sampleOutR = 0; + echoOutL = 0; + echoOutR = 0; + for (int i = 0; i < 8; i++) { + CycleChannel(i); } -} - -// Set the callbacks -void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) { - sample_fetcher_ = fetcher; -} - -void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) { - sample_pusher_ = pusher; -} - -int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) { - Voice const& voice = voices_[voice_num]; - uint16_t sample_address = voice.source_number; - - // Use the callback to fetch the sample - int16_t sample = static_cast(sample_fetcher_(sample_address) << 8); - return sample; -} - -int16_t DigitalSignalProcessor::ProcessSample(uint8_t voice_num, - int16_t sample) { - Voice const& voice = voices_[voice_num]; - - // Adjust the pitch (for simplicity, we're just adjusting the sample value) - sample += voice.pitch_low + (voice.pitch_high << 8); - - // Apply volume (separate for left and right for stereo sound) - int16_t left_sample = (sample * voice.vol_left) / 255; - int16_t right_sample = (sample * voice.vol_right) / 255; - - // Combine stereo samples into a single 16-bit value - return (left_sample + right_sample) / 2; -} - -void DigitalSignalProcessor::MixSamples() { - int16_t mixed_sample = 0; - - for (uint8_t i = 0; i < kNumVoices; i++) { - int16_t decoded_sample = DecodeSample(i); - int16_t processed_sample = ProcessSample(i, decoded_sample); - mixed_sample += processed_sample; + HandleEcho(); // also applies master volume + counter = counter == 0 ? 30720 : counter - 1; + HandleNoise(); + evenCycle = !evenCycle; + // handle mute flag + if (mute) { + sampleOutL = 0; + sampleOutR = 0; } - - // Clamp the mixed sample to 16-bit range - if (mixed_sample > 32767) { - mixed_sample = 32767; - } else if (mixed_sample < -32768) { - mixed_sample = -32768; - } - - // Use the callback to push the mixed sample - sample_pusher_(mixed_sample); + // put final sample in the samplebuffer + sampleBuffer[(sampleOffset & 0x3ff) * 2] = sampleOutL; + sampleBuffer[(sampleOffset++ & 0x3ff) * 2 + 1] = sampleOutR; } -void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) { - uint8_t adsr1 = ReadVoiceReg(voice, 0x05); - uint8_t adsr2 = ReadVoiceReg(voice, 0x06); - uint8_t gain = ReadVoiceReg(voice, 0x07); +static int clamp16(int val) { + return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val); +} - uint8_t enableADSR = (adsr1 & 0x80) >> 7; +static int clip16(int val) { return (int16_t)(val & 0xffff); } - if (enableADSR) { - // Handle ADSR envelope - Voice& voice_obj = voices_[voice]; - switch (voice_obj.state) { - case VoiceState::ATTACK: - // Update amplitude based on attack rate - voice_obj.current_amplitude += AttackRate(adsr1); - if (voice_obj.current_amplitude >= ENVELOPE_MAX) { - voice_obj.current_amplitude = ENVELOPE_MAX; - voice_obj.state = VoiceState::DECAY; - } - break; - case VoiceState::DECAY: - // Update amplitude based on decay rate - voice_obj.current_amplitude -= DecayRate(adsr2); - if (voice_obj.current_amplitude <= voice_obj.decay_level) { - voice_obj.current_amplitude = voice_obj.decay_level; - voice_obj.state = VoiceState::SUSTAIN; - } - break; - case VoiceState::SUSTAIN: - // Keep amplitude at the calculated decay level - voice_obj.current_amplitude = voice_obj.decay_level; - break; - case VoiceState::RELEASE: - // Update amplitude based on release rate - voice_obj.current_amplitude -= ReleaseRate(adsr2); - if (voice_obj.current_amplitude <= 0) { - voice_obj.current_amplitude = 0; - voice_obj.state = VoiceState::OFF; - } - break; - default: - break; +bool Dsp::CheckCounter(int rate) { + if (rate == 0) return false; + return ((counter + rateOffsets[rate]) % rateValues[rate]) == 0; +} + +void Dsp::HandleEcho() { + // increment fir buffer index + firBufferIndex++; + firBufferIndex &= 0x7; + // get value out of ram + uint16_t adr = echoBufferAdr + echoBufferIndex; + int16_t ramSample = aram_[adr] | (aram_[(adr + 1) & 0xffff] << 8); + firBufferL[firBufferIndex] = ramSample >> 1; + ramSample = aram_[(adr + 2) & 0xffff] | (aram_[(adr + 3) & 0xffff] << 8); + firBufferR[firBufferIndex] = ramSample >> 1; + // calculate FIR-sum + int sumL = 0, sumR = 0; + for (int i = 0; i < 8; i++) { + sumL += (firBufferL[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6; + sumR += (firBufferR[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6; + if (i == 6) { + // clip to 16-bit before last addition + sumL = clip16(sumL); + sumR = clip16(sumR); } + } + sumL = clamp16(sumL) & ~1; + sumR = clamp16(sumR) & ~1; + // apply master volume and modify output with sum + sampleOutL = clamp16(((sampleOutL * masterVolumeL) >> 7) + + ((sumL * echoVolumeL) >> 7)); + sampleOutR = clamp16(((sampleOutR * masterVolumeR) >> 7) + + ((sumR * echoVolumeR) >> 7)); + // get echo value + int echoL = clamp16(echoOutL + clip16((sumL * feedbackVolume) >> 7)) & ~1; + int echoR = clamp16(echoOutR + clip16((sumR * feedbackVolume) >> 7)) & ~1; + // write it to ram + if (echoWrites) { + aram_[adr] = echoL & 0xff; + aram_[(adr + 1) & 0xffff] = echoL >> 8; + aram_[(adr + 2) & 0xffff] = echoR & 0xff; + aram_[(adr + 3) & 0xffff] = echoR >> 8; + } + // handle indexes + if (echoBufferIndex == 0) { + echoLength = echoDelay * 4; + } + echoBufferIndex += 4; + if (echoBufferIndex >= echoLength) { + echoBufferIndex = 0; + } +} + +void Dsp::CycleChannel(int ch) { + // handle pitch counter + int pitch = channel[ch].pitch; + if (ch > 0 && channel[ch].pitchModulation) { + pitch += ((channel[ch - 1].sampleOut >> 5) * pitch) >> 10; + } + // get current brr header and get sample address + channel[ch].brrHeader = aram_[channel[ch].decodeOffset]; + uint16_t samplePointer = dirPage + 4 * channel[ch].srcn; + if (channel[ch].startDelay == 0) samplePointer += 2; + uint16_t sampleAdr = + aram_[samplePointer] | (aram_[(samplePointer + 1) & 0xffff] << 8); + // handle starting of sample + if (channel[ch].startDelay > 0) { + if (channel[ch].startDelay == 5) { + // first keyed on + channel[ch].decodeOffset = sampleAdr; + channel[ch].blockOffset = 1; + channel[ch].bufferOffset = 0; + channel[ch].brrHeader = 0; + ram[0x7c] &= ~(1 << ch); // clear ENDx + } + channel[ch].gain = 0; + channel[ch].startDelay--; + channel[ch].pitchCounter = 0; + if (channel[ch].startDelay > 0 && channel[ch].startDelay < 4) { + channel[ch].pitchCounter = 0x4000; + } + pitch = 0; + } + // get sample + int sample = 0; + if (channel[ch].useNoise) { + sample = clip16(noiseSample * 2); } else { - // Handle Gain envelope - // Extract mode from the gain byte - uint8_t mode = (gain & 0xE0) >> 5; - uint8_t rate = gain & 0x1F; - - Voice& voice_obj = voices_[voice]; - - switch (mode) { - case 0: // Direct Designation - case 1: - case 2: - case 3: - voice_obj.current_amplitude = - rate << 3; // Multiplying by 8 to scale to 0-255 - break; - - case 6: // Increase Mode (Linear) - voice_obj.current_amplitude += gainTimings[0][rate]; - if (voice_obj.current_amplitude > ENVELOPE_MAX) { - voice_obj.current_amplitude = ENVELOPE_MAX; - } - break; - - case 7: // Increase Mode (Bent Line) - // Hypothetical behavior: Increase linearly at first, then increase - // more slowly You'll likely need to adjust this based on your - // specific requirements - if (voice_obj.current_amplitude < (ENVELOPE_MAX / 2)) { - voice_obj.current_amplitude += gainTimings[1][rate]; - } else { - voice_obj.current_amplitude += gainTimings[1][rate] / 2; - } - if (voice_obj.current_amplitude > ENVELOPE_MAX) { - voice_obj.current_amplitude = ENVELOPE_MAX; - } - break; - - case 4: // Decrease Mode (Linear) - if (voice_obj.current_amplitude < gainTimings[2][rate]) { - voice_obj.current_amplitude = 0; - } else { - voice_obj.current_amplitude -= gainTimings[2][rate]; - } - break; - - case 5: // Decrease Mode (Exponential) - voice_obj.current_amplitude -= - (voice_obj.current_amplitude * gainTimings[3][rate]) / ENVELOPE_MAX; - break; - - default: - // Default behavior can be handled here if necessary - break; + sample = GetSample(ch); + } + sample = ((sample * channel[ch].gain) >> 11) & ~1; + // handle reset and release + if (reset || (channel[ch].brrHeader & 0x03) == 1) { + channel[ch].adsrState = 3; // go to release + channel[ch].gain = 0; + } + // handle keyon/keyoff + if (evenCycle) { + if (channel[ch].keyOff) { + channel[ch].adsrState = 3; // go to release + } + if (channel[ch].keyOn) { + channel[ch].startDelay = 5; + channel[ch].adsrState = 0; // go to attack + channel[ch].keyOn = false; } } -} - -void DigitalSignalProcessor::update_voice_state(uint8_t voice_num) { - if (voice_num >= kNumVoices) return; - - Voice& voice = voices_[voice_num]; - switch (voice.state) { - case VoiceState::OFF: - // Reset current amplitude - voice.current_amplitude = 0; - break; - - case VoiceState::ATTACK: - // Increase the current amplitude at a rate defined by the ATTACK - // setting - voice.current_amplitude += AttackRate(voice.adsr1); - if (voice.current_amplitude >= ENVELOPE_MAX) { - voice.current_amplitude = ENVELOPE_MAX; - voice.state = VoiceState::DECAY; - voice.decay_level = CalculateDecayLevel(voice.adsr2); + // handle envelope + if (channel[ch].startDelay == 0) { + HandleGain(ch); + } + // decode new brr samples if needed and update offsets + if (channel[ch].pitchCounter >= 0x4000) { + DecodeBrr(ch); + if (channel[ch].blockOffset >= 7) { + if (channel[ch].brrHeader & 0x1) { + channel[ch].decodeOffset = sampleAdr; + ram[0x7c] |= 1 << ch; // set ENDx + } else { + channel[ch].decodeOffset += 9; } - break; - - case VoiceState::DECAY: - // Decrease the current amplitude at a rate defined by the DECAY setting - voice.current_amplitude -= DecayRate(voice.adsr2); - if (voice.current_amplitude <= voice.decay_level) { - voice.current_amplitude = voice.decay_level; - voice.state = VoiceState::SUSTAIN; - } - break; - - case VoiceState::SUSTAIN: - // Keep the current amplitude at the decay level - break; - - case VoiceState::RELEASE: - // Decrease the current amplitude at a rate defined by the RELEASE - // setting - voice.current_amplitude -= ReleaseRate(voice.adsr2); - if (voice.current_amplitude == 0) { - voice.state = VoiceState::OFF; - } - break; + channel[ch].blockOffset = 1; + } else { + channel[ch].blockOffset += 2; + } + } + // update pitch counter + channel[ch].pitchCounter &= 0x3fff; + channel[ch].pitchCounter += pitch; + if (channel[ch].pitchCounter > 0x7fff) channel[ch].pitchCounter = 0x7fff; + // set outputs + ram[(ch << 4) | 8] = channel[ch].gain >> 4; + ram[(ch << 4) | 9] = sample >> 8; + channel[ch].sampleOut = sample; + sampleOutL = clamp16(sampleOutL + ((sample * channel[ch].volumeL) >> 7)); + sampleOutR = clamp16(sampleOutR + ((sample * channel[ch].volumeR) >> 7)); + if (channel[ch].echoEnable) { + echoOutL = clamp16(echoOutL + ((sample * channel[ch].volumeL) >> 7)); + echoOutR = clamp16(echoOutR + ((sample * channel[ch].volumeR) >> 7)); } } -void DigitalSignalProcessor::process_envelope(uint8_t voice_num) { - if (voice_num >= kNumVoices) return; +void Dsp::HandleGain(int ch) { + int newGain = channel[ch].gain; + int rate = 0; + // handle gain mode + if (channel[ch].adsrState == 3) { // release + rate = 31; + newGain -= 8; + } else { + if (!channel[ch].useGain) { + rate = channel[ch].adsrRates[channel[ch].adsrState]; + switch (channel[ch].adsrState) { + case 0: + newGain += rate == 31 ? 1024 : 32; + break; // attack + case 1: + newGain -= ((newGain - 1) >> 8) + 1; + break; // decay + case 2: + newGain -= ((newGain - 1) >> 8) + 1; + break; // sustain + } + } else { + if (!channel[ch].directGain) { + rate = channel[ch].adsrRates[3]; + switch (channel[ch].gainMode) { + case 0: + newGain -= 32; + break; // linear decrease + case 1: + newGain -= ((newGain - 1) >> 8) + 1; + break; // exponential decrease + case 2: + newGain += 32; + break; // linear increase + case 3: + newGain += (channel[ch].preclampGain < 0x600) ? 32 : 8; + break; // bent increase + } + } else { // direct gain + rate = 31; + newGain = channel[ch].gainValue; + } + } + } + // use sustain level according to mode + int sustainLevel = channel[ch].useGain ? channel[ch].gainSustainLevel + : channel[ch].sustainLevel; + if (channel[ch].adsrState == 1 && (newGain >> 8) == sustainLevel) { + channel[ch].adsrState = 2; // go to sustain + } + // store pre-clamped gain (for bent increase) + channel[ch].preclampGain = newGain & 0xffff; + // clamp gain + if (newGain < 0 || newGain > 0x7ff) { + newGain = newGain < 0 ? 0 : 0x7ff; + if (channel[ch].adsrState == 0) { + channel[ch].adsrState = 1; // go to decay + } + } + // store new value + if (CheckCounter(rate)) channel[ch].gain = newGain; +} - Voice& voice = voices_[voice_num]; +int16_t Dsp::GetSample(int ch) { + int pos = (channel[ch].pitchCounter >> 12) + channel[ch].bufferOffset; + int offset = (channel[ch].pitchCounter >> 4) & 0xff; + int16_t news = channel[ch].decodeBuffer[(pos + 3) % 12]; + int16_t olds = channel[ch].decodeBuffer[(pos + 2) % 12]; + int16_t olders = channel[ch].decodeBuffer[(pos + 1) % 12]; + int16_t oldests = channel[ch].decodeBuffer[pos % 12]; + int out = (gaussValues[0xff - offset] * oldests) >> 11; + out += (gaussValues[0x1ff - offset] * olders) >> 11; + out += (gaussValues[0x100 + offset] * olds) >> 11; + out = clip16(out) + ((gaussValues[offset] * news) >> 11); + return clamp16(out) & ~1; +} - // Update the voice state first (based on keys, etc.) - update_voice_state(voice_num); +void Dsp::DecodeBrr(int ch) { + int shift = channel[ch].brrHeader >> 4; + int filter = (channel[ch].brrHeader & 0xc) >> 2; + int bOff = channel[ch].bufferOffset; + int old = channel[ch].decodeBuffer[bOff == 0 ? 11 : bOff - 1] >> 1; + int older = channel[ch].decodeBuffer[bOff == 0 ? 10 : bOff - 2] >> 1; + uint8_t curByte = 0; + for (int i = 0; i < 4; i++) { + int s = 0; + if (i & 1) { + s = curByte & 0xf; + } else { + curByte = aram_[(channel[ch].decodeOffset + channel[ch].blockOffset + + (i >> 1)) & + 0xffff]; + s = curByte >> 4; + } + if (s > 7) s -= 16; + if (shift <= 0xc) { + s = (s << shift) >> 1; + } else { + s = (s >> 3) << 12; + } + switch (filter) { + case 1: + s += old + (-old >> 4); + break; + case 2: + s += 2 * old + ((3 * -old) >> 5) - older + (older >> 4); + break; + case 3: + s += 2 * old + ((13 * -old) >> 6) - older + ((3 * older) >> 4); + break; + } + channel[ch].decodeBuffer[bOff + i] = clamp16(s) * 2; // cuts off bit 15 + older = old; + old = channel[ch].decodeBuffer[bOff + i] >> 1; + } + channel[ch].bufferOffset += 4; + if (channel[ch].bufferOffset >= 12) channel[ch].bufferOffset = 0; +} - // Calculate the envelope value based on the current amplitude - voice.envx = calculate_envelope_value(voice.current_amplitude); +void Dsp::HandleNoise() { + if (CheckCounter(noiseRate)) { + int bit = (noiseSample & 1) ^ ((noiseSample >> 1) & 1); + noiseSample = ((noiseSample >> 1) & 0x3fff) | (bit << 14); + } +} - // Apply the envelope value to the audio output - apply_envelope_to_output(voice_num); +uint8_t Dsp::Read(uint8_t adr) { return ram[adr]; } + +void Dsp::Write(uint8_t adr, uint8_t val) { + int ch = adr >> 4; + switch (adr) { + case 0x00: + case 0x10: + case 0x20: + case 0x30: + case 0x40: + case 0x50: + case 0x60: + case 0x70: { + channel[ch].volumeL = val; + break; + } + case 0x01: + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: { + channel[ch].volumeR = val; + break; + } + case 0x02: + case 0x12: + case 0x22: + case 0x32: + case 0x42: + case 0x52: + case 0x62: + case 0x72: { + channel[ch].pitch = (channel[ch].pitch & 0x3f00) | val; + break; + } + case 0x03: + case 0x13: + case 0x23: + case 0x33: + case 0x43: + case 0x53: + case 0x63: + case 0x73: { + channel[ch].pitch = ((channel[ch].pitch & 0x00ff) | (val << 8)) & 0x3fff; + break; + } + case 0x04: + case 0x14: + case 0x24: + case 0x34: + case 0x44: + case 0x54: + case 0x64: + case 0x74: { + channel[ch].srcn = val; + break; + } + case 0x05: + case 0x15: + case 0x25: + case 0x35: + case 0x45: + case 0x55: + case 0x65: + case 0x75: { + channel[ch].adsrRates[0] = (val & 0xf) * 2 + 1; + channel[ch].adsrRates[1] = ((val & 0x70) >> 4) * 2 + 16; + channel[ch].useGain = (val & 0x80) == 0; + break; + } + case 0x06: + case 0x16: + case 0x26: + case 0x36: + case 0x46: + case 0x56: + case 0x66: + case 0x76: { + channel[ch].adsrRates[2] = val & 0x1f; + channel[ch].sustainLevel = (val & 0xe0) >> 5; + break; + } + case 0x07: + case 0x17: + case 0x27: + case 0x37: + case 0x47: + case 0x57: + case 0x67: + case 0x77: { + channel[ch].directGain = (val & 0x80) == 0; + channel[ch].gainMode = (val & 0x60) >> 5; + channel[ch].adsrRates[3] = val & 0x1f; + channel[ch].gainValue = (val & 0x7f) * 16; + channel[ch].gainSustainLevel = (val & 0xe0) >> 5; + break; + } + case 0x0c: { + masterVolumeL = val; + break; + } + case 0x1c: { + masterVolumeR = val; + break; + } + case 0x2c: { + echoVolumeL = val; + break; + } + case 0x3c: { + echoVolumeR = val; + break; + } + case 0x4c: { + for (int i = 0; i < 8; i++) { + channel[i].keyOn = val & (1 << i); + } + break; + } + case 0x5c: { + for (int i = 0; i < 8; i++) { + channel[i].keyOff = val & (1 << i); + } + break; + } + case 0x6c: { + reset = val & 0x80; + mute = val & 0x40; + echoWrites = (val & 0x20) == 0; + noiseRate = val & 0x1f; + break; + } + case 0x7c: { + val = 0; // any write clears ENDx + break; + } + case 0x0d: { + feedbackVolume = val; + break; + } + case 0x2d: { + for (int i = 0; i < 8; i++) { + channel[i].pitchModulation = val & (1 << i); + } + break; + } + case 0x3d: { + for (int i = 0; i < 8; i++) { + channel[i].useNoise = val & (1 << i); + } + break; + } + case 0x4d: { + for (int i = 0; i < 8; i++) { + channel[i].echoEnable = val & (1 << i); + } + break; + } + case 0x5d: { + dirPage = val << 8; + break; + } + case 0x6d: { + echoBufferAdr = val << 8; + break; + } + case 0x7d: { + echoDelay = + (val & 0xf) * 512; // 2048-byte steps, stereo sample is 4 bytes + break; + } + case 0x0f: + case 0x1f: + case 0x2f: + case 0x3f: + case 0x4f: + case 0x5f: + case 0x6f: + case 0x7f: { + firValues[ch] = val; + break; + } + } + ram[adr] = val; +} + +void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame, + bool pal_timing) { + // resample from 534 / 641 samples per frame to wanted value + float wantedSamples = (pal_timing ? 641.0 : 534.0); + double adder = wantedSamples / samples_per_frame; + double location = lastFrameBoundary - wantedSamples; + for (int i = 0; i < samples_per_frame; i++) { + sample_data[i * 2] = sample_buffer_[(((int)location) & 0x3ff) * 2]; + sample_data[i * 2 + 1] = sample_buffer_[(((int)location) & 0x3ff) * 2 + 1]; + location += adder; + } } } // namespace audio diff --git a/src/app/emu/audio/dsp.h b/src/app/emu/audio/dsp.h index 21dbfe7c..762692c9 100644 --- a/src/app/emu/audio/dsp.h +++ b/src/app/emu/audio/dsp.h @@ -5,6 +5,7 @@ #include #include +#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; -using SamplePusher = std::function; +typedef struct DspChannel { + // pitch + uint16_t pitch; + uint16_t pitchCounter; + bool pitchModulation; + // brr decoding + int16_t decodeBuffer[12]; + uint8_t bufferOffset; + uint8_t srcn; + uint16_t decodeOffset; + uint8_t blockOffset; // offset within brr block + uint8_t brrHeader; + bool useNoise; + uint8_t startDelay; + // adsr, envelope, gain + uint8_t adsrRates[4]; // attack, decay, sustain, gain + uint8_t adsrState; // 0: attack, 1: decay, 2: sustain, 3: release + uint8_t sustainLevel; + uint8_t gainSustainLevel; + bool useGain; + uint8_t gainMode; + bool directGain; + uint16_t gainValue; // for direct gain + uint16_t preclampGain; // for bent increase + uint16_t gain; + // keyon/off + bool keyOn; + bool keyOff; + // output + int16_t sampleOut; // final sample, to be multiplied by channel volume + int8_t volumeL; + int8_t volumeR; + bool echoEnable; +} DspChannel; /** * The S-DSP is a digital signal processor generating the sound data. @@ -34,277 +67,92 @@ using SamplePusher = std::function; * There are 8 voices, numbered 0 to 7. * Each voice X has 10 registers in the range $X0-$X9. * - * | Name | Address | Bits | Notes | + * | Name | Address | Bits | Notes | * |---------|---------|-----------|--------------------------------------------------------| - * | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | - * | VOL (R) | $X1 | SVVV VVVV | Right channel volume, signed. | - * | P (L) | $X2 | LLLL LLLL | Low 8 bits of sample pitch. | - * | P (H) | $X3 | --HH HHHH | High 6 bits of sample pitch. | - * | SCRN | $X4 | SSSS SSSS | Selects a sample source entry from the directory. | - * | ADSR (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A). | - * | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | - * | GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | - * | ENVX | $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. | - * | OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample wave | - * | | | | multiplied by ENVX, before applying VOL. | + * | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | | VOL (R) | + * $X1 | SVVV VVVV | Right channel volume, signed. | | P (L) | $X2 | + * LLLL LLLL | Low 8 bits of sample pitch. | | P (H) + * | $X3 | --HH HHHH | High 6 bits of sample pitch. | | SCRN | $X4 | + * SSSS SSSS | Selects a sample source entry from the directory. | | ADSR + * (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A). + * | | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | | + * GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | | ENVX | + * $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. | | + * OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample + * wave | | | | | multiplied by ENVX, before + * applying VOL. | */ -class DigitalSignalProcessor { - private: - static const size_t kNumVoices = 8; - static const size_t kNumVoiceRegs = 10; - static const size_t kNumGlobalRegs = 15; - - enum class VoiceState { OFF, ATTACK, DECAY, SUSTAIN, RELEASE }; - - struct Voice { - int8_t vol_left; // x0 - int8_t vol_right; // x1 - uint8_t pitch_low; // x2 - uint8_t pitch_high; // x3 - uint8_t source_number; // x4 - uint8_t adsr1; // x5 - uint8_t adsr2; // x6 - uint8_t gain; // x7 - uint8_t envx; // x8 (read-only) - int8_t outx; // x9 (read-only) - - VoiceState state = VoiceState::OFF; - uint16_t current_amplitude = 0; // Current amplitude value used for ADSR - uint16_t decay_level; // Calculated decay level based on ADSR settings - }; - Voice voices_[8]; - - // Global DSP registers - uint8_t mvol_left; // 0C - uint8_t mvol_right; // 0D - uint8_t evol_left; // 0E - uint8_t evol_right; // 0F - uint8_t kon; // 10 - uint8_t koff; // 11 - uint8_t flags; // 12 - uint8_t endx; // 13 (read-only) - - // Global registers - std::vector globalRegs = std::vector(kNumGlobalRegs, 0x00); - - static const uint16_t ENVELOPE_MAX = 2047; // $7FF - - // Attack times in ms - const std::vector attackTimes = { - 4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 0}; - - // Decay times in ms - const std::vector decayTimes = {1200, 740, 440, 290, - 180, 110, 74, 37}; - - // Release times in ms - const std::vector 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> 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> DigitalSignalProcessorPeriodTable = { - // ... Your DSP period table here ... - }; - - // DSP Period Offset - const std::vector 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& aram) : aram_(aram) {} + + void NewFrame(); void Reset(); - void SetSampleFetcher(std::function fetcher); - void SetSamplePusher(std::function 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& 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 diff --git a/src/app/emu/audio/internal/addressing.cc b/src/app/emu/audio/internal/addressing.cc index 29097704..447d3fd6 100644 --- a/src/app/emu/audio/internal/addressing.cc +++ b/src/app/emu/audio/internal/addressing.cc @@ -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(read(PC)); diff --git a/src/app/emu/audio/internal/instructions.cc b/src/app/emu/audio/internal/instructions.cc index 79217fb9..ee207e9e 100644 --- a/src/app/emu/audio/internal/instructions.cc +++ b/src/app/emu/audio/internal/instructions.cc @@ -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); } diff --git a/src/app/emu/audio/spc700.cc b/src/app/emu/audio/spc700.cc index 34a585ca..1c5dee06 100644 --- a/src/app/emu/audio/spc700.cc +++ b/src/app/emu/audio/spc700.cc @@ -13,916 +13,1288 @@ namespace app { namespace emu { namespace audio { -void Spc700::Reset() { - PC = 0; - A = 0; - X = 0; - Y = 0; - SP = 0xFF; - PSW = ByteToFlags(0x00); - aram_.reset(); +void Spc700::Reset(bool hard) { + if (hard) { + PC = 0; + A = 0; + X = 0; + Y = 0; + SP = 0x00; + PSW = ByteToFlags(0x00); + } + step = 0; + stopped_ = false; + reset_wanted_ = true; } -void Spc700::BootIplRom() { - PC = 0xFFC0; - A = 0; - X = 0; - Y = 0; - int i = 0; - while (PC != 0xFFC0 + 0x3F) { - uint8_t opcode = read(PC); - ExecuteInstructions(opcode); - PC++; - i++; - - if (i > 1000) { - break; - } +void Spc700::RunOpcode() { + if (reset_wanted_) { + // based on 6502, brk without writes + reset_wanted_ = false; + read(PC); + read(PC); + read(0x100 | SP--); + read(0x100 | SP--); + read(0x100 | SP--); + callbacks_.idle(false); + PSW.I = false; + PC = read_word(0xfffe); + return; } + if (stopped_) { + callbacks_.idle(true); + return; + } + if (step == 0) { + bstep = 0; + opcode = ReadOpcode(); + step = 1; + return; + } + ExecuteInstructions(opcode); + if (step == 1) step = 0; // reset step for non cycle-stepped opcodes. } void Spc700::ExecuteInstructions(uint8_t opcode) { uint16_t initialPC = PC; switch (opcode) { - // 8-bit Move Memory to Register - case 0xE8: // MOV A, #imm - { - MOV(A, imm()); + case 0x00: { // nop imp + read(PC); + // no operation break; } - case 0xE6: // MOV A, (X) - { - MOV(A, X); + case 0x01: + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: + case 0x81: + case 0x91: + case 0xa1: + case 0xb1: + case 0xc1: + case 0xd1: + case 0xe1: + case 0xf1: { // tcall imp + read(PC); + callbacks_.idle(false); + push_word(PC); + callbacks_.idle(false); + uint16_t adr = 0xffde - (2 * (opcode >> 4)); + PC = read_word(adr); break; } - case 0xBF: // MOV A, (X)+ - { - MOV(A, X); - X++; + case 0x02: + case 0x22: + case 0x42: + case 0x62: + case 0x82: + case 0xa2: + case 0xc2: + case 0xe2: { // set1 dp + uint16_t adr = dp(); + write(adr, read(adr) | (1 << (opcode >> 5))); break; } - case 0xE4: // MOV A, dp - { - MOV(A, dp()); + case 0x12: + case 0x32: + case 0x52: + case 0x72: + case 0x92: + case 0xb2: + case 0xd2: + case 0xf2: { // clr1 dp + uint16_t adr = dp(); + write(adr, read(adr) & ~(1 << (opcode >> 5))); break; } - case 0xF4: // MOV A, dp+X - { - MOV(A, dp_plus_x()); + case 0x03: + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xa3: + case 0xc3: + case 0xe3: { // bbs dp, rel + uint8_t val = read(dp()); + callbacks_.idle(false); + DoBranch(ReadOpcode(), val & (1 << (opcode >> 5))); break; } - case 0xE5: // MOV A, !abs - { - MOV(A, read(abs())); - break; - } - case 0xF5: // MOV A, !abs+X - { - MOV(A, abs() + X); - break; - } - case 0xF6: // MOV A, !abs+Y - { - MOV(A, abs() + Y); - break; - } - case 0xE7: // MOV A, [dp+X] - { - MOV(A, read(dp_plus_x_indirect())); - break; - } - case 0xF7: // MOV A, [dp]+Y - { - MOV(A, read(dp_indirect_plus_y())); - break; - } - case 0xCD: // MOV X, #imm - { - MOV(X, imm()); - break; - } - case 0xF8: // MOV X, dp - { - MOV(X, dp()); - break; - } - case 0xF9: // MOV X, dp+Y - { - MOV(X, dp_plus_y()); - break; - } - case 0xE9: // MOV X, !abs - { - MOV(X, abs()); - break; - } - case 0x8D: // MOV Y, #imm - { - MOV(Y, imm()); - break; - } - case 0xEB: // MOV Y, dp - { - MOV(Y, dp()); - break; - } - case 0xFB: // MOV Y, dp+X - { - MOV(Y, dp_plus_x()); - break; - } - case 0xEC: // MOV Y, !abs - { - MOV(Y, abs()); + case 0x13: + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xb3: + case 0xd3: + case 0xf3: { // bbc dp, rel + uint8_t val = read(dp()); + callbacks_.idle(false); + DoBranch(ReadOpcode(), (val & (1 << (opcode >> 5))) == 0); break; } - // 8-bit move register to memory - case 0xC6: // MOV (X), A - { - MOV_ADDR(X, A); + case 0x04: { // or dp + OR(dp()); break; } - case 0xAF: // MOV (X)+, A - { - MOV_ADDR(X, A); + case 0x05: { // or abs + OR(abs()); break; } - case 0xC4: // MOV dp, A - { - MOV_ADDR(get_dp_addr(), A); + case 0x06: { // or ind + OR(ind()); break; } - case 0xD4: // MOV dp+X, A - { - MOV_ADDR(get_dp_addr() + X, A); + case 0x07: { // or idx + OR(idx()); break; } - case 0xC5: // MOV !abs, A - { - MOV_ADDR(abs(), A); + case 0x08: { // or imm + OR(imm()); break; } - case 0xD5: // MOV !abs+X, A - { - MOV_ADDR(abs() + X, A); + case 0x09: { // orm dp, dp + uint8_t src = 0; + uint16_t dst = dp_dp(&src); + ORM(dst, src); break; } - case 0xD6: // MOV !abs+Y, A - { - MOV_ADDR(abs() + Y, A); + case 0x0a: { // or1 abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + PSW.C = PSW.C | ((read(adr) >> bit) & 1); + callbacks_.idle(false); break; } - case 0xC7: // MOV [dp+X], A - { - MOV_ADDR(dp_plus_x_indirect(), A); - break; - } - case 0xD7: // MOV [dp]+Y, A - { - MOV_ADDR(dp_indirect_plus_y(), A); - break; - } - case 0xD8: // MOV dp, X - { - MOV_ADDR(get_dp_addr(), X); - break; - } - case 0xD9: // MOV dp+Y, X - { - MOV_ADDR(get_dp_addr() + Y, X); - break; - } - case 0xC9: // MOV !abs, X - { - MOV_ADDR(abs(), X); - break; - } - case 0xCB: // MOV dp, Y - { - MOV_ADDR(get_dp_addr(), Y); - break; - } - case 0xDB: // MOV dp+X, Y - { - MOV_ADDR(get_dp_addr() + X, Y); - break; - } - case 0xCC: // MOV !abs, Y - { - MOV_ADDR(abs(), Y); - break; - } - - // . 8-bit move register to register / special direct page moves - case 0x7D: // MOV A, X - { - MOV(A, X); - break; - } - case 0xDD: // MOV A, Y - { - MOV(A, Y); - break; - } - case 0x5D: // MOV X, A - { - MOV(X, A); - break; - } - case 0xFD: // MOV Y, A - { - MOV(Y, A); - break; - } - case 0x9D: // MOV X, SP - { - MOV(X, SP); - break; - } - case 0xBD: // MOV SP, X - { - MOV(SP, X); - break; - } - case 0xFA: // MOV dp, dp - { - MOV_ADDR(get_dp_addr(), dp()); - break; - } - case 0x8F: // MOV dp, #imm - { - MOV_ADDR(get_dp_addr(), imm()); - break; - } - - // . 8-bit arithmetic - case 0x88: // ADC A, #imm - { - ADC(A, imm()); - break; - } - case 0x86: // ADC A, (X) - { - ADC(A, X); - break; - } - case 0x84: // ADC A, dp - { - ADC(A, dp()); - break; - } - case 0x94: // ADC A, dp+X - { - ADC(A, dp_plus_x()); - break; - } - case 0x85: // ADC A, !abs - { - ADC(A, abs()); - break; - } - case 0x95: // ADC A, !abs+X - { - ADC(A, abs() + X); - break; - } - case 0x96: // ADC A, !abs+Y - { - ADC(A, abs() + Y); - break; - } - case 0x87: // ADC A, [dp+X] - { - ADC(A, dp_plus_x_indirect()); - break; - } - case 0x97: // ADC A, [dp]+Y - { - ADC(A, dp_indirect_plus_y()); - break; - } - case 0x99: // ADC (X), (Y) - break; - case 0x89: // ADC dp, dp - { - ADC(mutable_dp(), dp()); - break; - } - case 0x98: // ADC dp, #imm - { - ADC(mutable_dp(), imm()); - break; - } - - case 0xA8: // SBC A, #imm - { - SBC(A, imm()); - break; - } - case 0xA6: // SBC A, (X) - { - SBC(A, mutable_read(X)); - break; - } - case 0xA4: // SBC A, dp - { - SBC(A, dp()); - break; - } - case 0xB4: // SBC A, dp+X - { - SBC(A, dp_plus_x()); - break; - } - case 0xA5: // SBC A, !abs - { - SBC(A, abs()); - break; - } - case 0xB5: // SBC A, !abs+X - { - SBC(A, abs() + X); - break; - } - case 0xB6: // SBC A, !abs+Y - { - SBC(A, abs() + Y); - break; - } - case 0xA7: // SBC A, [dp+X] - { - SBC(A, dp_plus_x_indirect()); - break; - } - case 0xB7: // SBC A, [dp]+Y - { - SBC(A, dp_indirect_plus_y()); - break; - } - case 0xB9: // SBC (X), (Y) - { - SBC(mutable_read(X), mutable_read(Y)); - break; - } - case 0xA9: // SBC dp, dp - { - SBC(mutable_dp(), dp()); - break; - } - case 0xB8: // SBC dp, #imm - { - SBC(mutable_dp(), imm()); - break; - } - - case 0x68: // CMP A, #imm - { - CMP(A, imm()); - break; - } - case 0x66: // CMP A, (X) - { - CMP(A, read(X)); - break; - } - case 0x64: // CMP A, dp - { - CMP(A, dp()); - break; - } - case 0x74: // CMP A, dp+X - { - CMP(A, dp_plus_x()); - break; - } - case 0x65: // CMP A, !abs - { - CMP(A, abs()); - break; - } - case 0x75: // CMP A, !abs+X - { - CMP(A, abs() + X); - break; - } - case 0x76: // CMP A, !abs+Y - { - CMP(A, abs() + Y); - break; - } - case 0x67: // CMP A, [dp+X] - break; - case 0x77: // CMP A, [dp]+Y - break; - case 0x79: // CMP (X), (Y) - break; - case 0x69: // CMP dp, dp - { - CMP(mutable_dp(), dp()); - break; - } - case 0x78: // CMP dp, #imm - { - CMP(mutable_dp(), imm()); - break; - } - case 0xC8: // CMP X, #imm - { - CMP(X, imm()); - break; - } - case 0x3E: // CMP X, dp - { - CMP(X, dp()); - break; - } - case 0x1E: // CMP X, !abs - { - CMP(X, abs()); - break; - } - case 0xAD: // CMP Y, #imm - { - CMP(Y, imm()); - break; - } - case 0x7E: // CMP Y, dp - { - CMP(Y, dp()); - break; - } - case 0x5E: // CMP Y, !abs - { - CMP(Y, abs()); - break; - } - - // 8-bit boolean logic - case 0x28: // AND A, #imm - { - AND(A, imm()); - break; - } - case 0x26: // AND A, (X) - { - AND(A, mutable_read(X)); - break; - } - case 0x24: // AND A, dp - { - AND(A, dp()); - break; - } - case 0x34: // AND A, dp+X - { - AND(A, dp_plus_x()); - break; - } - case 0x25: // AND A, !abs - { - AND(A, abs()); - break; - } - case 0x35: // AND A, !abs+X - { - AND(A, abs() + X); - break; - } - case 0x36: // AND A, !abs+Y - { - AND(A, abs() + Y); - break; - } - case 0x27: // AND A, [dp+X] - { - AND(A, dp_plus_x_indirect()); - break; - } - case 0x37: // AND A, [dp]+Y - { - AND(A, dp_indirect_plus_y()); - break; - } - case 0x39: // AND (X), (Y) - { - AND(mutable_read(X), mutable_read(Y)); - break; - } - case 0x29: // AND dp, dp - { - AND(mutable_dp(), dp()); - break; - } - case 0x38: // AND dp, #imm - { - AND(mutable_dp(), imm()); - break; - } - - case 0x08: // OR A, #imm - { - OR(A, imm()); - break; - } - case 0x06: // OR A, (X) - { - OR(A, mutable_read(X)); - break; - } - case 0x04: // OR A, dp - { - OR(A, dp()); - break; - } - case 0x14: // OR A, dp+X - { - OR(A, dp_plus_x()); - break; - } - case 0x05: // OR A, !abs - { - OR(A, abs()); - break; - } - case 0x15: // OR A, !abs+X - { - OR(A, abs() + X); - break; - } - case 0x16: // OR A, !abs+Y - { - OR(A, abs() + Y); - break; - } - case 0x07: // OR A, [dp+X] - { - OR(A, dp_plus_x_indirect()); - break; - } - case 0x17: // OR A, [dp]+Y - { - OR(A, dp_indirect_plus_y()); - break; - } - case 0x19: // OR (X), (Y) - OR(mutable_read(X), mutable_read(Y)); - break; - case 0x09: // OR dp, dp - OR(mutable_dp(), dp()); - break; - case 0x18: // OR dp, #imm - OR(mutable_dp(), imm()); - break; - case 0x48: // EOR A, #imm - EOR(A, imm()); - break; - case 0x46: // EOR A, (X) - EOR(A, mutable_read(X)); - break; - case 0x44: // EOR A, dp - EOR(A, dp()); - break; - case 0x54: // EOR A, dp+X - EOR(A, dp_plus_x()); - break; - case 0x45: // EOR A, !abs - EOR(A, abs()); - break; - case 0x55: // EOR A, !abs+X - EOR(A, abs() + X); - break; - case 0x56: // EOR A, !abs+Y - EOR(A, abs() + Y); - break; - case 0x47: // EOR A, [dp+X] - EOR(A, dp_plus_x_indirect()); - break; - case 0x57: // EOR A, [dp]+Y - EOR(A, dp_indirect_plus_y()); - break; - case 0x59: // EOR (X), (Y) - EOR(mutable_read(X), mutable_read(Y)); - break; - case 0x49: // EOR dp, dp - EOR(mutable_dp(), dp()); - break; - case 0x58: // EOR dp, #imm - EOR(mutable_dp(), imm()); - break; - - // . 8-bit increment / decrement - - case 0xBC: // INC A - INC(A); - break; - case 0xAB: // INC dp - INC(mutable_dp()); - break; - case 0xBB: // INC dp+X - INC(mutable_read((PSW.P << 8) + dp_plus_x())); - break; - case 0xAC: // INC !abs - INC(mutable_read(abs())); - break; - case 0x3D: // INC X - INC(X); - break; - case 0xFC: // INC Y - INC(Y); - break; - case 0x9C: // DEC A - DEC(A); - break; - case 0x8B: // DEC dp - DEC(mutable_dp()); - break; - case 0x9B: // DEC dp+X - DEC(mutable_read((PSW.P << 8) + dp_plus_x())); - break; - case 0x8C: // DEC !abs - DEC(mutable_read(abs())); - break; - case 0x1D: // DEC X - DEC(X); - break; - case 0xDC: // DEC Y - DEC(Y); - break; - - // 8-bit shift / rotation - - case 0x1C: // ASL A - ASL(A); - break; - case 0x0B: // ASL dp + case 0x0b: { // asl dp ASL(dp()); break; - case 0x1B: // ASL dp+X - ASL(dp_plus_x()); - break; - case 0x0C: // ASL !abs + } + case 0x0c: { // asl abs ASL(abs()); break; - case 0x5C: // LSR A - LSR(A); + } + case 0x0d: { // pushp imp + read(PC); + push_byte(FlagsToByte(PSW)); + callbacks_.idle(false); break; - case 0x4B: // LSR dp - LSR(mutable_dp()); + } + case 0x0e: { // tset1 abs + uint16_t adr = abs(); + uint8_t val = read(adr); + read(adr); + uint8_t result = A + (val ^ 0xff) + 1; + PSW.Z = (result == 0); + PSW.N = (result & 0x80); + write(adr, val | A); break; - case 0x5B: // LSR dp+X - LSR(mutable_read((PSW.P << 8) + dp_plus_x())); + } + case 0x0f: { // brk imp + read(PC); + push_word(PC); + push_byte(FlagsToByte(PSW)); + callbacks_.idle(false); + PSW.I = false; + PSW.B = true; + PC = read_word(0xffde); break; - case 0x4C: // LSR !abs - LSR(mutable_read(abs())); + } + case 0x10: { // bpl rel + DoBranch(ReadOpcode(), !PSW.N); break; - - case 0x3C: // ROL A - ROL(A); + } + case 0x14: { // or dpx + OR(dpx()); break; - case 0x2B: // ROL dp + } + case 0x15: { // or abx + OR(abs_x()); + break; + } + case 0x16: { // or aby + OR(abs_y()); + break; + } + case 0x17: { // or idy + OR(idy()); + break; + } + case 0x18: { // orm dp, imm + uint8_t src = 0; + uint16_t dst = dp_imm(&src); + ORM(dst, src); + break; + } + case 0x19: { // orm ind, ind + uint8_t src = 0; + uint16_t dst = ind_ind(&src); + ORM(dst, src); + break; + } + case 0x1a: { // decw dp + uint16_t low = 0; + uint16_t high = dp_word(&low); + uint16_t value = read(low) - 1; + write(low, value & 0xff); + value += read(high) << 8; + write(high, value >> 8); + PSW.Z = value == 0; + PSW.N = value & 0x8000; + break; + } + case 0x1b: { // asl dpx + ASL(dpx()); + break; + } + case 0x1c: { // asla imp + read(PC); + PSW.C = A & 0x80; + A <<= 1; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0x1d: { // decx imp + read(PC); + X--; + PSW.Z = (X == 0); + PSW.N = (X & 0x80); + break; + } + case 0x1e: { // cmpx abs + CMPX(abs()); + break; + } + case 0x1f: { // jmp iax + uint16_t pointer = ReadOpcodeWord(); + callbacks_.idle(false); + PC = read_word((pointer + X) & 0xffff); + break; + } + case 0x20: { // clrp imp + read(PC); + PSW.P = false; + break; + } + case 0x24: { // and dp + AND(dp()); + break; + } + case 0x25: { // and abs + AND(abs()); + break; + } + case 0x26: { // and ind + AND(ind()); + break; + } + case 0x27: { // and idx + AND(idx()); + break; + } + case 0x28: { // and imm + AND(imm()); + break; + } + case 0x29: { // andm dp, dp + uint8_t src = 0; + uint16_t dst = dp_dp(&src); + ANDM(dst, src); + break; + } + case 0x2a: { // or1n abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + PSW.C = PSW.C | (~(read(adr) >> bit) & 1); + callbacks_.idle(false); + break; + } + case 0x2b: { // rol dp ROL(dp()); break; - case 0x3B: // ROL dp+X - ROL(dp_plus_x()); - break; - case 0x2C: // ROL !abs + } + case 0x2c: { // rol abs ROL(abs()); break; - case 0x7C: // ROR A - // ROR(A); - break; - case 0x6B: // ROR dp - // ROR(dp()); - break; - case 0x7B: // ROR dp+X - // ROR(dp_plus_x()); - break; - case 0x6C: // ROR !abs - // ROR(abs()); - break; - case 0x9F: // XCN A Exchange nibbles of A - XCN(A); - break; - - // . 16-bit operations - - case 0xBA: // MOVW YA, dp - MOVW(YA, dp()); - break; - case 0xDA: // MOVW dp, YA - MOVW(mutable_read_16(dp()), YA); - break; - case 0x3A: // INCW dp - INCW(mutable_read_16(dp())); - break; - case 0x1A: // DECW dp - DECW(mutable_read_16(dp())); - break; - case 0x7A: // ADDW YA, dp - ADDW(YA, dp()); - break; - case 0x9A: // SUBW YA, dp - SUBW(YA, dp()); - break; - case 0x5A: // CMPW YA, dp - // CMPW(YA, dp()); - break; - case 0xCF: // MUL YA - MUL(YA); - break; - case 0x9E: // DIV YA, X - // DIV(YA, X); - break; - - // . decimal adjust - - case 0xDF: // DAA A - break; - case 0xBE: // DAS A - break; - - // . branching - - case 0x2F: // BRA rel - BRA(rel()); - break; - case 0xF0: // BEQ rel - BEQ(rel()); - break; - case 0xD0: // BNE rel - BNE(rel()); - break; - case 0xB0: // BCS rel - BCS(rel()); - break; - case 0x90: // BCC rel - BCC(rel()); - break; - case 0x70: // BVS rel - BVS(rel()); - break; - case 0x50: // BVC rel - BVC(rel()); - break; - case 0x30: // BMI rel - BMI(rel()); - break; - case 0x10: // BPL rel - BPL(rel()); - break; - case 0x2E: // CBNE dp, rel - break; - case 0xDE: // CBNE dp+X, rel - break; - case 0x6E: // DBNZ dp, rel - break; - case 0xFE: // DBNZ Y, rel - break; - case 0x5F: // JMP !abs - JMP(abs()); - break; - case 0x1F: // JMP [!abs+X] - // JMP_INDIRECT(abs() + X); - break; - - // . subroutines - case 0x3F: // CALL !abs - { - CALL(abs()); + } + case 0x2d: { // pusha imp + read(PC); + push_byte(A); + callbacks_.idle(false); break; } - case 0x4F: // PCALL up - { - PCALL(imm()); + case 0x2e: { // cbne dp, rel + uint8_t val = read(dp()) ^ 0xff; + callbacks_.idle(false); + uint8_t result = A + val + 1; + DoBranch(ReadOpcode(), result != 0); break; } - case 0x6F: // RET - { - RET(); + case 0x2f: { // bra rel + DoBranch(ReadOpcode(), true); break; } - case 0x7F: // RETI - { - RETI(); + case 0x30: { // bmi rel + DoBranch(ReadOpcode(), PSW.N); break; } - - // . stack - case 0x2D: // PUSH A - { - PUSH(A); + case 0x34: { // and dpx + AND(dpx()); break; } - case 0x4D: // PUSH X - { - PUSH(X); + case 0x35: { // and abx + AND(abs_x()); break; } - case 0x6D: // PUSH Y - { - PUSH(Y); + case 0x36: { // and aby + AND(abs_y()); break; } - case 0x0D: // PUSH PSW - { - PUSH(FlagsToByte(PSW)); + case 0x37: { // and idy + AND(idy()); break; } - - case 0xAE: // POP A - { - POP(A); + case 0x38: { // andm dp, imm + uint8_t src = 0; + uint16_t dst = dp_imm(&src); + ANDM(dst, src); break; } - case 0xCE: // POP X - { - POP(X); + case 0x39: { // andm ind, ind + uint8_t src = 0; + uint16_t dst = ind_ind(&src); + ANDM(dst, src); break; } - case 0xEE: // POP Y - { - POP(Y); + case 0x3a: { // incw dp + uint16_t low = 0; + uint16_t high = dp_word(&low); + uint16_t value = read(low) + 1; + write(low, value & 0xff); + value += read(high) << 8; + write(high, value >> 8); + PSW.Z = value == 0; + PSW.N = value & 0x8000; break; } - case 0x8E: // POP PSW - { - uint8_t flags_byte; - POP(flags_byte); - PSW = ByteToFlags(flags_byte); + case 0x3b: { // rol dpx + ROL(dpx()); break; } - - // . memory bit operations - - case 0xEA: // NOT1 abs, bit - // NOT1(abs(), bit()); - break; - case 0xAA: // MOV1 C, abs, bit - break; - case 0xCA: // MOV1 abs, bit, C - break; - case 0x4A: // AND1 C, abs, bit - break; - case 0x6A: // AND1 C, /abs, bit - break; - case 0x0A: // OR1 C, abs, bit - break; - case 0x2A: // OR1 C, /abs, bit - break; - case 0x8A: // EOR1 C, abs, bit - break; - - // . status flags - - case 0x60: // CLRC - CLRC(); - break; - case 0x80: // SETC - SETC(); - break; - case 0xED: // NOTC - NOTC(); - break; - case 0xE0: // CLRV - CLRV(); - break; - case 0x20: // CLRP - CLRP(); - break; - case 0x40: // SETP - SETP(); - break; - case 0xA0: // EI - EI(); - break; - case 0xC0: // DI - DI(); - break; - - // .no-operation and haltF - case 0x00: // NOP - { - NOP(); + case 0x3c: { // rola imp + read(PC); + bool newC = A & 0x80; + A = (A << 1) | PSW.C; + PSW.C = newC; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); break; } - case 0xEF: // SLEEP - { - SLEEP(); + case 0x3d: { // incx imp + read(PC); + X++; + PSW.Z = (X == 0); + PSW.N = (X & 0x80); break; } - case 0x0F: // STOP - { - STOP(); + case 0x3e: { // cmpx dp + CMPX(dp()); + break; + } + case 0x3f: { // call abs + uint16_t dst = ReadOpcodeWord(); + callbacks_.idle(false); + push_word(PC); + callbacks_.idle(false); + callbacks_.idle(false); + PC = dst; + break; + } + case 0x40: { // setp imp + read(PC); + PSW.P = true; + break; + } + case 0x44: { // eor dp + EOR(dp()); + break; + } + case 0x45: { // eor abs + EOR(abs()); + break; + } + case 0x46: { // eor ind + EOR(ind()); + break; + } + case 0x47: { // eor idx + EOR(idx()); + break; + } + case 0x48: { // eor imm + EOR(imm()); + break; + } + case 0x49: { // eorm dp, dp + uint8_t src = 0; + uint16_t dst = dp_dp(&src); + EORM(dst, src); + break; + } + case 0x4a: { // and1 abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + PSW.C = PSW.C & ((read(adr) >> bit) & 1); + break; + } + case 0x4b: { // lsr dp + LSR(dp()); + break; + } + case 0x4c: { // lsr abs + LSR(abs()); + break; + } + case 0x4d: { // pushx imp + read(PC); + push_byte(X); + callbacks_.idle(false); + break; + } + case 0x4e: { // tclr1 abs + uint16_t adr = abs(); + uint8_t val = read(adr); + read(adr); + uint8_t result = A + (val ^ 0xff) + 1; + PSW.Z = (result == 0); + PSW.N = (result & 0x80); + write(adr, val & ~A); + break; + } + case 0x4f: { // pcall dp + uint8_t dst = ReadOpcode(); + callbacks_.idle(false); + push_word(PC); + callbacks_.idle(false); + PC = 0xff00 | dst; + break; + } + case 0x50: { // bvc rel + DoBranch(ReadOpcode(), !PSW.V); + break; + } + case 0x54: { // eor dpx + EOR(dpx()); + break; + } + case 0x55: { // eor abx + EOR(abs_x()); + break; + } + case 0x56: { // eor aby + EOR(abs_y()); + break; + } + case 0x57: { // eor idy + EOR(idy()); + break; + } + case 0x58: { // eorm dp, imm + uint8_t src = 0; + uint16_t dst = dp_imm(&src); + EORM(dst, src); + break; + } + case 0x59: { // eorm ind, ind + uint8_t src = 0; + uint16_t dst = ind_ind(&src); + EORM(dst, src); + break; + } + case 0x5a: { // cmpw dp + uint16_t low = 0; + // uint16_t high = dp_word(&low); + uint16_t value = read_word(low) ^ 0xffff; + uint16_t ya = A | (Y << 8); + int result = ya + value + 1; + PSW.C = result > 0xffff; + PSW.Z = (result & 0xffff) == 0; + PSW.N = result & 0x8000; + break; + } + case 0x5b: { // lsr dpx + LSR(dpx()); + break; + } + case 0x5c: { // lsra imp + read(PC); + PSW.C = A & 1; + A >>= 1; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0x5d: { // movxa imp + read(PC); + X = A; + PSW.Z = (X == 0); + PSW.N = (X & 0x80); + break; + } + case 0x5e: { // cmpy abs + CMPY(abs()); + break; + } + case 0x5f: { // jmp abs + PC = ReadOpcodeWord(); + break; + } + case 0x60: { // clrc imp + read(PC); + PSW.C = false; + break; + } + case 0x64: { // cmp dp + CMP(dp()); + break; + } + case 0x65: { // cmp abs + CMP(abs()); + break; + } + case 0x66: { // cmp ind + CMP(ind()); + break; + } + case 0x67: { // cmp idx + CMP(idx()); + break; + } + case 0x68: { // cmp imm + CMP(imm()); + break; + } + case 0x69: { // cmpm dp, dp + uint8_t src = 0; + uint16_t dst = dp_dp(&src); + CMPM(dst, src); + break; + } + case 0x6a: { // and1n abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + PSW.C = PSW.C & (~(read(adr) >> bit) & 1); + break; + } + case 0x6b: { // ror dp + ROR(dp()); + break; + } + case 0x6c: { // ror abs + ROR(abs()); + break; + } + case 0x6d: { // pushy imp + read(PC); + push_byte(Y); + callbacks_.idle(false); + break; + } + case 0x6e: { // dbnz dp, rel + uint16_t adr = dp(); + uint8_t result = read(adr) - 1; + write(adr, result); + DoBranch(ReadOpcode(), result != 0); + break; + } + case 0x6f: { // ret imp + read(PC); + callbacks_.idle(false); + PC = pull_word(); + break; + } + case 0x70: { // bvs rel + DoBranch(ReadOpcode(), PSW.V); + break; + } + case 0x74: { // cmp dpx + CMP(dpx()); + break; + } + case 0x75: { // cmp abx + CMP(abs_x()); + break; + } + case 0x76: { // cmp aby + CMP(abs_y()); + break; + } + case 0x77: { // cmp idy + CMP(idy()); + break; + } + case 0x78: { // cmpm dp, imm + uint8_t src = 0; + uint16_t dst = dp_imm(&src); + CMPM(dst, src); + break; + } + case 0x79: { // cmpm ind, ind + uint8_t src = 0; + uint16_t dst = ind_ind(&src); + CMPM(dst, src); + break; + } + case 0x7a: { // addw dp + uint16_t low = 0; + uint16_t high = dp_word(&low); + uint8_t vall = read(low); + callbacks_.idle(false); + uint16_t value = vall | (read(high) << 8); + uint16_t ya = A | (Y << 8); + int result = ya + value; + PSW.V = (ya & 0x8000) == (value & 0x8000) && + (value & 0x8000) != (result & 0x8000); + PSW.H = ((ya & 0xfff) + (value & 0xfff)) > 0xfff; + PSW.C = result > 0xffff; + PSW.Z = (result & 0xffff) == 0; + PSW.N = result & 0x8000; + A = result & 0xff; + Y = result >> 8; + break; + } + case 0x7b: { // ror dpx + ROR(dpx()); + break; + } + case 0x7c: { // rora imp + read(PC); + bool newC = A & 1; + A = (A >> 1) | (PSW.C << 7); + PSW.C = newC; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0x7d: { // movax imp + read(PC); + A = X; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0x7e: { // cmpy dp + CMPY(dp()); + break; + } + case 0x7f: { // reti imp + read(PC); + callbacks_.idle(false); + PSW = ByteToFlags(pull_byte()); + PC = pull_word(); + break; + } + case 0x80: { // setc imp + read(PC); + PSW.C = true; + break; + } + case 0x84: { // adc dp + ADC(dp()); + break; + } + case 0x85: { // adc abs + ADC(abs()); + break; + } + case 0x86: { // adc ind + ADC(ind()); + break; + } + case 0x87: { // adc idx + ADC(idx()); + break; + } + case 0x88: { // adc imm + ADC(imm()); + break; + } + case 0x89: { // adcm dp, dp + uint8_t src = 0; + uint16_t dst = dp_dp(&src); + ADCM(dst, src); + break; + } + case 0x8a: { // eor1 abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + PSW.C = PSW.C ^ ((read(adr) >> bit) & 1); + callbacks_.idle(false); + break; + } + case 0x8b: { // dec dp + DEC(dp()); + break; + } + case 0x8c: { // dec abs + DEC(abs()); + break; + } + case 0x8d: { // movy imm + MOVY(imm()); + break; + } + case 0x8e: { // popp imp + read(PC); + callbacks_.idle(false); + PSW = ByteToFlags(pull_byte()); + break; + } + case 0x8f: { // movm dp, imm + uint8_t val = 0; + uint16_t dst = dp_imm(&val); + read(dst); + write(dst, val); + break; + } + case 0x90: { // bcc rel + DoBranch(ReadOpcode(), !PSW.C); + break; + } + case 0x94: { // adc dpx + ADC(dpx()); + break; + } + case 0x95: { // adc abx + ADC(abs_x()); + break; + } + case 0x96: { // adc aby + ADC(abs_y()); + break; + } + case 0x97: { // adc idy + ADC(idy()); + break; + } + case 0x98: { // adcm dp, imm + uint8_t src = 0; + uint16_t dst = dp_imm(&src); + ADCM(dst, src); + break; + } + case 0x99: { // adcm ind, ind + uint8_t src = 0; + uint16_t dst = ind_ind(&src); + ADCM(dst, src); + break; + } + case 0x9a: { // subw dp + uint16_t low = 0; + uint16_t high = dp_word(&low); + uint8_t vall = read(low); + callbacks_.idle(false); + uint16_t value = (vall | (read(high) << 8)) ^ 0xffff; + uint16_t ya = A | (Y << 8); + int result = ya + value + 1; + PSW.V = (ya & 0x8000) == (value & 0x8000) && + (value & 0x8000) != (result & 0x8000); + PSW.H = ((ya & 0xfff) + (value & 0xfff) + 1) > 0xfff; + PSW.C = result > 0xffff; + PSW.Z = (result & 0xffff) == 0; + PSW.N = result & 0x8000; + A = result & 0xff; + Y = result >> 8; + break; + } + case 0x9b: { // dec dpx + DEC(dpx()); + break; + } + case 0x9c: { // deca imp + read(PC); + A--; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0x9d: { // movxp imp + read(PC); + X = SP; + PSW.Z = (X == 0); + PSW.N = (X & 0x80); + break; + } + case 0x9e: { // div imp + read(PC); + for (int i = 0; i < 10; i++) callbacks_.idle(false); + PSW.H = (X & 0xf) <= (Y & 0xf); + int yva = (Y << 8) | A; + int x = X << 9; + for (int i = 0; i < 9; i++) { + yva <<= 1; + yva |= (yva & 0x20000) ? 1 : 0; + yva &= 0x1ffff; + if (yva >= x) yva ^= 1; + if (yva & 1) yva -= x; + yva &= 0x1ffff; + } + Y = yva >> 9; + PSW.V = yva & 0x100; + A = yva & 0xff; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0x9f: { // xcn imp + read(PC); + callbacks_.idle(false); + callbacks_.idle(false); + callbacks_.idle(false); + A = (A >> 4) | (A << 4); + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0xa0: { // ei imp + read(PC); + callbacks_.idle(false); + PSW.I = true; + break; + } + case 0xa4: { // sbc dp + SBC(dp()); + break; + } + case 0xa5: { // sbc abs + SBC(abs()); + break; + } + case 0xa6: { // sbc ind + SBC(ind()); + break; + } + case 0xa7: { // sbc idx + SBC(idx()); + break; + } + case 0xa8: { // sbc imm + SBC(imm()); + break; + } + case 0xa9: { // sbcm dp, dp + uint8_t src = 0; + uint16_t dst = dp_dp(&src); + SBCM(dst, src); + break; + } + case 0xaa: { // mov1 abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + PSW.C = (read(adr) >> bit) & 1; + break; + } + case 0xab: { // inc dp + INC(dp()); + break; + } + case 0xac: { // inc abs + INC(abs()); + break; + } + case 0xad: { // cmpy imm + CMPY(imm()); + break; + } + case 0xae: { // popa imp + read(PC); + callbacks_.idle(false); + A = pull_byte(); + break; + } + case 0xaf: { // movs ind+ + uint16_t adr = ind_p(); + callbacks_.idle(false); + write(adr, A); + break; + } + case 0xb0: { // bcs rel + DoBranch(ReadOpcode(), PSW.C); + break; + } + case 0xb4: { // sbc dpx + SBC(dpx()); + break; + } + case 0xb5: { // sbc abx + SBC(abs_x()); + break; + } + case 0xb6: { // sbc aby + SBC(abs_y()); + break; + } + case 0xb7: { // sbc idy + SBC(idy()); + break; + } + case 0xb8: { // sbcm dp, imm + uint8_t src = 0; + uint16_t dst = dp_imm(&src); + SBCM(dst, src); + break; + } + case 0xb9: { // sbcm ind, ind + uint8_t src = 0; + uint16_t dst = ind_ind(&src); + SBCM(dst, src); + break; + } + case 0xba: { // movw dp + uint16_t low = 0; + uint16_t high = dp_word(&low); + uint8_t vall = read(low); + callbacks_.idle(false); + uint16_t val = vall | (read(high) << 8); + A = val & 0xff; + Y = val >> 8; + PSW.Z = val == 0; + PSW.N = val & 0x8000; + break; + } + case 0xbb: { // inc dpx + INC(dpx()); + break; + } + case 0xbc: { // inca imp + read(PC); + A++; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + ; + break; + } + case 0xbd: { // movpx imp + read(PC); + SP = X; + break; + } + case 0xbe: { // das imp + read(PC); + callbacks_.idle(false); + if (A > 0x99 || !PSW.C) { + A -= 0x60; + PSW.C = false; + } + if ((A & 0xf) > 9 || !PSW.H) { + A -= 6; + } + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0xbf: { // mov ind+ + uint16_t adr = ind_p(); + A = read(adr); + callbacks_.idle(false); + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0xc0: { // di imp + read(PC); + callbacks_.idle(false); + PSW.I = false; + break; + } + case 0xc4: { // movs dp + MOVS(dp()); + break; + } + case 0xc5: { // movs abs + MOVS(abs()); + break; + } + case 0xc6: { // movs ind + MOVS(ind()); + break; + } + case 0xc7: { // movs idx + MOVS(idx()); + break; + } + case 0xc8: { // cmpx imm + CMPX(imm()); + break; + } + case 0xc9: { // movsx abs + MOVSX(abs()); + break; + } + case 0xca: { // mov1s abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + uint8_t result = (read(adr) & (~(1 << bit))) | (PSW.C << bit); + callbacks_.idle(false); + write(adr, result); + break; + } + case 0xcb: { // movsy dp + MOVSY(dp()); + break; + } + case 0xcc: { // movsy abs + MOVSY(abs()); + break; + } + case 0xcd: { // movx imm + MOVX(imm()); + break; + } + case 0xce: { // popx imp + read(PC); + callbacks_.idle(false); + X = pull_byte(); + break; + } + case 0xcf: { // mul imp + read(PC); + for (int i = 0; i < 7; i++) callbacks_.idle(false); + uint16_t result = A * Y; + A = result & 0xff; + Y = result >> 8; + PSW.Z = ((Y & 0xFFFF) == 0); + PSW.N = (Y & 0x8000); + break; + } + case 0xd0: { // bne rel + switch (step++) { + case 1: + dat = ReadOpcode(); + if (PSW.Z) step = 0; + break; + case 2: + callbacks_.idle(false); + break; + case 3: + callbacks_.idle(false); + PC += (int8_t)dat; + step = 0; + break; + } + // DoBranch(ReadOpcode(), !PSW.Z); + break; + } + case 0xd4: { // movs dpx + MOVS(dpx()); + break; + } + case 0xd5: { // movs abx + MOVS(abs_x()); + break; + } + case 0xd6: { // movs aby + MOVS(abs_y()); + break; + } + case 0xd7: { // movs idy + MOVS(idy()); + break; + } + case 0xd8: { // movsx dp + MOVSX(dp()); + break; + } + case 0xd9: { // movsx dpy + MOVSX(dp_y()); + break; + } + case 0xda: { // movws dp + uint16_t low = 0; + uint16_t high = dp_word(&low); + read(low); + write(low, A); + write(high, Y); + break; + } + case 0xdb: { // movsy dpx + MOVSY(dpx()); + break; + } + case 0xdc: { // decy imp + read(PC); + Y--; + PSW.Z = ((Y & 0xFFFF) == 0); + PSW.N = (Y & 0x8000); + break; + } + case 0xdd: { // movay imp + read(PC); + A = Y; + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0xde: { // cbne dpx, rel + uint8_t val = read(dpx()) ^ 0xff; + callbacks_.idle(false); + uint8_t result = A + val + 1; + DoBranch(ReadOpcode(), result != 0); + break; + } + case 0xdf: { // daa imp + read(PC); + callbacks_.idle(false); + if (A > 0x99 || PSW.C) { + A += 0x60; + PSW.C = true; + } + if ((A & 0xf) > 9 || PSW.H) { + A += 6; + } + PSW.Z = (A == 0); + PSW.N = (A & 0x80); + break; + } + case 0xe0: { // clrv imp + read(PC); + PSW.V = false; + PSW.H = false; + break; + } + case 0xe4: { // mov dp + MOV(dp()); + break; + } + case 0xe5: { // mov abs + MOV(abs()); + break; + } + case 0xe6: { // mov ind + MOV(ind()); + break; + } + case 0xe7: { // mov idx + MOV(idx()); + break; + } + case 0xe8: { // mov imm + MOV(imm()); + break; + } + case 0xe9: { // movx abs + MOVX(abs()); + break; + } + case 0xea: { // not1 abs.bit + uint16_t adr = 0; + uint8_t bit = abs_bit(&adr); + uint8_t result = read(adr) ^ (1 << bit); + write(adr, result); + break; + } + case 0xeb: { // movy dp + MOVY(dp()); + break; + } + case 0xec: { // movy abs + MOVY(abs()); + break; + } + case 0xed: { // notc imp + read(PC); + callbacks_.idle(false); + PSW.C = !PSW.C; + break; + } + case 0xee: { // popy imp + read(PC); + callbacks_.idle(false); + Y = pull_byte(); + break; + } + case 0xef: { // sleep imp + read(PC); + callbacks_.idle(false); + stopped_ = true; // no interrupts, so sleeping stops as well + break; + } + case 0xf0: { // beq rel + DoBranch(ReadOpcode(), PSW.Z); + break; + } + case 0xf4: { // mov dpx + MOV(dpx()); + break; + } + case 0xf5: { // mov abx + MOV(abs_x()); + break; + } + case 0xf6: { // mov aby + MOV(abs_y()); + break; + } + case 0xf7: { // mov idy + MOV(idy()); + break; + } + case 0xf8: { // movx dp + MOVX(dp()); + break; + } + case 0xf9: { // movx dpy + MOVX(dp_y()); + break; + } + case 0xfa: { // movm dp, dp + uint8_t val = 0; + uint16_t dst = dp_dp(&val); + write(dst, val); + break; + } + case 0xfb: { // movy dpx + MOVY(dpx()); + break; + } + case 0xfc: { // incy imp + read(PC); + Y++; + PSW.Z = ((Y & 0xFFFF) == 0); + PSW.N = (Y & 0x8000); + break; + } + case 0xfd: { // movya imp + read(PC); + Y = A; + PSW.Z = ((Y & 0xFFFF) == 0); + PSW.N = (Y & 0x8000); + break; + } + case 0xfe: { // dbnzy rel + read(PC); + callbacks_.idle(false); + Y--; + DoBranch(ReadOpcode(), Y != 0); + break; + } + case 0xff: { // stop imp + read(PC); + callbacks_.idle(false); + stopped_ = true; break; } default: - std::cout << "Unknown opcode: " << std::hex << opcode << std::endl; + throw std::runtime_error("Unknown SPC opcode: " + std::to_string(opcode)); break; } - - LogInstruction(initialPC, opcode); } void Spc700::LogInstruction(uint16_t initial_pc, uint8_t opcode) { @@ -950,7 +1322,7 @@ void Spc700::LogInstruction(uint16_t initial_pc, uint8_t opcode) { std::cerr << log_entry << std::endl; // Append the log entry to the log - log_.push_back(log_entry); + // log_.push_back(log_entry); } } // namespace audio diff --git a/src/app/emu/audio/spc700.h b/src/app/emu/audio/spc700.h index 8a8d7b88..07128599 100644 --- a/src/app/emu/audio/spc700.h +++ b/src/app/emu/audio/spc700.h @@ -2,6 +2,7 @@ #define YAZE_APP_EMU_SPC700_H #include +#include #include #include #include @@ -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 write; + std::function read; + std::function 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 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(&aram_.mutable_read(address)); - } else { - // NOTE: Mutable access to IPL ROM is not allowed - return *reinterpret_cast(&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); diff --git a/src/app/emu/cpu/cpu.cc b/src/app/emu/cpu/cpu.cc index 980b5f94..163e1a6c 100644 --- a/src/app/emu/cpu/cpu.cc +++ b/src/app/emu/cpu/cpu.cc @@ -6,1473 +6,1800 @@ #include #include +#include "app/emu/cpu/internal/opcodes.h" + namespace yaze { namespace app { namespace emu { -void Cpu::Update(UpdateMode mode, int stepCount) { - int cycles = (mode == UpdateMode::Run) ? clock.GetCycleCount() : stepCount; +void Cpu::Reset(bool hard) { + if (hard) { + A = 0; + X = 0; + Y = 0; + PC = 0; + PB = 0; + D = 0; + DB = 0; + E = 1; + status = 0x34; + irq_wanted_ = false; + } - // Execute the calculated number of cycles - for (int i = 0; i < cycles; i++) { - if (IsBreakpoint(PC)) { - break; + reset_wanted_ = true; + stopped_ = false; + waiting_ = false; + nmi_wanted_ = false; + int_wanted_ = false; + int_delay_ = false; +} + +void Cpu::RunOpcode() { + if (reset_wanted_) { + reset_wanted_ = false; + // reset: brk/interrupt without writes + auto sp = SP(); + + ReadByte((PB << 16) | PC); + callbacks_.idle(false); + ReadByte(0x100 | (sp-- & 0xff)); + ReadByte(0x100 | (sp-- & 0xff)); + ReadByte(0x100 | (sp-- & 0xff)); + sp = (sp & 0xff) | 0x100; + + SetSP(sp); + + E = 1; + SetInterruptFlag(true); + SetDecimalFlag(false); + SetFlags(status); // updates x and m flags, clears + // upper half of x and y if needed + PB = 0; + PC = ReadWord(0xfffc, 0xfffd); + return; + } + if (stopped_) { + callbacks_.idle(true); + return; + } + if (waiting_) { + if (irq_wanted_ || nmi_wanted_) { + waiting_ = false; + callbacks_.idle(false); + CheckInt(); + callbacks_.idle(false); + return; + } else { + callbacks_.idle(true); + return; } + } + // not stopped or waiting, execute a opcode or go to interrupt + if (int_wanted_) { + ReadByte((PB << 16) | PC); + DoInterrupt(); + } else { + uint8_t opcode = ReadOpcode(); + ExecuteInstruction(opcode); + } +} - // Fetch and execute an instruction - ExecuteInstruction(ReadByte((PB << 16) + PC)); - - // Handle any interrupts, if necessary - HandleInterrupts(); - - if (mode == UpdateMode::Step) { - break; - } +void Cpu::DoInterrupt() { + callbacks_.idle(false); + PushByte(PB); + PushWord(PC); + PushByte(status); + SetInterruptFlag(true); + SetDecimalFlag(false); + PB = 0; + int_wanted_ = false; + if (nmi_wanted_) { + nmi_wanted_ = false; + PC = ReadWord(0xffea, 0xffeb); + } else { // irq + PC = ReadWord(0xffee, 0xffef); } } void Cpu::ExecuteInstruction(uint8_t opcode) { - uint8_t cycles = 0; uint8_t instruction_length = 0; + uint16_t cache_pc = PC; uint32_t operand = 0; bool immediate = false; bool accumulator_mode = GetAccumulatorSize(); switch (opcode) { - case 0x61: // ADC DP Indexed Indirect, X - { - cycles = 6; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByte(DirectPageIndexedIndirectX()); - ADC(operand); + case 0x00: { // brk imm(s) + uint32_t vector = (E) ? 0xfffe : 0xffe6; + ReadOpcode(); + if (!E) PushByte(PB); + PushWord(PC, false); + PushByte(status); + SetInterruptFlag(true); + SetDecimalFlag(false); + PB = 0; + PC = ReadWord(vector, vector + 1, true); break; } - case 0x63: // ADC Stack Relative - { - cycles = 4; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByte(StackRelative()); - ADC(operand); + case 0x01: { // ora idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + ORA(low, high); break; } - case 0x65: // ADC Direct Page - { - cycles = 3; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByte(DirectPage()); - ADC(operand); + case 0x02: { // cop imm(s) + uint32_t vector = (E) ? 0xfff4 : 0xffe4; + ReadOpcode(); + if (!E) PushByte(PB); + PushWord(PC, false); + PushByte(status); + SetInterruptFlag(true); + SetDecimalFlag(false); + PB = 0; + PC = ReadWord(vector, vector + 1, true); break; } - case 0x67: // ADC DP Indirect Long - { - cycles = 6; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadWord(DirectPageIndirectLong()); - ADC(operand); + case 0x03: { // ora sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + ORA(low, high); break; } - case 0x69: // ADC Immediate - { - cycles = 2; - if (!m()) cycles++; + case 0x04: { // tsb dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Tsb(low, high); + break; + } + case 0x05: { // ora dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + ORA(low, high); + break; + } + case 0x06: { // asl dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Asl(low, high); + break; + } + case 0x07: { // ora idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + ORA(low, high); + break; + } + case 0x08: { // php imp + callbacks_.idle(false); + CheckInt(); + PushByte(status); + break; + } + case 0x09: { // ora imm(m) + uint32_t low = 0; + uint32_t high = Immediate(&low, false); + ORA(low, high); + break; + } + case 0x0a: { // asla imp + AdrImp(); if (GetAccumulatorSize()) { - instruction_length = 2; + SetCarryFlag(A & 0x80); + A = (A & 0xff00) | ((A << 1) & 0xff); } else { - instruction_length = 3; + SetCarryFlag(A & 0x8000); + A <<= 1; } - operand = Immediate(); - immediate = true; - ADC(operand); - break; - } - case 0x6D: // ADC Absolute - { - cycles = 4; - if (!m()) cycles++; - instruction_length = 3; - operand = ReadWord(Absolute()); - ADC(operand); - break; + SetZN(A, GetAccumulatorSize()); + break; + } + case 0x0b: { // phd imp + callbacks_.idle(false); + PushWord(D, true); + break; + } + case 0x0c: { // tsb abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Tsb(low, high); + break; + } + case 0x0d: { // ora abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + ORA(low, high); + break; + } + case 0x0e: { // asl abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Asl(low, high); + break; + } + case 0x0f: { // ora abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + ORA(low, high); + break; + } + case 0x10: { // bpl rel + DoBranch(!GetNegativeFlag()); + break; + } + case 0x11: { // ora idy(r) + uint32_t low = 0; + uint32_t high = AdrIdy(&low, false); + ORA(low, high); + break; + } + case 0x12: { // ora idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + ORA(low, high); + break; + } + case 0x13: { // ora isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + ORA(low, high); + break; + } + case 0x14: { // trb dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Trb(low, high); + break; + } + case 0x15: { // ora dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + ORA(low, high); + break; + } + case 0x16: { // asl dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Asl(low, high); + break; + } + case 0x17: { // ora ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + ORA(low, high); + break; + } + case 0x18: { // clc imp + AdrImp(); + SetCarryFlag(false); + break; + } + case 0x19: { // ora aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + ORA(low, high); + break; + } + case 0x1a: { // inca imp + AdrImp(); + if (GetAccumulatorSize()) { + A = (A & 0xff00) | ((A + 1) & 0xff); + } else { + A++; + } + SetZN(A, GetAccumulatorSize()); + break; + } + case 0x1b: { // tcs imp + AdrImp(); + SetSP(A); + break; + } + case 0x1c: { // trb abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Trb(low, high); + break; + } + case 0x1d: { // ora abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + ORA(low, high); + break; + } + case 0x1e: { // asl abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Asl(low, high); + break; + } + case 0x1f: { // ora alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + ORA(low, high); + break; + } + case 0x20: { // jsr abs + uint16_t value = ReadOpcodeWord(false); + callbacks_.idle(false); + PushWord(PC - 1, true); + PC = value; + break; + } + case 0x21: { // and idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + And(low, high); + break; + } + case 0x22: { // jsl abl + uint16_t value = ReadOpcodeWord(false); + PushByte(PB); + callbacks_.idle(false); + uint8_t newK = ReadOpcode(); + PushWord(PC - 1, true); + PC = value; + PB = newK; + break; + } + case 0x23: { // and sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + And(low, high); + break; + } + case 0x24: { // bit dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Bit(low, high); + break; + } + case 0x25: { // and dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + And(low, high); + break; + } + case 0x26: { // rol dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Rol(low, high); + break; + } + case 0x27: { // and idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + And(low, high); + break; + } + case 0x28: { // plp imp + callbacks_.idle(false); + callbacks_.idle(false); + CheckInt(); + SetFlags(PopByte()); + break; + } + case 0x29: { // and imm(m) + uint32_t low = 0; + uint32_t high = Immediate(&low, false); + And(low, high); + break; + } + case 0x2a: { // rola imp + AdrImp(); + int result = (A << 1) | GetCarryFlag(); + if (GetAccumulatorSize()) { + SetCarryFlag(result & 0x100); + A = (A & 0xff00) | (result & 0xff); + } else { + SetCarryFlag(result & 0x10000); + A = result; + } + SetZN(A, GetAccumulatorSize()); + break; + } + case 0x2b: { // pld imp + callbacks_.idle(false); + callbacks_.idle(false); + D = PopWord(true); + SetZN(D, false); + break; + } + case 0x2c: { // bit abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Bit(low, high); + break; + } + case 0x2d: { // and abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + And(low, high); + break; + } + case 0x2e: { // rol abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Rol(low, high); + break; + } + case 0x2f: { // and abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + And(low, high); + break; + } + case 0x30: { // bmi rel + DoBranch(GetNegativeFlag()); + break; + } + case 0x31: { // and idy(r) + uint32_t low = 0; + uint32_t high = AdrIdy(&low, false); + And(low, high); + break; + } + case 0x32: { // and idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + And(low, high); + break; + } + case 0x33: { // and isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + And(low, high); + break; + } + case 0x34: { // bit dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Bit(low, high); + break; } - case 0x6F: // ADC Absolute Long - { - cycles = 5; - if (!m()) cycles++; - instruction_length = 4; - operand = ReadWord(AbsoluteLong()); - ADC(operand); + case 0x35: { // and dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + And(low, high); break; } - case 0x71: // ADC DP Indirect Indexed, Y - { - cycles = 5; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByteOrWord(DirectPageIndirectIndexedY()); - ADC(operand); + case 0x36: { // rol dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Rol(low, high); break; } - case 0x72: // ADC DP Indirect - { - cycles = 5; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByte(DirectPageIndirect()); - ADC(operand); + case 0x37: { // and ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + And(low, high); break; } - case 0x73: // ADC SR Indirect Indexed, Y - { - cycles = 7; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByte(StackRelativeIndirectIndexedY()); - ADC(operand); + case 0x38: { // sec imp + AdrImp(); + SetCarryFlag(true); break; } - case 0x75: // ADC DP Indexed, X - { - cycles = 4; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByteOrWord(DirectPageIndexedX()); - ADC(operand); + case 0x39: { // and aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + And(low, high); break; } - case 0x77: // ADC DP Indirect Long Indexed, Y - { - cycles = 6; - if (!m()) cycles++; - instruction_length = 2; - operand = ReadByteOrWord(DirectPageIndirectLongIndexedY()); - ADC(operand); + case 0x3a: { // deca imp + AdrImp(); + if (GetAccumulatorSize()) { + A = (A & 0xff00) | ((A - 1) & 0xff); + } else { + A--; + } + SetZN(A, GetAccumulatorSize()); break; } - case 0x79: // ADC Absolute Indexed, Y - { - cycles = 4; - if (!m()) cycles++; - instruction_length = 3; - operand = ReadWord(AbsoluteIndexedY()); - ADC(operand); + case 0x3b: { // tsc imp + AdrImp(); + A = SP(); + SetZN(A, false); break; } - case 0x7D: // ADC Absolute Indexed, X - { - cycles = 4; - if (!m()) cycles++; - instruction_length = 3; - operand = ReadWord(AbsoluteIndexedX()); - ADC(operand); + case 0x3c: { // bit abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + Bit(low, high); break; } - case 0x7F: // ADC Absolute Long Indexed, X - { - cycles = 5; - if (!m()) cycles++; - instruction_length = 4; - operand = ReadByteOrWord(AbsoluteLongIndexedX()); - ADC(operand); + case 0x3d: { // and abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + And(low, high); break; } - - case 0x21: // AND DP Indexed Indirect, X - { - operand = ReadByteOrWord(DirectPageIndexedIndirectX()); - AND(operand, true); // Not immediate, but value has been retrieved + case 0x3e: { // rol abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Rol(low, high); break; } - case 0x23: // AND Stack Relative - { - operand = StackRelative(); - AND(operand); + case 0x3f: { // and alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + And(low, high); break; } - case 0x25: // AND Direct Page - { - operand = DirectPage(); - AND(operand); + case 0x40: { // rti imp + callbacks_.idle(false); + callbacks_.idle(false); + SetFlags(PopByte()); + PC = PopWord(false); + CheckInt(); + PB = PopByte(); break; } - case 0x27: // AND DP Indirect Long - { - operand = DirectPageIndirectLong(); - AND(operand); + case 0x41: { // eor idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + Eor(low, high); break; } - case 0x29: // AND Immediate - { - operand = Immediate(); - immediate = true; - AND(operand, true); + case 0x42: { // wdm imm(s) + CheckInt(); + ReadOpcode(); break; } - case 0x2D: // AND Absolute - { - operand = Absolute(); - AND(operand); + case 0x43: { // eor sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + Eor(low, high); break; } - case 0x2F: // AND Absolute Long - { - operand = AbsoluteLong(); - ANDAbsoluteLong(operand); + case 0x44: { // mvp bm + uint8_t dest = ReadOpcode(); + uint8_t src = ReadOpcode(); + DB = dest; + WriteByte((dest << 16) | Y, ReadByte((src << 16) | X)); + A--; + X--; + Y--; + if (A != 0xffff) { + PC -= 3; + } + if (GetIndexSize()) { + X &= 0xff; + Y &= 0xff; + } + callbacks_.idle(false); + CheckInt(); + callbacks_.idle(false); break; } - case 0x31: // AND DP Indirect Indexed, Y - { - operand = DirectPageIndirectIndexedY(); - AND(operand); + case 0x45: { // eor dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Eor(low, high); break; } - case 0x32: // AND DP Indirect - { - operand = DirectPageIndirect(); - AND(operand); + case 0x46: { // lsr dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Lsr(low, high); break; } - case 0x33: // AND SR Indirect Indexed, Y - { - operand = StackRelativeIndirectIndexedY(); - AND(operand); + case 0x47: { // eor idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + Eor(low, high); break; } - case 0x35: // AND DP Indexed, X - { - operand = DirectPageIndexedX(); - AND(operand); + case 0x48: { // pha imp + callbacks_.idle(false); + if (GetAccumulatorSize()) { + CheckInt(); + PushByte(A); + } else { + PushWord(A, true); + } break; } - case 0x37: // AND DP Indirect Long Indexed, Y - { - operand = DirectPageIndirectLongIndexedY(); - AND(operand); + case 0x49: { // eor imm(m) + uint32_t low = 0; + uint32_t high = Immediate(&low, false); + Eor(low, high); break; } - case 0x39: // AND Absolute Indexed, Y - { - operand = AbsoluteIndexedY(); - AND(operand); + case 0x4a: { // lsra imp + AdrImp(); + SetCarryFlag(A & 1); + if (GetAccumulatorSize()) { + A = (A & 0xff00) | ((A >> 1) & 0x7f); + } else { + A >>= 1; + } + SetZN(A, GetAccumulatorSize()); break; } - case 0x3D: // AND Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - AND(operand); + case 0x4b: { // phk imp + callbacks_.idle(false); + CheckInt(); + PushByte(PB); break; } - case 0x3F: // AND Absolute Long Indexed, X - { - operand = AbsoluteLongIndexedX(); - AND(operand); + case 0x4c: { // jmp abs + PC = ReadOpcodeWord(true); break; } - - case 0x06: // ASL Direct Page - { - operand = DirectPage(); - ASL(operand); + case 0x4d: { // eor abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Eor(low, high); break; } - case 0x0A: // ASL Accumulator - { - A <<= 1; - A &= 0xFE; - SetCarryFlag(A & 0x80); - SetNegativeFlag(A); - SetZeroFlag(!A); + case 0x4e: { // lsr abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Lsr(low, high); break; } - case 0x0E: // ASL Absolute - { - operand = Absolute(); - ASL(operand); + case 0x4f: { // eor abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + Eor(low, high); break; } - case 0x16: // ASL DP Indexed, X - { - operand = ReadByteOrWord((0x00 << 16) + DirectPageIndexedX()); - ASL(operand); + case 0x50: { // bvc rel + DoBranch(!GetOverflowFlag()); break; } - case 0x1E: // ASL Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - ASL(operand); + case 0x51: { // eor idy(r) + uint32_t low = 0; + uint32_t high = AdrIdy(&low, false); + Eor(low, high); break; } - - case 0x90: // BCC Branch if carry clear - { - operand = FetchByte(); - BCC(operand); + case 0x52: { // eor idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + Eor(low, high); break; } - - case 0xB0: // BCS Branch if carry set - { - operand = FetchByte(); - BCS(operand); + case 0x53: { // eor isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + Eor(low, high); break; } - - case 0xF0: // BEQ Branch if equal (zero set) - { - operand = FetchByte(); - BEQ(operand); + case 0x54: { // mvn bm + uint8_t dest = ReadOpcode(); + uint8_t src = ReadOpcode(); + DB = dest; + WriteByte((dest << 16) | Y, ReadByte((src << 16) | X)); + A--; + X++; + Y++; + if (A != 0xffff) { + PC -= 3; + } + if (GetIndexSize()) { + X &= 0xff; + Y &= 0xff; + } + callbacks_.idle(false); + CheckInt(); + callbacks_.idle(false); break; } - - case 0x24: // BIT Direct Page - { - operand = DirectPage(); - BIT(operand); + case 0x55: { // eor dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Eor(low, high); break; } - case 0x2C: // BIT Absolute - { - operand = Absolute(); - BIT(operand); + case 0x56: { // lsr dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Lsr(low, high); break; } - case 0x34: // BIT DP Indexed, X - { - operand = DirectPageIndexedX(); - BIT(operand); + case 0x57: { // eor ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + Eor(low, high); break; } - case 0x3C: // BIT Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - BIT(operand); + case 0x58: { // cli imp + AdrImp(); + SetInterruptFlag(false); break; } - case 0x89: // BIT Immediate - { - operand = Immediate(); - BIT(operand); - immediate = true; + case 0x59: { // eor aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + Eor(low, high); break; } - - case 0x30: // BMI Branch if minus (negative set) - { - operand = FetchByte(); - BMI(operand); + case 0x5a: { // phy imp + callbacks_.idle(false); + if (GetIndexSize()) { + CheckInt(); + PushByte(Y); + } else { + PushWord(Y, true); + } break; } - - case 0xD0: // BNE Branch if not equal (zero clear) - { - operand = FetchSignedByte(); - BNE(operand); + case 0x5b: { // tcd imp + AdrImp(); + D = A; + SetZN(D, false); break; } - - case 0x10: // BPL Branch if plus (negative clear) - { - operand = FetchSignedByte(); - BPL(operand); + case 0x5c: { // jml abl + uint16_t value = ReadOpcodeWord(false); + CheckInt(); + PB = ReadOpcode(); + PC = value; break; } - - case 0x80: // BRA Branch always - { - operand = FetchByte(); - BRA(operand); + case 0x5d: { // eor abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + Eor(low, high); break; } - - case 0x00: // BRK Break - { - BRK(); - std::cout << "BRK" << std::endl; - // Print all the registers - std::cout << "A: " << std::hex << std::setw(2) << std::setfill('0') - << (int)A << std::endl; - std::cout << "X: " << std::hex << std::setw(2) << std::setfill('0') - << (int)X << std::endl; - std::cout << "Y: " << std::hex << std::setw(2) << std::setfill('0') - << (int)Y << std::endl; - std::cout << "S: " << std::hex << std::setw(2) << std::setfill('0') - << (int)SP() << std::endl; - std::cout << "PC: " << std::hex << std::setw(4) << std::setfill('0') - << (int)PC << std::endl; - std::cout << "PB: " << std::hex << std::setw(2) << std::setfill('0') - << (int)PB << std::endl; - std::cout << "D: " << std::hex << std::setw(4) << std::setfill('0') - << (int)D << std::endl; - std::cout << "DB: " << std::hex << std::setw(2) << std::setfill('0') - << (int)DB << std::endl; - std::cout << "E: " << std::hex << std::setw(2) << std::setfill('0') - << (int)E << std::endl; - // status registers - std::cout << "C: " << std::hex << std::setw(2) << std::setfill('0') - << (int)status << std::endl; + case 0x5e: { // lsr abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Lsr(low, high); break; } - - case 0x82: // BRL Branch always long - { // operand = FetchSignedWord(); - operand = FetchWord(); - BRL(operand); + case 0x5f: { // eor alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + Eor(low, high); break; } - - case 0x50: // BVC Branch if overflow clear - { - operand = FetchByte(); - BVC(operand); + case 0x60: { // rts imp + callbacks_.idle(false); + callbacks_.idle(false); + PC = PopWord(false) + 1; + CheckInt(); + callbacks_.idle(false); break; } - - case 0x70: // BVS Branch if overflow set - { - operand = FetchByte(); - BVS(operand); + case 0x61: { // adc idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + Adc(low, high); break; } - - case 0x18: // CLC Clear carry - { - CLC(); + case 0x62: { // per rll + uint16_t value = ReadOpcodeWord(false); + callbacks_.idle(false); + PushWord(PC + (int16_t)value, true); break; } - - case 0xD8: // CLD Clear decimal - { - CLD(); + case 0x63: { // adc sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + Adc(low, high); break; } - - case 0x58: // CLI Clear interrupt disable - { - CLI(); + case 0x64: { // stz dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Stz(low, high); break; } - - case 0xB8: // CLV Clear overflow - { - CLV(); + case 0x65: { // adc dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Adc(low, high); break; } - - case 0xC1: // CMP DP Indexed Indirect, X - { - operand = ReadByteOrWord(DirectPageIndexedIndirectX()); - CMP(operand); + case 0x66: { // ror dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Ror(low, high); break; } - case 0xC3: // CMP Stack Relative - { - operand = StackRelative(); - CMP(operand); + case 0x67: { // adc idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + Adc(low, high); break; } - case 0xC5: // CMP Direct Page - { - operand = DirectPage(); - CMP(operand); + case 0x68: { // pla imp + callbacks_.idle(false); + callbacks_.idle(false); + if (GetAccumulatorSize()) { + CheckInt(); + A = (A & 0xff00) | PopByte(); + } else { + A = PopWord(true); + } + SetZN(A, GetAccumulatorSize()); break; } - case 0xC7: // CMP DP Indirect Long - { - operand = DirectPageIndirectLong(); - CMP(operand); + case 0x69: { // adc imm(m) + uint32_t low = 0; + uint32_t high = Immediate(&low, false); + Adc(low, high); break; } - case 0xC9: // CMP Immediate - { - operand = Immediate(); - immediate = true; - CMP(operand, immediate); + case 0x6a: { // rora imp + AdrImp(); + bool carry = A & 1; + auto C = GetCarryFlag(); + if (GetAccumulatorSize()) { + A = (A & 0xff00) | ((A >> 1) & 0x7f) | (C << 7); + } else { + A = (A >> 1) | (C << 15); + } + SetCarryFlag(carry); + SetZN(A, GetAccumulatorSize()); break; } - case 0xCD: // CMP Absolute - { - operand = Absolute(AccessType::Data); - CMP(operand); + case 0x6b: { // rtl imp + callbacks_.idle(false); + callbacks_.idle(false); + PC = PopWord(false) + 1; + CheckInt(); + PB = PopByte(); break; } - case 0xCF: // CMP Absolute Long - { - operand = AbsoluteLong(); - CMP(operand); + case 0x6c: { // jmp ind + uint16_t adr = ReadOpcodeWord(false); + PC = ReadWord(adr, (adr + 1) & 0xffff, true); break; } - case 0xD1: // CMP DP Indirect Indexed, Y - { - operand = DirectPageIndirectIndexedY(); - CMP(operand); + case 0x6d: { // adc abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Adc(low, high); break; } - case 0xD2: // CMP DP Indirect - { - operand = DirectPageIndirect(); - CMP(operand); + case 0x6e: { // ror abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Ror(low, high); break; } - case 0xD3: // CMP SR Indirect Indexed, Y - { - operand = StackRelativeIndirectIndexedY(); - CMP(operand); + case 0x6f: { // adc abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + Adc(low, high); break; } - case 0xD5: // CMP DP Indexed, X - { - operand = DirectPageIndexedX(); - CMP(operand); + case 0x70: { // bvs rel + DoBranch(GetOverflowFlag()); break; } - case 0xD7: // CMP DP Indirect Long Indexed, Y - { - operand = DirectPageIndirectLongIndexedY(); - CMP(operand); + case 0x71: { // adc idy(r) + uint32_t low = 0; + uint32_t high = AdrIdy(&low, false); + Adc(low, high); break; } - case 0xD9: // CMP Absolute Indexed, Y - { - operand = AbsoluteIndexedY(); - CMP(operand); + case 0x72: { // adc idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + Adc(low, high); break; } - case 0xDD: // CMP Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - CMP(operand); + case 0x73: { // adc isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + Adc(low, high); break; } - case 0xDF: // CMP Absolute Long Indexed, X - { - operand = AbsoluteLongIndexedX(); - CMP(operand); + case 0x74: { // stz dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Stz(low, high); break; } - - case 0x02: // COP - { - COP(); + case 0x75: { // adc dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Adc(low, high); break; } - - case 0xE0: // CPX Immediate - { - operand = Immediate(/*index_size=*/true); - immediate = true; - CPX(operand, immediate); + case 0x76: { // ror dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Ror(low, high); break; } - case 0xE4: // CPX Direct Page - { - operand = DirectPage(); - CPX(operand); + case 0x77: { // adc ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + Adc(low, high); break; } - case 0xEC: // CPX Absolute - { - operand = Absolute(); - CPX(operand); + case 0x78: { // sei imp + AdrImp(); + SetInterruptFlag(true); break; } - - case 0xC0: // CPY Immediate - { - operand = Immediate(); - immediate = true; - CPY(operand, immediate); + case 0x79: { // adc aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + Adc(low, high); break; } - case 0xC4: // CPY Direct Page - { - operand = DirectPage(); - CPY(operand); + case 0x7a: { // ply imp + callbacks_.idle(false); + callbacks_.idle(false); + if (GetIndexSize()) { + CheckInt(); + Y = PopByte(); + } else { + Y = PopWord(true); + } + SetZN(Y, GetIndexSize()); break; } - case 0xCC: // CPY Absolute - { - operand = Absolute(); - CPY(operand); + case 0x7b: { // tdc imp + AdrImp(); + A = D; + SetZN(A, false); break; } - - case 0x3A: // DEC Accumulator - { - DEC(A, /*accumulator=*/true); + case 0x7c: { // jmp iax + uint16_t adr = ReadOpcodeWord(false); + callbacks_.idle(false); + PC = ReadWord((PB << 16) | ((adr + X) & 0xffff), + ((PB << 16) | ((adr + X + 1) & 0xffff)), true); break; } - case 0xC6: // DEC Direct Page - { - operand = DirectPage(); - DEC(operand); + case 0x7d: { // adc abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + Adc(low, high); break; } - case 0xCE: // DEC Absolute - { - operand = Absolute(); - DEC(operand); + case 0x7e: { // ror abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Ror(low, high); break; } - case 0xD6: // DEC DP Indexed, X - { - operand = DirectPageIndexedX(); - DEC(operand); + case 0x7f: { // adc alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + Adc(low, high); break; } - case 0xDE: // DEC Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - DEC(operand); + case 0x80: { // bra rel + DoBranch(true); break; } - - case 0xCA: // DEX - { - DEX(); + case 0x81: { // sta idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + Sta(low, high); break; } - - case 0x88: // DEY - { - DEY(); + case 0x82: { // brl rll + PC += (int16_t)ReadOpcodeWord(false); + CheckInt(); + callbacks_.idle(false); break; } - - case 0x41: // EOR DP Indexed Indirect, X - { - operand = DirectPageIndexedIndirectX(); - EOR(operand); + case 0x83: { // sta sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + Sta(low, high); break; } - case 0x43: // EOR Stack Relative - { - operand = StackRelative(); - EOR(operand); + case 0x84: { // sty dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Sty(low, high); break; } - case 0x45: // EOR Direct Page - { - operand = DirectPage(); - EOR(operand); + case 0x85: { // sta dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Sta(low, high); break; } - case 0x47: // EOR DP Indirect Long - { - operand = DirectPageIndirectLong(); - EOR(operand); + case 0x86: { // stx dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Stx(low, high); break; } - case 0x49: // EOR Immediate - { - operand = Immediate(); - immediate = true; - EOR(operand, immediate); + case 0x87: { // sta idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + Sta(low, high); break; } - case 0x4D: // EOR Absolute - { - operand = Absolute(); - EOR(operand); + case 0x88: { // dey imp + AdrImp(); + if (GetIndexSize()) { + Y = (Y - 1) & 0xff; + } else { + Y--; + } + SetZN(Y, GetIndexSize()); break; } - case 0x4F: // EOR Absolute Long - { - operand = AbsoluteLong(); - EOR(operand); + case 0x89: { // biti imm(m) + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t result = (A & 0xff) & ReadOpcode(); + SetZeroFlag(result == 0); + } else { + uint16_t result = A & ReadOpcodeWord(true); + SetZeroFlag(result == 0); + } break; } - case 0x51: // EOR DP Indirect Indexed, Y - { - operand = DirectPageIndirectIndexedY(); - EOR(operand); + case 0x8a: { // txa imp + AdrImp(); + if (GetAccumulatorSize()) { + A = (A & 0xff00) | (X & 0xff); + } else { + A = X; + } + SetZN(A, GetAccumulatorSize()); break; } - case 0x52: // EOR DP Indirect - { - operand = DirectPageIndirect(); - EOR(operand); + case 0x8b: { // phb imp + callbacks_.idle(false); + CheckInt(); + PushByte(DB); break; } - case 0x53: // EOR SR Indirect Indexed, Y - { - operand = StackRelativeIndirectIndexedY(); - EOR(operand); + case 0x8c: { // sty abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Sty(low, high); break; } - case 0x55: // EOR DP Indexed, X - { - operand = DirectPageIndexedX(); - EOR(operand); + case 0x8d: { // sta abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Sta(low, high); break; } - case 0x57: // EOR DP Indirect Long Indexed, Y - { - operand = ReadByteOrWord(DirectPageIndirectLongIndexedY()); - EOR(operand); + case 0x8e: { // stx abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Stx(low, high); break; } - case 0x59: // EOR Absolute Indexed, Y - { - operand = AbsoluteIndexedY(); - EOR(operand); + case 0x8f: { // sta abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + Sta(low, high); break; } - case 0x5D: // EOR Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - EOR(operand); + case 0x90: { // bcc rel + DoBranch(!GetCarryFlag()); break; } - case 0x5F: // EOR Absolute Long Indexed, X - { - operand = AbsoluteLongIndexedX(); - EOR(operand); + case 0x91: { // sta idy + uint32_t low = 0; + uint32_t high = AdrIdy(&low, true); + Sta(low, high); break; } - - case 0x1A: // INC Accumulator - { - INC(A, /*accumulator=*/true); + case 0x92: { // sta idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + Sta(low, high); break; } - case 0xE6: // INC Direct Page - { - operand = DirectPage(); - INC(operand); + case 0x93: { // sta isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + Sta(low, high); break; } - case 0xEE: // INC Absolute - { - operand = Absolute(); - INC(operand); + case 0x94: { // sty dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Sty(low, high); break; } - case 0xF6: // INC DP Indexed, X - { - operand = DirectPageIndexedX(); - INC(operand); + case 0x95: { // sta dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Sta(low, high); break; } - case 0xFE: // INC Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - INC(operand); + case 0x96: { // stx dpy + uint32_t low = 0; + uint32_t high = AdrDpy(&low); + Stx(low, high); break; } - - case 0xE8: // INX - { - INX(); + case 0x97: { // sta ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + Sta(low, high); break; } - - case 0xC8: // INY - { - INY(); + case 0x98: { // tya imp + AdrImp(); + if (GetAccumulatorSize()) { + A = (A & 0xff00) | (Y & 0xff); + } else { + A = Y; + } + SetZN(A, GetAccumulatorSize()); break; } - - case 0x4C: // JMP Absolute - { - JMP(Absolute()); + case 0x99: { // sta aby + uint32_t low = 0; + uint32_t high = AdrAby(&low, true); + Sta(low, high); break; } - case 0x5C: // JMP Absolute Long - { - JML(AbsoluteLong()); + case 0x9a: { // txs imp + AdrImp(); + SetSP(X); break; } - case 0x6C: // JMP Absolute Indirect - { - JMP(AbsoluteIndirect()); + case 0x9b: { // txy imp + AdrImp(); + if (GetIndexSize()) { + Y = X & 0xff; + } else { + Y = X; + } + SetZN(Y, GetIndexSize()); break; } - case 0x7C: // JMP Absolute Indexed Indirect - { - JMP(AbsoluteIndexedIndirect()); + case 0x9c: { // stz abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Stz(low, high); break; } - case 0xDC: // JMP Absolute Indirect Long - { - operand = AbsoluteIndirectLong(); - JMP(operand); - PB = operand >> 16; + case 0x9d: { // sta abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Sta(low, high); break; } - - case 0x20: // JSR Absolute - { - operand = Absolute(AccessType::Control); - PB = (operand >> 16); - JSR(operand); + case 0x9e: { // stz abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Stz(low, high); break; } - - case 0x22: // JSL Absolute Long - { - JSL(AbsoluteLong()); + case 0x9f: { // sta alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + Sta(low, high); break; } - - case 0xFC: // JSR Absolute Indexed Indirect - { - JSR(AbsoluteIndexedIndirect()); + case 0xa0: { // ldy imm(x) + uint32_t low = 0; + uint32_t high = Immediate(&low, true); + Ldy(low, high); break; } - - case 0xA1: // LDA DP Indexed Indirect, X - { - operand = DirectPageIndexedIndirectX(); - LDA(operand); + case 0xa1: { // lda idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + Lda(low, high); break; } - case 0xA3: // LDA Stack Relative - { - operand = StackRelative(); - LDA(operand); + case 0xa2: { // ldx imm(x) + uint32_t low = 0; + uint32_t high = Immediate(&low, true); + Ldx(low, high); break; } - case 0xA5: // LDA Direct Page - { - operand = DirectPage(); - LDA(operand, false, true); + case 0xa3: { // lda sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + Lda(low, high); break; } - case 0xA7: // LDA DP Indirect Long - { - operand = DirectPageIndirectLong(); - LDA(operand); + case 0xa4: { // ldy dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Ldy(low, high); break; } - case 0xA9: // LDA Immediate - { - operand = Immediate(); - immediate = true; - LDA(operand, immediate); + case 0xa5: { // lda dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Lda(low, high); break; } - case 0xAD: // LDA Absolute - { - operand = Absolute(); - LDA(operand); + case 0xa6: { // ldx dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Ldx(low, high); break; } - case 0xAF: // LDA Absolute Long - { - operand = AbsoluteLong(); - LDA(operand); + case 0xa7: { // lda idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + Lda(low, high); break; } - case 0xB1: // LDA DP Indirect Indexed, Y - { - operand = DirectPageIndirectIndexedY(); - LDA(operand); + case 0xa8: { // tay imp + AdrImp(); + if (GetIndexSize()) { + Y = A & 0xff; + } else { + Y = A; + } + SetZN(Y, GetIndexSize()); break; } - case 0xB2: // LDA DP Indirect - { - operand = DirectPageIndirect(); - LDA(operand); + case 0xa9: { // lda imm(m) + uint32_t low = 0; + uint32_t high = Immediate(&low, false); + Lda(low, high); break; } - case 0xB3: // LDA SR Indirect Indexed, Y - { - operand = StackRelativeIndirectIndexedY(); - LDA(operand); + case 0xaa: { // tax imp + AdrImp(); + if (GetIndexSize()) { + X = A & 0xff; + } else { + X = A; + } + SetZN(X, GetIndexSize()); break; } - case 0xB5: // LDA DP Indexed, X - { - operand = DirectPageIndexedX(); - LDA(operand); + case 0xab: { // plb imp + callbacks_.idle(false); + callbacks_.idle(false); + CheckInt(); + DB = PopByte(); + SetZN(DB, true); break; } - case 0xB7: // LDA DP Indirect Long Indexed, Y - { - operand = DirectPageIndirectLongIndexedY(); - LDA(operand); + case 0xac: { // ldy abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Ldy(low, high); break; } - case 0xB9: // LDA Absolute Indexed, Y - { - operand = AbsoluteIndexedY(); - LDA(operand); + case 0xad: { // lda abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Lda(low, high); break; } - case 0xBD: // LDA Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - LDA(operand, false, false, true); + case 0xae: { // ldx abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Ldx(low, high); break; } - case 0xBF: // LDA Absolute Long Indexed, X - { - operand = AbsoluteLongIndexedX(); - LDA(operand); + case 0xaf: { // lda abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + Lda(low, high); break; } - - case 0xA2: // LDX Immediate - { - operand = Immediate(); - immediate = true; - LDX(operand, immediate); + case 0xb0: { // bcs rel + DoBranch(GetCarryFlag()); break; } - case 0xA6: // LDX Direct Page - { - operand = DirectPage(); - LDX(operand); + case 0xb1: { // lda idy(r) + uint32_t low = 0; + uint32_t high = AdrIdy(&low, false); + Lda(low, high); break; } - case 0xAE: // LDX Absolute - { - operand = Absolute(); - LDX(operand); + case 0xb2: { // lda idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + Lda(low, high); break; } - case 0xB6: // LDX DP Indexed, Y - { - operand = DirectPageIndexedY(); - LDX(operand); + case 0xb3: { // lda isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + Lda(low, high); break; } - case 0xBE: // LDX Absolute Indexed, Y - { - operand = AbsoluteIndexedY(); - LDX(operand); + case 0xb4: { // ldy dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Ldy(low, high); break; } - - case 0xA0: // LDY Immediate - { - operand = Immediate(); - immediate = true; - LDY(operand, immediate); + case 0xb5: { // lda dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Lda(low, high); break; } - case 0xA4: // LDY Direct Page - { - operand = DirectPage(); - LDY(operand); + case 0xb6: { // ldx dpy + uint32_t low = 0; + uint32_t high = AdrDpy(&low); + Ldx(low, high); break; } - case 0xAC: // LDY Absolute - { - operand = Absolute(); - LDY(operand); + case 0xb7: { // lda ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + Lda(low, high); break; } - case 0xB4: // LDY DP Indexed, X - { - operand = DirectPageIndexedX(); - LDY(operand); + case 0xb8: { // clv imp + AdrImp(); + SetOverflowFlag(false); break; } - case 0xBC: // LDY Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - LDY(operand); + case 0xb9: { // lda aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + Lda(low, high); break; } - - case 0x46: // LSR Direct Page - { - operand = DirectPage(); - LSR(operand); + case 0xba: { // tsx imp + AdrImp(); + if (GetIndexSize()) { + SetSP(X & 0xff); + } else { + SetSP(X); + } + SetZN(X, GetIndexSize()); break; } - case 0x4A: // LSR Accumulator - { - LSR(A, /*accumulator=*/true); + case 0xbb: { // tyx imp + AdrImp(); + if (GetIndexSize()) { + X = Y & 0xff; + } else { + X = Y; + } + SetZN(X, GetIndexSize()); break; } - case 0x4E: // LSR Absolute - { - operand = Absolute(); - LSR(operand); + case 0xbc: { // ldy abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + Ldy(low, high); break; } - case 0x56: // LSR DP Indexed, X - { - operand = DirectPageIndexedX(); - LSR(operand); + case 0xbd: { // lda abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + Lda(low, high); break; } - case 0x5E: // LSR Absolute Indexed, X - { - operand = AbsoluteIndexedX(); - LSR(operand); + case 0xbe: { // ldx aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + Ldx(low, high); break; } - - case 0x54: - // MVN(); + case 0xbf: { // lda alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + Lda(low, high); break; - - case 0xEA: // NOP - NOP(); + } + case 0xc0: { // cpy imm(x) + uint32_t low = 0; + uint32_t high = Immediate(&low, true); + Cpy(low, high); break; - - case 0x01: // ORA DP Indexed Indirect, X - operand = DirectPageIndexedIndirectX(); - ORA(operand); + } + case 0xc1: { // cmp idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + Cmp(low, high); break; - case 0x03: // ORA Stack Relative - operand = StackRelative(); - ORA(operand); + } + case 0xc2: { // rep imm(s) + uint8_t val = ReadOpcode(); + CheckInt(); + SetFlags(status & ~val); + callbacks_.idle(false); break; - case 0x05: // ORA Direct Page - operand = DirectPage(); - ORA(operand); + } + case 0xc3: { // cmp sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + Cmp(low, high); break; - case 0x07: // ORA DP Indirect Long - operand = DirectPageIndirectLong(); - ORA(operand); + } + case 0xc4: { // cpy dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Cpy(low, high); break; - case 0x09: // ORA Immediate - operand = Immediate(); - immediate = true; - ORA(operand, immediate); + } + case 0xc5: { // cmp dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Cmp(low, high); break; - case 0x0D: // ORA Absolute - operand = Absolute(); - ORA(operand); + } + case 0xc6: { // dec dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Dec(low, high); break; - case 0x0F: // ORA Absolute Long - operand = AbsoluteLong(); - ORA(operand); + } + case 0xc7: { // cmp idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + Cmp(low, high); break; - case 0x11: // ORA DP Indirect Indexed, Y - operand = DirectPageIndirectIndexedY(); - ORA(operand); + } + case 0xc8: { // iny imp + AdrImp(); + if (GetIndexSize()) { + Y = (Y + 1) & 0xff; + } else { + Y++; + } + SetZN(Y, GetIndexSize()); break; - case 0x12: // ORA DP Indirect - operand = DirectPageIndirect(); - ORA(operand); + } + case 0xc9: { // cmp imm(m) + uint32_t low = 0; + uint32_t high = Immediate(&low, false); + Cmp(low, high); break; - case 0x13: // ORA SR Indirect Indexed, Y - operand = StackRelativeIndirectIndexedY(); - ORA(operand); + } + case 0xca: { // dex imp + AdrImp(); + if (GetIndexSize()) { + X = (X - 1) & 0xff; + } else { + X--; + } + SetZN(X, GetIndexSize()); break; - case 0x15: // ORA DP Indexed, X - operand = DirectPageIndexedX(); - ORA(operand); + } + case 0xcb: { // wai imp + waiting_ = true; + callbacks_.idle(false); + callbacks_.idle(false); break; - case 0x17: // ORA DP Indirect Long Indexed, Y - operand = DirectPageIndirectLongIndexedY(); - ORA(operand); + } + case 0xcc: { // cpy abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Cpy(low, high); break; - case 0x19: // ORA Absolute Indexed, Y - operand = AbsoluteIndexedY(); - ORA(operand); + } + case 0xcd: { // cmp abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Cmp(low, high); break; - case 0x1D: // ORA Absolute Indexed, X - operand = AbsoluteIndexedX(); - ORA(operand); + } + case 0xce: { // dec abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Dec(low, high); break; - case 0x1F: // ORA Absolute Long Indexed, X - operand = AbsoluteLongIndexedX(); - ORA(operand); + } + case 0xcf: { // cmp abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + Cmp(low, high); break; - - case 0xF4: // PEA Push Effective Absolute address - PEA(); + } + case 0xd0: { // bne rel + DoBranch(!GetZeroFlag()); break; - - case 0xD4: // PEI Push Effective Indirect address - PEI(); + } + case 0xd1: { // cmp idy(r) + uint32_t low = 0; + uint32_t high = AdrIdy(&low, false); + Cmp(low, high); break; - - case 0x62: // PER Push Effective PC Relative Indirect address - PER(); + } + case 0xd2: { // cmp idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + Cmp(low, high); break; - - case 0x48: // PHA Push Accumulator - PHA(); + } + case 0xd3: { // cmp isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + Cmp(low, high); break; - - case 0x8B: // PHB Push Data Bank Register - PHB(); + } + case 0xd4: { // pei dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + PushWord(ReadWord(low, high, false), true); break; - - case 0x0B: // PHD Push Direct Page Register - PHD(); + } + case 0xd5: { // cmp dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Cmp(low, high); break; - - case 0x4B: // PHK Push Program Bank Register - PHK(); + } + case 0xd6: { // dec dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Dec(low, high); break; - - case 0x08: // PHP Push Processor Status Register - PHP(); - break; - - case 0xDA: // PHX Push X register - PHX(); - break; - - case 0x5A: // PHY Push Y register - PHY(); - break; - - case 0x68: // PLA Pull Accumulator - PLA(); - break; - - case 0xAB: // PLB Pull Data Bank Register - PLB(); - break; - - case 0x2B: // PLD Pull Direct Page Register - PLD(); - break; - - case 0x28: // PLP Pull Processor Status Register - PLP(); - break; - - case 0xFA: // PLX Pull X register - PLX(); + } + case 0xd7: { // cmp ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + Cmp(low, high); break; - - case 0x7A: // PLY Pull Y register - PLY(); + } + case 0xd8: { // cld imp + AdrImp(); + SetDecimalFlag(false); break; - - case 0xC2: // REP Reset status bits - operand = FetchByte(); - immediate = true; - REP(); - break; - - case 0x26: // ROL Direct Page - operand = DirectPage(); - ROL(operand); - break; - case 0x2A: // ROL Accumulator - ROL(A, /*accumulator=*/true); - break; - case 0x2E: // ROL Absolute - operand = Absolute(); - ROL(operand); - break; - case 0x36: // ROL DP Indexed, X - operand = DirectPageIndexedX(); - ROL(operand); - break; - case 0x3E: // ROL Absolute Indexed, X - operand = AbsoluteIndexedX(); - ROL(operand); - break; - - case 0x66: // ROR Direct Page - operand = DirectPage(); - ROR(operand); - break; - case 0x6A: // ROR Accumulator - ROR(A, /*accumulator=*/true); - break; - case 0x6E: // ROR Absolute - operand = Absolute(); - ROR(operand); - break; - case 0x76: // ROR DP Indexed, X - operand = DirectPageIndexedX(); - ROR(operand); - break; - case 0x7E: // ROR Absolute Indexed, X - operand = AbsoluteIndexedX(); - ROR(operand); - break; - - case 0x40: // RTI Return from interrupt - RTI(); - break; - - case 0x6B: // RTL Return from subroutine long - RTL(); - break; - - case 0x60: // RTS Return from subroutine - RTS(); - break; - - case 0xE1: // SBC DP Indexed Indirect, X - operand = DirectPageIndexedIndirectX(); - SBC(operand); - break; - case 0xE3: // SBC Stack Relative - operand = StackRelative(); - SBC(operand); - break; - case 0xE5: // SBC Direct Page - operand = DirectPage(); - SBC(operand); - break; - case 0xE7: // SBC DP Indirect Long - operand = DirectPageIndirectLong(); - SBC(operand); - break; - case 0xE9: // SBC Immediate - operand = Immediate(); - immediate = true; - SBC(operand, immediate); - break; - case 0xED: // SBC Absolute - operand = Absolute(); - SBC(operand); - break; - case 0xEF: // SBC Absolute Long - operand = AbsoluteLong(); - SBC(operand); - break; - case 0xF1: // SBC DP Indirect Indexed, Y - operand = DirectPageIndirectIndexedY(); - SBC(operand); - break; - case 0xF2: // SBC DP Indirect - operand = DirectPageIndirect(); - SBC(operand); - break; - case 0xF3: // SBC SR Indirect Indexed, Y - operand = StackRelativeIndirectIndexedY(); - SBC(operand); - break; - case 0xF5: // SBC DP Indexed, X - operand = DirectPageIndexedX(); - SBC(operand); - break; - case 0xF7: // SBC DP Indirect Long Indexed, Y - operand = DirectPageIndirectLongIndexedY(); - SBC(operand); - break; - case 0xF9: // SBC Absolute Indexed, Y - operand = AbsoluteIndexedY(); - SBC(operand); - break; - case 0xFD: // SBC Absolute Indexed, X - operand = AbsoluteIndexedX(); - SBC(operand); - break; - case 0xFF: // SBC Absolute Long Indexed, X - operand = AbsoluteLongIndexedX(); - SBC(operand); - break; - - case 0x38: // SEC Set carry - SEC(); - break; - - case 0xF8: // SED Set decimal - SED(); - break; - - case 0x78: // SEI Set interrupt disable - SEI(); - break; - - case 0xE2: // SEP Set status bits - operand = FetchByte(); - immediate = true; - SEP(); - break; - - case 0x81: // STA DP Indexed Indirect, X - operand = DirectPageIndexedIndirectX(); - STA(operand); - break; - case 0x83: // STA Stack Relative - operand = StackRelative(); - STA(operand); - break; - case 0x85: // STA Direct Page - operand = DirectPage(); - STA(operand); - break; - case 0x87: // STA DP Indirect Long - operand = DirectPageIndirectLong(); - STA(operand); - break; - case 0x8D: // STA Absolute - operand = Absolute(AccessType::Data); - STA(operand); - break; - case 0x8F: // STA Absolute Long - operand = AbsoluteLong(); - STA(operand); - break; - case 0x91: // STA DP Indirect Indexed, Y - operand = DirectPageIndirectIndexedY(); - STA(operand); - break; - case 0x92: // STA DP Indirect - operand = DirectPageIndirect(); - STA(operand); - break; - case 0x93: // STA SR Indirect Indexed, Y - operand = StackRelativeIndirectIndexedY(); - STA(operand); - break; - case 0x95: // STA DP Indexed, X - operand = DirectPageIndexedX(); - STA(operand); - break; - case 0x97: // STA DP Indirect Long Indexed, Y - { - operand = DirectPageIndirectLongIndexedY(); - STA(operand); - break; - } - case 0x99: // STA Absolute Indexed, Y - operand = AbsoluteIndexedY(); - STA(operand); - break; - case 0x9D: // STA Absolute Indexed, X - operand = AbsoluteIndexedX(); - STA(operand); - break; - case 0x9F: // STA Absolute Long Indexed, X - operand = AbsoluteLongIndexedX(); - STA(operand); - break; - - case 0xDB: // STP Stop the processor - STP(); - break; - - case 0x86: // STX Direct Page - operand = DirectPage(); - STX(operand); - break; - case 0x8E: // STX Absolute - operand = Absolute(); - STX(operand); - break; - case 0x96: // STX DP Indexed, Y - operand = DirectPageIndexedY(); - STX(operand); - break; - - case 0x84: // STY Direct Page - operand = DirectPage(); - STY(operand); - break; - case 0x8C: // STY Absolute - operand = Absolute(); - STY(operand); - break; - case 0x94: // STY DP Indexed, X - operand = DirectPageIndexedX(); - STY(operand); - break; - - case 0x64: // STZ Direct Page - operand = DirectPage(); - STZ(operand); + } + case 0xd9: { // cmp aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + Cmp(low, high); break; - case 0x74: // STZ DP Indexed, X - operand = DirectPageIndexedX(); - STZ(operand); + } + case 0xda: { // phx imp + callbacks_.idle(false); + if (GetIndexSize()) { + CheckInt(); + PushByte(X); + } else { + PushWord(X, true); + } break; - case 0x9C: // STZ Absolute - operand = Absolute(); - STZ(operand); + } + case 0xdb: { // stp imp + stopped_ = true; + callbacks_.idle(false); + callbacks_.idle(false); break; - case 0x9E: // STZ Absolute Indexed, X - operand = AbsoluteIndexedX(); - STZ(operand); + } + case 0xdc: { // jml ial + uint16_t adr = ReadOpcodeWord(false); + PC = ReadWord(adr, ((adr + 1) & 0xffff), false); + CheckInt(); + PB = ReadByte((adr + 2) & 0xffff); break; - - case 0xAA: // TAX Transfer accumulator to X - TAX(); + } + case 0xdd: { // cmp abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + Cmp(low, high); break; - - case 0xA8: // TAY Transfer accumulator to Y - TAY(); + } + case 0xde: { // dec abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Dec(low, high); break; - - case 0x5B: // TCD - TCD(); + } + case 0xdf: { // cmp alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + Cmp(low, high); break; - - case 0x1B: // TCS - TCS(); + } + case 0xe0: { // cpx imm(x) + uint32_t low = 0; + uint32_t high = Immediate(&low, true); + Cpx(low, high); break; - - case 0x7B: // TDC - TDC(); + } + case 0xe1: { // sbc idx + uint32_t low = 0; + uint32_t high = AdrIdx(&low); + Sbc(low, high); break; - - case 0x14: // TRB Direct Page - operand = DirectPage(); - TRB(operand); + } + case 0xe2: { // sep imm(s) + uint8_t val = ReadOpcode(); + CheckInt(); + SetFlags(status | val); + callbacks_.idle(false); break; - case 0x1C: // TRB Absolute - operand = Absolute(); - TRB(operand); + } + case 0xe3: { // sbc sr + uint32_t low = 0; + uint32_t high = AdrSr(&low); + Sbc(low, high); break; - - case 0x04: // TSB Direct Page - operand = DirectPage(); - TSB(operand); + } + case 0xe4: { // cpx dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Cpx(low, high); break; - case 0x0C: // TSB Absolute - operand = Absolute(); - TSB(operand); + } + case 0xe5: { // sbc dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Sbc(low, high); break; - - case 0x3B: // TSC - TSC(); - break; - - case 0xBA: // TSX Transfer stack pointer to X - TSX(); - break; - - case 0x8A: // TXA Transfer X to accumulator - TXA(); - break; - - case 0x9A: // TXS Transfer X to stack pointer - TXS(); - break; - - case 0x9B: // TXY Transfer X to Y - TXY(); - break; - - case 0x98: // TYA Transfer Y to accumulator - TYA(); + } + case 0xe6: { // inc dp + uint32_t low = 0; + uint32_t high = AdrDp(&low); + Inc(low, high); break; - - case 0xBB: // TYX Transfer Y to X - TYX(); + } + case 0xe7: { // sbc idl + uint32_t low = 0; + uint32_t high = AdrIdl(&low); + Sbc(low, high); break; - - case 0xCB: // WAI Wait for interrupt - WAI(); + } + case 0xe8: { // inx imp + AdrImp(); + if (GetIndexSize()) { + X = (X + 1) & 0xff; + } else { + X++; + } + SetZN(X, GetIndexSize()); break; - - case 0xEB: // XBA Exchange B and A - XBA(); + } + case 0xe9: { // sbc imm(m) + uint32_t low = 0; + uint32_t high = Immediate(&low, false); + Sbc(low, high); break; - - case 0xFB: // XCE Exchange carry and emulation bits - XCE(); + } + case 0xea: { // nop imp + AdrImp(); + // no operation break; - default: - std::cerr << "Unknown instruction: " << std::hex - << static_cast(opcode) << std::endl; + } + case 0xeb: { // xba imp + uint8_t low = A & 0xff; + uint8_t high = A >> 8; + A = (low << 8) | high; + SetZN(high, true); + callbacks_.idle(false); + CheckInt(); + callbacks_.idle(false); + break; + } + case 0xec: { // cpx abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Cpx(low, high); + break; + } + case 0xed: { // sbc abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Sbc(low, high); + break; + } + case 0xee: { // inc abs + uint32_t low = 0; + uint32_t high = Absolute(&low); + Inc(low, high); + break; + } + case 0xef: { // sbc abl + uint32_t low = 0; + uint32_t high = AdrAbl(&low); + Sbc(low, high); + break; + } + case 0xf0: { // beq rel + DoBranch(GetZeroFlag()); + break; + } + case 0xf1: { // sbc idy(r) + uint32_t low = 0; + uint32_t high = AdrIdy(&low, false); + Sbc(low, high); + break; + } + case 0xf2: { // sbc idp + uint32_t low = 0; + uint32_t high = AdrIdp(&low); + Sbc(low, high); + break; + } + case 0xf3: { // sbc isy + uint32_t low = 0; + uint32_t high = AdrIsy(&low); + Sbc(low, high); + break; + } + case 0xf4: { // pea imm(l) + PushWord(ReadOpcodeWord(false), true); + break; + } + case 0xf5: { // sbc dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Sbc(low, high); break; + } + case 0xf6: { // inc dpx + uint32_t low = 0; + uint32_t high = AdrDpx(&low); + Inc(low, high); + break; + } + case 0xf7: { // sbc ily + uint32_t low = 0; + uint32_t high = AdrIly(&low); + Sbc(low, high); + break; + } + case 0xf8: { // sed imp + AdrImp(); + SetDecimalFlag(true); + break; + } + case 0xf9: { // sbc aby(r) + uint32_t low = 0; + uint32_t high = AdrAby(&low, false); + Sbc(low, high); + break; + } + case 0xfa: { // plx imp + callbacks_.idle(false); + callbacks_.idle(false); + if (GetIndexSize()) { + CheckInt(); + X = PopByte(); + } else { + X = PopWord(true); + } + SetZN(X, GetIndexSize()); + break; + } + case 0xfb: { // xce imp + AdrImp(); + bool temp = GetCarryFlag(); + SetCarryFlag(E); + E = temp; + SetFlags(status); // updates x and m flags, clears upper half of x and y + // if needed + break; + } + case 0xfc: { // jsr iax + uint8_t adrl = ReadOpcode(); + PushWord(PC, false); + uint16_t adr = adrl | (ReadOpcode() << 8); + callbacks_.idle(false); + uint16_t value = ReadWord((PB << 16) | ((adr + X) & 0xffff), + (PB << 16) | ((adr + X + 1) & 0xffff), true); + PC = value; + break; + } + case 0xfd: { // sbc abx(r) + uint32_t low = 0; + uint32_t high = AdrAbx(&low, false); + Sbc(low, high); + break; + } + case 0xfe: { // inc abx + uint32_t low = 0; + uint32_t high = AdrAbx(&low, true); + Inc(low, high); + break; + } + case 0xff: { // sbc alx + uint32_t low = 0; + uint32_t high = AdrAlx(&low); + Sbc(low, high); + break; + } } - - LogInstructions(PC, opcode, operand, immediate, accumulator_mode); - instruction_length = GetInstructionLength(opcode); - UpdatePC(instruction_length); + if (log_instructions_) { + LogInstructions(cache_pc, opcode, operand, immediate, accumulator_mode); + } + // instruction_length = GetInstructionLength(opcode); + // UpdatePC(instruction_length); } void Cpu::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, @@ -1582,20 +1909,18 @@ void Cpu::LogInstructions(uint16_t PC, uint8_t opcode, uint16_t operand, std::cout << std::endl; } } - +/** uint8_t Cpu::GetInstructionLength(uint8_t opcode) { switch (opcode) { case 0x00: // BRK case 0x02: // COP PC = next_pc_; + PB = next_pb_; return 0; - // TODO: Handle JMPs in logging. case 0x20: // JSR Absolute case 0x4C: // JMP Absolute case 0x6C: // JMP Absolute Indirect - case 0x5C: // JMP Absolute Indexed Indirect - case 0x22: // JSL Absolute Long case 0x7C: // JMP Absolute Indexed Indirect case 0xFC: // JSR Absolute Indexed Indirect case 0xDC: // JMP Absolute Indirect Long @@ -1604,6 +1929,12 @@ uint8_t Cpu::GetInstructionLength(uint8_t opcode) { PC = next_pc_; return 0; + case 0x22: // JSL Absolute Long + case 0x5C: // JMP Absolute Indexed Indirect + PC = next_pc_; + PB = next_pb_; + return 0; + case 0x80: // BRA Relative PC += next_pc_; return 2; @@ -1926,71 +2257,7 @@ uint8_t Cpu::GetInstructionLength(uint8_t opcode) { return 1; // Default to 1 as a safe fallback } } - -// TODO: Implement 65816 interrupts. -void Cpu::HandleInterrupts() { - if (GetInterruptFlag()) { - return; - } - - /** - if (GetIRQFlag()) { - if (GetEmulationFlag()) { - PushWord(PC); - PushByte(status); - SetInterruptFlag(true); - SetDecimalFlag(false); - SetIRQFlag(false); - SetEmulationFlag(true); - try { - PC = memory.ReadWord(0xFFFE); - } catch (const std::exception& e) { - std::cout << "IRQ: " << e.what() << std::endl; - } - } else { - PushWord(PC); - PushByte(status); - SetInterruptFlag(true); - SetDecimalFlag(false); - SetIRQFlag(false); - SetEmulationFlag(false); - try { - PC = memory.ReadWord(0xFFFE); - } catch (const std::exception& e) { - std::cout << "IRQ: " << e.what() << std::endl; - } - } - } - - if (GetNMIFlag()) { - if (GetEmulationFlag()) { - PushWord(PC); - PushByte(status); - SetInterruptFlag(true); - SetDecimalFlag(false); - SetNMIFlag(false); - SetEmulationFlag(true); - try { - PC = memory.ReadWord(0xFFFA); - } catch (const std::exception& e) { - std::cout << "NMI: " << e.what() << std::endl; - } - } else { - PushWord(PC); - PushByte(status); - SetInterruptFlag(true); - SetDecimalFlag(false); - SetNMIFlag(false); - SetEmulationFlag(false); - try { - PC = memory.ReadWord(0xFFFA); - } catch (const std::exception& e) { - std::cout << "NMI: " << e.what() << std::endl; - } - } - } - */ -} +*/ } // namespace emu } // namespace app diff --git a/src/app/emu/cpu/cpu.h b/src/app/emu/cpu/cpu.h index d5af069e..a9168a69 100644 --- a/src/app/emu/cpu/cpu.h +++ b/src/app/emu/cpu/cpu.h @@ -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 breakpoints_; std::vector instruction_log_; @@ -90,7 +70,7 @@ class Cpu : public memory::Memory, // 0xFFF8,F9 - ABORT 0xFFE8,E9 - ABORT // 0xFFE6,E7 - BRK // 0xFFF4,F5 - COP 0xFFE4,E5 - COP - void HandleInterrupts(); + void DoInterrupt(); // ====================================================== // Registers @@ -117,18 +97,34 @@ class Cpu : public memory::Memory, // E 6502 emulation mode // B #$10 00010000 Break (emulation mode only) + void SetFlags(uint8_t val) { + status = val; + if (E) { + SetAccumulatorSize(true); + SetIndexSize(true); + SetSP(SP() & 0xFF | 0x100); + } + if (GetIndexSize()) { + X &= 0xff; + Y &= 0xff; + } + } + + void SetZN(uint16_t value, bool byte) { + if (byte) { + SetZeroFlag((value & 0xff) == 0); + SetNegativeFlag(value & 0x80); + } else { + SetZeroFlag(value == 0); + SetNegativeFlag(value & 0x8000); + } + } + // Setting flags in the status register bool m() { return GetAccumulatorSize() ? 1 : 0; } + bool xf() { return GetIndexSize() ? 1 : 0; } int GetAccumulatorSize() const { return status & 0x20; } int GetIndexSize() const { return status & 0x10; } - void set_16_bit_mode() { - SetAccumulatorSize(true); - SetIndexSize(true); - } - void set_8_bit_mode() { - SetAccumulatorSize(false); - SetIndexSize(false); - } void SetAccumulatorSize(bool set) { SetFlag(0x20, set); } void SetIndexSize(bool set) { SetFlag(0x10, set); } @@ -152,7 +148,95 @@ class Cpu : public memory::Memory, enum class AccessType { Control, Data }; - // ========================================================================== + uint8_t ReadOpcode() { return ReadByte((PB << 16) | PC++); } + + uint16_t ReadOpcodeWord(bool int_check = false) { + uint8_t value = ReadOpcode(); + if (int_check) CheckInt(); + return value | (ReadOpcode() << 8); + } + + // Memory access routines + uint8_t ReadByte(uint32_t address) { return callbacks_.read_byte(address); } + uint16_t ReadWord(uint32_t address, uint32_t address_high, + bool int_check = false) { + uint8_t value = ReadByte(address); + if (int_check) CheckInt(); + uint8_t value2 = ReadByte(address_high); + return value | (value2 << 8); + } + uint32_t ReadWordLong(uint32_t address) { + uint8_t value = ReadByte(address); + uint8_t value2 = ReadByte(address + 1); + uint8_t value3 = ReadByte(address + 2); + return value | (value2 << 8) | (value3 << 16); + } + + void WriteByte(uint32_t address, uint8_t value) { + callbacks_.write_byte(address, value); + } + + void WriteWord(uint32_t address, uint32_t address_high, uint16_t value, + bool reversed = false, bool int_check = false) { + if (reversed) { + callbacks_.write_byte(address_high, value >> 8); + if (int_check) CheckInt(); + callbacks_.write_byte(address, value & 0xFF); + } else { + callbacks_.write_byte(address, value & 0xFF); + if (int_check) CheckInt(); + callbacks_.write_byte(address_high, value >> 8); + } + } + void WriteLong(uint32_t address, uint32_t value) { + callbacks_.write_byte(address, value & 0xFF); + callbacks_.write_byte(address + 1, (value >> 8) & 0xFF); + callbacks_.write_byte(address + 2, value >> 16); + } + + void PushByte(uint8_t value) { + callbacks_.write_byte(SP(), value); + SetSP(SP() - 1); + if (E) SetSP((SP() & 0xff) | 0x100); + } + void PushWord(uint16_t value, bool int_check = false) { + PushByte(value >> 8); + if (int_check) CheckInt(); + PushByte(value & 0xFF); + } + void PushLong(uint32_t value) { // Push 24-bit value + PushByte(value >> 16); + PushWord(value & 0xFFFF); + } + + uint8_t PopByte() { + SetSP(SP() + 1); + if (E) SetSP((SP() & 0xff) | 0x100); + return ReadByte(SP()); + } + uint16_t PopWord(bool int_check = false) { + uint8_t low = PopByte(); + if (int_check) CheckInt(); + return low | (PopByte() << 8); + } + uint32_t PopLong() { // Pop 24-bit value + uint32_t low = PopWord(); + uint32_t high = PopByte(); + return (high << 16) | low; + } + + void DoBranch(bool check) { + if (!check) CheckInt(); + uint8_t value = ReadOpcode(); + if (check) { + CheckInt(); + callbacks_.idle(false); // taken branch: 1 extra cycle + PC += (int8_t)value; + } + } + + void set_int_delay(bool delay) { int_delay_ = delay; } + // Addressing Modes // Effective Address: @@ -162,7 +246,7 @@ class Cpu : public memory::Memory, // Low: First operand byte // // LDA addr - uint32_t Absolute(AccessType access_type = AccessType::Data); + uint32_t Absolute(uint32_t* low); // Effective Address: // The Data Bank Register is concatened with the 16-bit operand @@ -171,6 +255,7 @@ class Cpu : public memory::Memory, // // LDA addr, X uint32_t AbsoluteIndexedX(); + uint32_t AdrAbx(uint32_t* low, bool write); // Effective Address: // The Data Bank Register is concatened with the 16-bit operand @@ -179,6 +264,17 @@ class Cpu : public memory::Memory, // // LDA addr, Y uint32_t AbsoluteIndexedY(); + uint32_t AdrAby(uint32_t* low, bool write); + + void AdrImp(); + uint32_t AdrIdx(uint32_t* low); + + uint32_t AdrIdp(uint32_t* low); + uint32_t AdrIdy(uint32_t* low, bool write); + uint32_t AdrIdl(uint32_t* low); + uint32_t AdrIly(uint32_t* low); + uint32_t AdrIsy(uint32_t* low); + uint32_t Immediate(uint32_t* low, bool xFlag); // Effective Address: // Bank: Program Bank Register (PBR) @@ -211,12 +307,14 @@ class Cpu : public memory::Memory, // // LDA long uint32_t AbsoluteLong(); + uint32_t AdrAbl(uint32_t* low); // Effective Address: // The 24-bit operand is added to X based on the emulation mode // // LDA long, X uint32_t AbsoluteLongIndexedX(); + uint32_t AdrAlx(uint32_t* low); // Source Effective Address: // Bank: Second operand byte @@ -238,6 +336,7 @@ class Cpu : public memory::Memory, // // LDA dp uint16_t DirectPage(); + uint32_t AdrDp(uint32_t* low); // Effective Address: // Bank: Zero @@ -246,6 +345,7 @@ class Cpu : public memory::Memory, // // LDA dp, X uint16_t DirectPageIndexedX(); + uint32_t AdrDpx(uint32_t* low); // Effective Address: // Bank: Zero @@ -253,6 +353,7 @@ class Cpu : public memory::Memory, // based on the emulation mode // LDA dp, Y uint16_t DirectPageIndexedY(); + uint32_t AdrDpy(uint32_t* low); // Effective Address: // Bank: Data bank register @@ -310,6 +411,7 @@ class Cpu : public memory::Memory, uint16_t Immediate(bool index_size = false); uint16_t StackRelative(); + uint32_t AdrSr(uint32_t* low); // Effective Address: // The Data Bank Register is concatenated to the Indirect Address; @@ -321,88 +423,6 @@ class Cpu : public memory::Memory, // LDA (sr, S), Y uint32_t StackRelativeIndirectIndexedY(); - // Memory access routines - uint8_t ReadByte(uint32_t address) const override { - return memory.ReadByte(address); - } - uint16_t ReadWord(uint32_t address) const override { - return memory.ReadWord(address); - } - uint32_t ReadWordLong(uint32_t address) const override { - return memory.ReadWordLong(address); - } - - std::vector 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(FetchByte()); } - - int16_t FetchSignedWord() { - auto offset = static_cast(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; }; diff --git a/src/app/emu/cpu/internal/addressing.cc b/src/app/emu/cpu/internal/addressing.cc index cbd76493..7086cfd4 100644 --- a/src/app/emu/cpu/internal/addressing.cc +++ b/src/app/emu/cpu/internal/addressing.cc @@ -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 \ No newline at end of file diff --git a/src/app/emu/cpu/internal/instructions.cc b/src/app/emu/cpu/internal/instructions.cc index 2eafcf63..c00d1f25 100644 --- a/src/app/emu/cpu/internal/instructions.cc +++ b/src/app/emu/cpu/internal/instructions.cc @@ -10,811 +10,393 @@ namespace emu { /** * 65816 Instruction Set - * - * TODO: STP, WDM */ -void Cpu::ADC(uint16_t operand) { - bool C = GetCarryFlag(); - if (GetAccumulatorSize()) { // 8-bit mode - uint16_t result = static_cast(A & 0xFF) + - static_cast(operand) + (C ? 1 : 0); - SetCarryFlag(result > 0xFF); // Update the carry flag - - // Update the overflow flag - bool overflow = (~(A ^ operand) & (A ^ result) & 0x80) != 0; - SetOverflowFlag(overflow); - - // Update the accumulator with proper wrap-around - A = (A & 0xFF00) | (result & 0xFF); - - SetZeroFlag((A & 0xFF) == 0); - SetNegativeFlag(A & 0x80); +void Cpu::And(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t value = ReadByte(low); + A = (A & 0xff00) | ((A & value) & 0xff); } else { - uint32_t result = - static_cast(A) + static_cast(operand) + (C ? 1 : 0); - SetCarryFlag(result > 0xFFFF); // Update the carry flag - - // Update the overflow flag - bool overflow = (~(A ^ operand) & (A ^ result) & 0x8000) != 0; - SetOverflowFlag(overflow); - - // Update the accumulator - A = result & 0xFFFF; - - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); + uint16_t value = ReadWord(low, high, true); + A &= value; } + SetZN(A, GetAccumulatorSize()); } -void Cpu::AND(uint32_t value, bool isImmediate) { - uint16_t operand; - if (GetAccumulatorSize()) { // 8-bit mode - operand = isImmediate ? value : memory.ReadByte(value); - A &= operand; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } else { // 16-bit mode - operand = isImmediate ? value : memory.ReadWord(value); - A &= operand; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); +void Cpu::Eor(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t value = ReadByte(low); + A = (A & 0xff00) | ((A ^ value) & 0xff); + } else { + uint16_t value = ReadWord(low, high, true); + A ^= value; } + SetZN(A, GetAccumulatorSize()); } -// New function for absolute long addressing mode -void Cpu::ANDAbsoluteLong(uint32_t address) { - uint32_t operand32 = memory.ReadWordLong(address); - A &= operand32; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); -} - -void Cpu::ASL(uint16_t address) { - uint8_t value = memory.ReadByte(address); - SetCarryFlag(!(value & 0x80)); // Set carry flag if bit 7 is set - value <<= 1; // Shift left - value &= 0xFE; // Clear bit 0 - memory.WriteByte(address, value); - SetNegativeFlag(!value); - SetZeroFlag(value); -} - -void Cpu::BCC(int8_t offset) { - if (!GetCarryFlag()) { // If the carry flag is clear - next_pc_ = offset; - } -} - -void Cpu::BCS(int8_t offset) { - if (GetCarryFlag()) { // If the carry flag is set - next_pc_ = offset; - } -} - -void Cpu::BEQ(int8_t offset) { - if (GetZeroFlag()) { // If the zero flag is set - next_pc_ = offset; - } -} - -void Cpu::BIT(uint16_t address) { - uint8_t value = memory.ReadByte(address); - SetNegativeFlag(value & 0x80); - SetOverflowFlag(value & 0x40); - SetZeroFlag((A & value) == 0); -} - -void Cpu::BMI(int8_t offset) { - if (GetNegativeFlag()) { // If the negative flag is set - next_pc_ = offset; - } -} - -void Cpu::BNE(int8_t offset) { - if (!GetZeroFlag()) { // If the zero flag is clear - // PC += offset; - next_pc_ = offset; - } -} - -void Cpu::BPL(int8_t offset) { - if (!GetNegativeFlag()) { // If the negative flag is clear - next_pc_ = offset; - } -} - -void Cpu::BRA(int8_t offset) { next_pc_ = offset; } - -void Cpu::BRK() { - next_pc_ = PC + 2; // Increment the program counter by 2 - memory.PushWord(next_pc_); - memory.PushByte(status); - SetInterruptFlag(true); - try { - next_pc_ = memory.ReadWord(0xFFFE); - } catch (const std::exception& e) { - std::cout << "BRK: " << e.what() << std::endl; - } -} - -void Cpu::BRL(int16_t offset) { next_pc_ = offset; } - -void Cpu::BVC(int8_t offset) { - if (!GetOverflowFlag()) { // If the overflow flag is clear - next_pc_ = offset; - } -} - -void Cpu::BVS(int8_t offset) { - if (GetOverflowFlag()) { // If the overflow flag is set - next_pc_ = offset; - } -} - -void Cpu::CLC() { status &= ~0x01; } - -void Cpu::CLD() { status &= ~0x08; } - -void Cpu::CLI() { status &= ~0x04; } - -void Cpu::CLV() { status &= ~0x40; } - -// n Set if MSB of result is set; else cleared -// z Set if result is zero; else cleared -// c Set if no borrow; else cleared -void Cpu::CMP(uint32_t value, bool isImmediate) { - if (GetAccumulatorSize()) { // 8-bit - uint8_t result; - if (isImmediate) { - result = A - (value & 0xFF); +void Cpu::Adc(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t value = ReadByte(low); + int result = 0; + if (GetDecimalFlag()) { + result = (A & 0xf) + (value & 0xf) + GetCarryFlag(); + if (result > 0x9) result = ((result + 0x6) & 0xf) + 0x10; + result = (A & 0xf0) + (value & 0xf0) + result; } else { - uint8_t memory_value = memory.ReadByte(value); - result = A - memory_value; + result = (A & 0xff) + value + GetCarryFlag(); } - SetZeroFlag(result == 0); - SetNegativeFlag(result & 0x80); - SetCarryFlag(A >= (value & 0xFF)); - } else { // 16-bit - uint16_t result; - if (isImmediate) { - result = A - (value & 0xFFFF); + SetOverflowFlag((A & 0x80) == (value & 0x80) && + (value & 0x80) != (result & 0x80)); + if (GetDecimalFlag() && result > 0x9f) result += 0x60; + SetCarryFlag(result > 0xff); + A = (A & 0xff00) | (result & 0xff); + } else { + uint16_t value = ReadWord(low, high, true); + int result = 0; + if (GetDecimalFlag()) { + result = (A & 0xf) + (value & 0xf) + GetCarryFlag(); + if (result > 0x9) result = ((result + 0x6) & 0xf) + 0x10; + result = (A & 0xf0) + (value & 0xf0) + result; + if (result > 0x9f) result = ((result + 0x60) & 0xff) + 0x100; + result = (A & 0xf00) + (value & 0xf00) + result; + if (result > 0x9ff) result = ((result + 0x600) & 0xfff) + 0x1000; + result = (A & 0xf000) + (value & 0xf000) + result; } else { - uint16_t memory_value = memory.ReadWord(value); - result = A - memory_value; + result = A + value + GetCarryFlag(); } + SetOverflowFlag((A & 0x8000) == (value & 0x8000) && + (value & 0x8000) != (result & 0x8000)); + if (GetDecimalFlag() && result > 0x9fff) result += 0x6000; + SetCarryFlag(result > 0xffff); + A = result; + } + SetZN(A, GetAccumulatorSize()); +} + +void Cpu::Sbc(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t value = ReadByte(low) ^ 0xff; + int result = 0; + if (GetDecimalFlag()) { + result = (A & 0xf) + (value & 0xf) + GetCarryFlag(); + if (result < 0x10) + result = (result - 0x6) & ((result - 0x6 < 0) ? 0xf : 0x1f); + result = (A & 0xf0) + (value & 0xf0) + result; + } else { + result = (A & 0xff) + value + GetCarryFlag(); + } + SetOverflowFlag((A & 0x80) == (value & 0x80) && + (value & 0x80) != (result & 0x80)); + if (GetDecimalFlag() && result < 0x100) result -= 0x60; + SetCarryFlag(result > 0xff); + A = (A & 0xff00) | (result & 0xff); + } else { + uint16_t value = ReadWord(low, high, true) ^ 0xffff; + int result = 0; + if (GetDecimalFlag()) { + result = (A & 0xf) + (value & 0xf) + GetCarryFlag(); + if (result < 0x10) + result = (result - 0x6) & ((result - 0x6 < 0) ? 0xf : 0x1f); + result = (A & 0xf0) + (value & 0xf0) + result; + if (result < 0x100) + result = (result - 0x60) & ((result - 0x60 < 0) ? 0xff : 0x1ff); + result = (A & 0xf00) + (value & 0xf00) + result; + if (result < 0x1000) + result = (result - 0x600) & ((result - 0x600 < 0) ? 0xfff : 0x1fff); + result = (A & 0xf000) + (value & 0xf000) + result; + } else { + result = A + value + GetCarryFlag(); + } + SetOverflowFlag((A & 0x8000) == (value & 0x8000) && + (value & 0x8000) != (result & 0x8000)); + if (GetDecimalFlag() && result < 0x10000) result -= 0x6000; + SetCarryFlag(result > 0xffff); + A = result; + } + SetZN(A, GetAccumulatorSize()); +} + +void Cpu::Cmp(uint32_t low, uint32_t high) { + int result = 0; + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t value = ReadByte(low) ^ 0xff; + result = (A & 0xff) + value + 1; + SetCarryFlag(result > 0xff); + } else { + uint16_t value = ReadWord(low, high, true) ^ 0xffff; + result = A + value + 1; + SetCarryFlag(result > 0xffff); + } + SetZN(result, GetAccumulatorSize()); +} + +void Cpu::Cpx(uint32_t low, uint32_t high) { + int result = 0; + if (GetIndexSize()) { + CheckInt(); + uint8_t value = ReadByte(low) ^ 0xff; + result = (X & 0xff) + value + 1; + SetCarryFlag(result > 0xff); + } else { + uint16_t value = ReadWord(low, high, true) ^ 0xffff; + result = X + value + 1; + SetCarryFlag(result > 0xffff); + } + SetZN(result, GetIndexSize()); +} + +void Cpu::Cpy(uint32_t low, uint32_t high) { + int result = 0; + if (GetIndexSize()) { + CheckInt(); + uint8_t value = ReadByte(low) ^ 0xff; + result = (Y & 0xff) + value + 1; + SetCarryFlag(result > 0xff); + } else { + uint16_t value = ReadWord(low, high, true) ^ 0xffff; + result = Y + value + 1; + SetCarryFlag(result > 0xffff); + } + SetZN(result, GetIndexSize()); +} + +void Cpu::Bit(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t value = ReadByte(low); + uint8_t result = (A & 0xff) & value; SetZeroFlag(result == 0); - SetNegativeFlag(result & 0x8000); - SetCarryFlag(A >= (value & 0xFFFF)); - } -} - -void Cpu::COP() { - next_pc_ += 2; // Increment the program counter by 2 - memory.PushWord(next_pc_); - memory.PushByte(status); - SetInterruptFlag(true); - if (E) { - next_pc_ = memory.ReadWord(0xFFF4); - } else { - next_pc_ = memory.ReadWord(0xFFE4); - } - SetDecimalFlag(false); -} - -void Cpu::CPX(uint32_t value, bool isImmediate) { - if (GetIndexSize()) { // 8-bit - uint8_t memory_value = isImmediate ? value : memory.ReadByte(value); - compare(X, memory_value); - } else { // 16-bit - uint16_t memory_value = isImmediate ? value : memory.ReadWord(value); - compare(X, memory_value); - } -} - -void Cpu::CPY(uint32_t value, bool isImmediate) { - if (GetIndexSize()) { // 8-bit - uint8_t memory_value = isImmediate ? value : memory.ReadByte(value); - compare(Y, memory_value); - } else { // 16-bit - uint16_t memory_value = isImmediate ? value : memory.ReadWord(value); - compare(Y, memory_value); - } -} - -void Cpu::DEC(uint32_t address, bool accumulator) { - if (accumulator) { - if (GetAccumulatorSize()) { // 8-bit - A = (A - 1) & 0xFF; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } else { // 16-bit - A = (A - 1) & 0xFFFF; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); - } - return; - } - - if (GetAccumulatorSize()) { - uint8_t value = memory.ReadByte(address); - value--; - memory.WriteByte(address, value); - SetZeroFlag(value == 0); SetNegativeFlag(value & 0x80); + SetOverflowFlag(value & 0x40); } else { - uint16_t value = memory.ReadWord(address); - value--; - memory.WriteWord(address, value); - SetZeroFlag(value == 0); + uint16_t value = ReadWord(low, high, true); + uint16_t result = A & value; + SetZeroFlag(result == 0); SetNegativeFlag(value & 0x8000); + SetOverflowFlag(value & 0x4000); } } -void Cpu::DEX() { - if (GetIndexSize()) { // 8-bit - X = static_cast(X - 1); - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x80); - } else { // 16-bit - X = static_cast(X - 1); - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x8000); - } -} - -void Cpu::DEY() { - if (GetIndexSize()) { // 8-bit - Y = static_cast(Y - 1); - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x80); - } else { // 16-bit - Y = static_cast(Y - 1); - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x8000); - } -} - -void Cpu::EOR(uint32_t address, bool isImmediate) { +void Cpu::Lda(uint32_t low, uint32_t high) { if (GetAccumulatorSize()) { - A ^= isImmediate ? address : memory.ReadByte(address); + CheckInt(); + A = (A & 0xff00) | ReadByte(low); + } else { + A = ReadWord(low, high, true); + } + SetZN(A, GetAccumulatorSize()); +} + +void Cpu::Ldx(uint32_t low, uint32_t high) { + if (GetIndexSize()) { + CheckInt(); + X = ReadByte(low); + } else { + X = ReadWord(low, high, true); + } + SetZN(X, GetIndexSize()); +} + +void Cpu::Ldy(uint32_t low, uint32_t high) { + if (GetIndexSize()) { + CheckInt(); + Y = ReadByte(low); + } else { + Y = ReadWord(low, high, true); + } + SetZN(Y, GetIndexSize()); +} + +void Cpu::Sta(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + WriteByte(low, A); + } else { + WriteWord(low, high, A, false, true); + } +} + +void Cpu::Stx(uint32_t low, uint32_t high) { + if (GetIndexSize()) { + CheckInt(); + WriteByte(low, X); + } else { + WriteWord(low, high, X, false, true); + } +} + +void Cpu::Sty(uint32_t low, uint32_t high) { + if (GetIndexSize()) { + CheckInt(); + WriteByte(low, Y); + } else { + WriteWord(low, high, Y, false, true); + } +} + +void Cpu::Stz(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + WriteByte(low, 0); + } else { + WriteWord(low, high, 0, false, true); + } +} + +void Cpu::Ror(uint32_t low, uint32_t high) { + bool carry = false; + int result = 0; + if (GetAccumulatorSize()) { + uint8_t value = ReadByte(low); + callbacks_.idle(false); + carry = value & 1; + result = (value >> 1) | (GetCarryFlag() << 7); + CheckInt(); + WriteByte(low, result); + } else { + uint16_t value = ReadWord(low, high, false); + callbacks_.idle(false); + carry = value & 1; + result = (value >> 1) | (GetCarryFlag() << 15); + WriteWord(low, high, result, true, true); + } + SetZN(result, GetAccumulatorSize()); + SetCarryFlag(carry); +} + +void Cpu::Rol(uint32_t low, uint32_t high) { + int result = 0; + if (GetAccumulatorSize()) { + result = (ReadByte(low) << 1) | GetCarryFlag(); + callbacks_.idle(false); + SetCarryFlag(result & 0x100); + CheckInt(); + WriteByte(low, result); + } else { + result = (ReadWord(low, high, false) << 1) | GetCarryFlag(); + callbacks_.idle(false); + SetCarryFlag(result & 0x10000); + WriteWord(low, high, result, true, true); + } + SetZN(result, GetAccumulatorSize()); +} + +void Cpu::Lsr(uint32_t low, uint32_t high) { + int result = 0; + if (GetAccumulatorSize()) { + uint8_t value = ReadByte(low); + callbacks_.idle(false); + SetCarryFlag(value & 1); + result = value >> 1; + CheckInt(); + WriteByte(low, result); + } else { + uint16_t value = ReadWord(low, high, false); + callbacks_.idle(false); + SetCarryFlag(value & 1); + result = value >> 1; + WriteWord(low, high, result, true, true); + } + SetZN(result, GetAccumulatorSize()); +} + +void Cpu::Asl(uint32_t low, uint32_t high) { + int result = 0; + if (GetAccumulatorSize()) { + result = ReadByte(low) << 1; + callbacks_.idle(false); + SetCarryFlag(result & 0x100); + CheckInt(); + WriteByte(low, result); + } else { + result = ReadWord(low, high, false) << 1; + callbacks_.idle(false); + SetCarryFlag(result & 0x10000); + WriteWord(low, high, result, true, true); + } + SetZN(result, GetAccumulatorSize()); +} + +void Cpu::Inc(uint32_t low, uint32_t high) { + int result = 0; + if (GetAccumulatorSize()) { + result = ReadByte(low) + 1; + callbacks_.idle(false); + CheckInt(); + WriteByte(low, result); + } else { + result = ReadWord(low, high, false) + 1; + callbacks_.idle(false); + WriteWord(low, high, result, true, true); + } + SetZN(result, GetAccumulatorSize()); +} + +void Cpu::Dec(uint32_t low, uint32_t high) { + int result = 0; + if (GetAccumulatorSize()) { + result = ReadByte(low) - 1; + callbacks_.idle(false); + CheckInt(); + WriteByte(low, result); + } else { + result = ReadWord(low, high, false) - 1; + callbacks_.idle(false); + WriteWord(low, high, result, true, true); + } + SetZN(result, GetAccumulatorSize()); +} + +void Cpu::Tsb(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + uint8_t value = ReadByte(low); + callbacks_.idle(false); + SetZeroFlag(((A & 0xff) & value) == 0); + CheckInt(); + WriteByte(low, value | (A & 0xff)); + } else { + uint16_t value = ReadWord(low, high, false); + callbacks_.idle(false); + SetZeroFlag((A & value) == 0); + WriteWord(low, high, value | A, true, true); + } +} + +void Cpu::Trb(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + uint8_t value = ReadByte(low); + callbacks_.idle(false); + SetZeroFlag(((A & 0xff) & value) == 0); + CheckInt(); + WriteByte(low, value & ~(A & 0xff)); + } else { + uint16_t value = ReadWord(low, high, false); + callbacks_.idle(false); + SetZeroFlag((A & value) == 0); + WriteWord(low, high, value & ~A, true, true); + } +} + +void Cpu::ORA(uint32_t low, uint32_t high) { + if (GetAccumulatorSize()) { + CheckInt(); + uint8_t value = ReadByte(low); + A = (A & 0xFF00) | ((A | value) & 0xFF); SetZeroFlag(A == 0); SetNegativeFlag(A & 0x80); } else { - A ^= isImmediate ? address : memory.ReadWord(address); + uint16_t value = ReadWord(low, high, true); + A |= value; SetZeroFlag(A == 0); SetNegativeFlag(A & 0x8000); } } -void Cpu::INC(uint32_t address, bool accumulator) { - if (accumulator) { - if (GetAccumulatorSize()) { // 8-bit - A = (A + 1) & 0xFF; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } else { // 16-bit - A = (A + 1) & 0xFFFF; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); - } - return; - } - - if (GetAccumulatorSize()) { - uint8_t value = memory.ReadByte(address); - value++; - memory.WriteByte(address, value); - SetNegativeFlag(value & 0x80); - SetZeroFlag(value == 0); - } else { - uint16_t value = memory.ReadWord(address); - value++; - memory.WriteWord(address, value); - SetNegativeFlag(value & 0x8000); - SetZeroFlag(value == 0); - } -} - -void Cpu::INX() { - if (GetIndexSize()) { // 8-bit - X = static_cast(X + 1); - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x80); - } else { // 16-bit - X = static_cast(X + 1); - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x8000); - } -} - -void Cpu::INY() { - if (GetIndexSize()) { // 8-bit - Y = static_cast(Y + 1); - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x80); - } else { // 16-bit - Y = static_cast(Y + 1); - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x8000); - } -} - -void Cpu::JMP(uint16_t address) { - next_pc_ = address; // Set program counter to the new address -} - -void Cpu::JML(uint32_t address) { - next_pc_ = static_cast(address & 0xFFFF); - // Set the PBR to the upper 8 bits of the address - PB = static_cast((address >> 16) & 0xFF); -} - -void Cpu::JSR(uint16_t address) { - memory.PushWord(PC); // Push the program counter onto the stack - next_pc_ = address; // Set program counter to the new address -} - -void Cpu::JSL(uint32_t address) { - memory.PushLong(PC); // Push the program counter onto the stack as a long - // value (24 bits) - next_pc_ = address; // Set program counter to the new address -} - -void Cpu::LDA(uint16_t address, bool isImmediate, bool direct_page, bool data_bank) { - uint8_t bank = PB; - if (direct_page) { - bank = 0; - } - if (GetAccumulatorSize()) { - A = isImmediate ? address : memory.ReadByte((bank << 16) | address); - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } else { - A = isImmediate ? address : memory.ReadWord((bank << 16) | address); - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); - } -} - -void Cpu::LDX(uint16_t address, bool isImmediate) { - if (GetIndexSize()) { - X = isImmediate ? address : memory.ReadByte(address); - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x80); - } else { - X = isImmediate ? address : memory.ReadWord(address); - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x8000); - } -} - -void Cpu::LDY(uint16_t address, bool isImmediate) { - if (GetIndexSize()) { - Y = isImmediate ? address : memory.ReadByte(address); - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x80); - } else { - Y = isImmediate ? address : memory.ReadWord(address); - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x8000); - } -} - -void Cpu::LSR(uint16_t address, bool accumulator) { - if (accumulator) { - if (GetAccumulatorSize()) { // 8-bit - SetCarryFlag(A & 0x01); - A >>= 1; - SetZeroFlag(A == 0); - SetNegativeFlag(false); - } else { // 16-bit - SetCarryFlag(A & 0x0001); - A >>= 1; - SetZeroFlag(A == 0); - SetNegativeFlag(false); - } - return; - } - uint8_t value = memory.ReadByte(address); - SetCarryFlag(value & 0x01); - value >>= 1; - memory.WriteByte(address, value); - SetNegativeFlag(false); - SetZeroFlag(value == 0); -} - -void Cpu::MVN(uint16_t source, uint16_t dest, uint16_t length) { - for (uint16_t i = 0; i < length; i++) { - memory.WriteByte(dest, memory.ReadByte(source)); - source++; - dest++; - } -} - -void Cpu::MVP(uint16_t source, uint16_t dest, uint16_t length) { - for (uint16_t i = 0; i < length; i++) { - memory.WriteByte(dest, memory.ReadByte(source)); - source--; - dest--; - } -} - -void Cpu::NOP() { - // Do nothing -} - -void Cpu::ORA(uint16_t address, bool isImmediate) { - if (GetAccumulatorSize()) { - A |= isImmediate ? address : memory.ReadByte(address); - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } else { - A |= isImmediate ? address : memory.ReadWord(address); - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); - } -} - -void Cpu::PEA() { - uint16_t address = FetchWord(); - memory.PushWord(address); -} - -void Cpu::PEI() { - uint16_t address = FetchWord(); - memory.PushWord(memory.ReadWord(address)); -} - -void Cpu::PER() { - uint16_t address = FetchWord(); - memory.PushWord(PC + address); -} - -void Cpu::PHA() { - if (GetAccumulatorSize()) { - memory.PushByte(static_cast(A)); - } else { - memory.PushWord(A); - } -} - -void Cpu::PHB() { memory.PushByte(DB); } - -void Cpu::PHD() { memory.PushWord(D); } - -void Cpu::PHK() { memory.PushByte(PB); } - -void Cpu::PHP() { memory.PushByte(status); } - -void Cpu::PHX() { - if (GetIndexSize()) { - memory.PushByte(static_cast(X)); - } else { - memory.PushWord(X); - } -} - -void Cpu::PHY() { - if (GetIndexSize()) { - memory.PushByte(static_cast(Y)); - } else { - memory.PushWord(Y); - } -} - -void Cpu::PLA() { - if (GetAccumulatorSize()) { - A = memory.PopByte(); - SetNegativeFlag((A & 0x80) != 0); - } else { - A = memory.PopWord(); - SetNegativeFlag((A & 0x8000) != 0); - } - SetZeroFlag(A == 0); -} - -void Cpu::PLB() { - DB = memory.PopByte(); - SetNegativeFlag((DB & 0x80) != 0); - SetZeroFlag(DB == 0); -} - -// Pull Direct Page Register from Stack -void Cpu::PLD() { - D = memory.PopWord(); - SetNegativeFlag((D & 0x8000) != 0); - SetZeroFlag(D == 0); -} - -// Pull Processor Status Register from Stack -void Cpu::PLP() { status = memory.PopByte(); } - -void Cpu::PLX() { - if (GetIndexSize()) { - X = memory.PopByte(); - SetNegativeFlag((A & 0x80) != 0); - } else { - X = memory.PopWord(); - SetNegativeFlag((A & 0x8000) != 0); - } - - SetZeroFlag(X == 0); -} - -void Cpu::PLY() { - if (GetIndexSize()) { - Y = memory.PopByte(); - SetNegativeFlag((A & 0x80) != 0); - } else { - Y = memory.PopWord(); - SetNegativeFlag((A & 0x8000) != 0); - } - SetZeroFlag(Y == 0); -} - -void Cpu::REP() { - auto byte = FetchByte(); - status &= ~byte; -} - -void Cpu::ROL(uint32_t address, bool accumulator) { - if (accumulator) { - if (GetAccumulatorSize()) { // 8-bit - uint8_t carry = GetCarryFlag() ? 0x01 : 0x00; - SetCarryFlag(A & 0x80); - A <<= 1; - A |= carry; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } else { // 16-bit - uint8_t carry = GetCarryFlag() ? 0x01 : 0x00; - SetCarryFlag(A & 0x8000); - A <<= 1; - A |= carry; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); - } - return; - } - - uint8_t value = memory.ReadByte(address); - uint8_t carry = GetCarryFlag() ? 0x01 : 0x00; - SetCarryFlag(value & 0x80); - value <<= 1; - value |= carry; - memory.WriteByte(address, value); - SetNegativeFlag(value & 0x80); - SetZeroFlag(value == 0); -} - -void Cpu::ROR(uint32_t address, bool accumulator) { - if (accumulator) { - if (GetAccumulatorSize()) { // 8-bit - uint8_t carry = GetCarryFlag() ? 0x80 : 0x00; - SetCarryFlag(A & 0x01); - A >>= 1; - A |= carry; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } else { // 16-bit - uint8_t carry = GetCarryFlag() ? 0x8000 : 0x00; - SetCarryFlag(A & 0x0001); - A >>= 1; - A |= carry; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); - } - return; - } - - uint8_t value = memory.ReadByte(address); - uint8_t carry = GetCarryFlag() ? 0x80 : 0x00; - SetCarryFlag(value & 0x01); - value >>= 1; - value |= carry; - memory.WriteByte(address, value); - SetNegativeFlag(value & 0x80); - SetZeroFlag(value == 0); -} - -void Cpu::RTI() { - status = memory.PopByte(); - PC = memory.PopWord(); -} - -void Cpu::RTL() { - next_pc_ = memory.PopWord(); - PB = memory.PopByte(); -} - -void Cpu::RTS() { - last_call_frame_ = memory.PopWord(); -} - -void Cpu::SBC(uint32_t value, bool isImmediate) { - uint16_t operand; - if (!GetAccumulatorSize()) { // 16-bit mode - operand = isImmediate ? value : memory.ReadWord(value); - uint16_t result = A - operand - (GetCarryFlag() ? 0 : 1); - SetCarryFlag(!(result > 0xFFFF)); // Update the carry flag - - // Update the overflow flag - bool overflow = ((A ^ operand) & (A ^ result) & 0x8000) != 0; - SetOverflowFlag(overflow); - - // Update the accumulator - A = result & 0xFFFF; - - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x8000); - } else { // 8-bit mode - operand = isImmediate ? value : memory.ReadByte(value); - uint8_t result = A - operand - (GetCarryFlag() ? 0 : 1); - SetCarryFlag(!(result > 0xFF)); // Update the carry flag - - // Update the overflow flag - bool overflow = ((A ^ operand) & (A ^ result) & 0x80) != 0; - SetOverflowFlag(overflow); - - // Update the accumulator - A = result & 0xFF; - - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); - } -} - -void Cpu::SEC() { status |= 0x01; } - -void Cpu::SED() { status |= 0x08; } - -void Cpu::SEI() { status |= 0x04; } - -void Cpu::SEP() { - auto byte = FetchByte(); - status |= byte; -} - -void Cpu::STA(uint32_t address) { - if (GetAccumulatorSize()) { - memory.WriteByte(address, static_cast(A)); - } else { - memory.WriteWord(address, A); - } -} - -// TODO: Make this work with the Clock class of the CPU - -void Cpu::STP() { - // During the next phase 2 clock cycle, stop the processors oscillator input - // The processor is effectively shut down until a reset occurs (RES` pin). -} - -void Cpu::STX(uint16_t address) { - if (GetIndexSize()) { - memory.WriteByte(address, static_cast(X)); - } else { - memory.WriteWord(address, X); - } -} - -void Cpu::STY(uint16_t address) { - if (GetIndexSize()) { - memory.WriteByte(address, static_cast(Y)); - } else { - memory.WriteWord(address, Y); - } -} - -void Cpu::STZ(uint16_t address) { - if (GetAccumulatorSize()) { - memory.WriteByte(address, 0x00); - } else { - memory.WriteWord(address, 0x0000); - } -} - -void Cpu::TAX() { - X = A; - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x80); -} - -void Cpu::TAY() { - Y = A; - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x80); -} - -void Cpu::TCD() { - D = A; - SetZeroFlag(D == 0); - SetNegativeFlag(D & 0x80); -} - -void Cpu::TCS() { memory.SetSP(A); } - -void Cpu::TDC() { - A = D; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); -} - -void Cpu::TRB(uint16_t address) { - uint8_t value = memory.ReadByte(address); - SetZeroFlag((A & value) == 0); - value &= ~A; - memory.WriteByte(address, value); -} - -void Cpu::TSB(uint16_t address) { - uint8_t value = memory.ReadByte(address); - SetZeroFlag((A & value) == 0); - value |= A; - memory.WriteByte(address, value); -} - -void Cpu::TSC() { - A = SP(); - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); -} - -void Cpu::TSX() { - X = SP(); - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x80); -} - -void Cpu::TXA() { - A = X; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); -} - -void Cpu::TXS() { memory.SetSP(X); } - -void Cpu::TXY() { - Y = X; - SetZeroFlag(X == 0); - SetNegativeFlag(X & 0x80); -} - -void Cpu::TYA() { - A = Y; - SetZeroFlag(A == 0); - SetNegativeFlag(A & 0x80); -} - -void Cpu::TYX() { - X = Y; - SetZeroFlag(Y == 0); - SetNegativeFlag(Y & 0x80); -} - -// TODO: Make this communicate with the SNES class - -void Cpu::WAI() { - // Pull the RDY pin low - // Power consumption is reduced(?) - // RDY remains low until an external hardware interupt - // (NMI, IRQ, ABORT, or RESET) is received from the SNES class -} - -void Cpu::XBA() { - uint8_t lowByte = A & 0xFF; - uint8_t highByte = (A >> 8) & 0xFF; - A = (lowByte << 8) | highByte; -} - -void Cpu::XCE() { - uint8_t carry = status & 0x01; - status &= ~0x01; - status |= E; - E = carry; -} - } // namespace emu } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/emu/cpu/internal/opcodes.h b/src/app/emu/cpu/internal/opcodes.h index ab112903..349833a0 100644 --- a/src/app/emu/cpu/internal/opcodes.h +++ b/src/app/emu/cpu/internal/opcodes.h @@ -56,6 +56,43 @@ const std::unordered_map 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"}}; -}; \ No newline at end of file +const std::unordered_map 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}}; \ No newline at end of file diff --git a/src/app/emu/debug/asm_parser.h b/src/app/emu/debug/asm_parser.h index 5ea1fe07..f8b107aa 100644 --- a/src/app/emu/debug/asm_parser.h +++ b/src/app/emu/debug/asm_parser.h @@ -8,109 +8,439 @@ #include #include +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" #include "app/emu/cpu/internal/opcodes.h" namespace yaze { namespace app { namespace emu { +enum class AddressingMode { + kAbsolute, + kAbsoluteLong, + kAbsoluteIndexedIndirect, + kAbsoluteIndexedX, + kAbsoluteIndexedY, + kAbsoluteIndirect, + kAbsoluteIndirectLong, + kAbsoluteLongIndexedX, + kAccumulator, + kBlockMove, + kDirectPage, + kDirectPageIndexedX, + kDirectPageIndexedY, + kDirectPageIndirect, + kDirectPageIndirectIndexedY, + kDirectPageIndirectLong, + kDirectPageIndirectLongIndexedY, + kDirectPageIndirectIndexedX, + kDirectPageIndirectLongIndexedX, + kImmediate, + kImplied, + kProgramCounterRelative, + kProgramCounterRelativeLong, + kStackRelative, + kStackRelativeIndirectIndexedY, + kStackRelativeIndirectIndexedYLong, + kStack, + kStackRelativeIndexedY, +}; + +// Key structure for mnemonic and addressing mode +struct MnemonicMode { + std::string mnemonic; + AddressingMode mode; + + bool operator==(const MnemonicMode& other) const { + return mnemonic == other.mnemonic && mode == other.mode; + } +}; + +// Custom hash function for the MnemonicMode structure +struct MnemonicModeHash { + std::size_t operator()(const MnemonicMode& k) const { + return std::hash()(k.mnemonic) ^ + (std::hash()(static_cast(k.mode)) << 1); + } +}; + class AsmParser { public: std::vector Parse(const std::string& instruction) { - std::smatch match; - if (!std::regex_match(instruction, match, instruction_regex_)) { + CreateInternalOpcodeMap(); + auto tokens = Tokenize(instruction); + if (tokens.size() < 1) { throw std::runtime_error("Invalid instruction format: " + instruction); } - std::string mnemonic = match[1]; - std::string addressing_mode = match[2]; - std::string operand = match[3]; + size_t index = 0; + std::vector bytes; + while (index < tokens.size()) { + // For each "line" worth of tokens, we need to extract the + // mnemonic, optional addressing mode qualifier, and operand. + // The operand can come in a variety of formats: + // - Immediate: #$01 + // - Immediate Word: #$1234 + // - Absolute: $1234 + // - Absolute Long: $123456 + // This parser is not exhaustive and only supports a subset of + // the possible addressing modes and operands. + const std::string& mnemonic = tokens[index]; + index++; - std::string lookup_string = mnemonic.substr(0, 3); + // Check if addressing mode qualifier is present + // Either .b, .w, .l, or nothing, which could mean + // it was omitted or the operand is implied + std::string qualifier = ""; + std::string potential_mode = tokens[index]; + if (absl::StrContains(potential_mode, ".")) { + qualifier = potential_mode; + index++; + } - auto opcode_entry = mnemonic_to_opcode_.find(mnemonic); - if (opcode_entry == mnemonic_to_opcode_.end()) { - throw std::runtime_error( - "Unknown mnemonic or addressing mode: " + mnemonic + addressing_mode); + // Now we check for either the immediate mode + // symbol # or the address symbol $ to determine + // the next step + std::string operand = tokens[index]; + if (operand == "#") { + index++; + // Check if the next token is a # character, in which case it is + // a hexadecimal value that needs to be converted to a byte + if (tokens[index] == "#") { + index++; + operand = tokens[index]; + index++; + } + } else if (operand == "$") { + index++; + operand = tokens[index]; + index++; + } + + AddressingMode mode = DetermineMode(tokens); + + MnemonicMode key{mnemonic, mode}; + auto opcode_entry = mnemonic_to_opcode_.find(key); + if (opcode_entry == mnemonic_to_opcode_.end()) { + throw std::runtime_error("Opcode not found for mnemonic and mode: " + + mnemonic); + } + + bytes.push_back(opcode_entry->second); + AppendOperandBytes(bytes, operand, mode); } - std::vector bytes = {opcode_entry->second}; - // AppendOperandBytes(bytes, operand, addressing_mode); - return bytes; } - void CreateInternalOpcodeMap() { - for (const auto& opcode_entry : opcode_to_mnemonic) { - std::string name = opcode_entry.second; - uint8_t opcode = opcode_entry.first; - mnemonic_to_opcode_[name] = opcode; + // Example: ADC.b #$01 + // Returns: ["ADC", ".b", "#", "$", "01"] + std::vector Tokenize(const std::string& instruction) { + std::vector 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& bytes, const std::string& operand, - const std::string& addressing_mode) { - if (addressing_mode == ".b") { - bytes.push_back(static_cast(std::stoi(operand, nullptr, 16))); - } else if (addressing_mode == ".w") { - uint16_t word_operand = - static_cast(std::stoi(operand, nullptr, 16)); - bytes.push_back(static_cast(word_operand & 0xFF)); - bytes.push_back(static_cast((word_operand >> 8) & 0xFF)); - } else if (addressing_mode == ".l") { - uint32_t long_operand = - static_cast(std::stoul(operand, nullptr, 16)); - bytes.push_back(static_cast(long_operand & 0xFF)); - bytes.push_back(static_cast((long_operand >> 8) & 0xFF)); - bytes.push_back(static_cast((long_operand >> 16) & 0xFF)); + const AddressingMode& addressing_mode) { + // Handle different addressing modes + switch (addressing_mode) { + case AddressingMode::kImmediate: { + bytes.push_back(static_cast(std::stoi(operand, nullptr, 16))); + break; + } + case AddressingMode::kAbsolute: { + uint16_t word_operand = + static_cast(std::stoi(operand, nullptr, 16)); + bytes.push_back(static_cast(word_operand & 0xFF)); + bytes.push_back(static_cast((word_operand >> 8) & 0xFF)); + break; + } + case AddressingMode::kAbsoluteLong: { + uint32_t long_operand = + static_cast(std::stoul(operand, nullptr, 16)); + bytes.push_back(static_cast(long_operand & 0xFF)); + bytes.push_back(static_cast((long_operand >> 8) & 0xFF)); + bytes.push_back(static_cast((long_operand >> 16) & 0xFF)); + break; + } + case AddressingMode::kImplied: { + break; + } + default: + // Unknown, append it anyway + bytes.push_back(static_cast(std::stoi(operand, nullptr, 16))); } } - enum class AddressingMode { - kAbsolute, - kAbsoluteLong, - kAbsoluteIndexedIndirect, - kAbsoluteIndexedX, - kAbsoluteIndexedY, - kAbsoluteIndirect, - kAbsoluteIndirectLong, - kAbsoluteLongIndexedX, - kAccumulator, - kBlockMove, - kDirectPage, - kDirectPageIndexedX, - kDirectPageIndexedY, - kDirectPageIndirect, - kDirectPageIndirectIndexedY, - kDirectPageIndirectLong, - kDirectPageIndirectLongIndexedY, - kDirectPageIndirectIndexedX, - kDirectPageIndirectLongIndexedX, - kImmediate, - kImplied, - kProgramCounterRelative, - kProgramCounterRelativeLong, - kStackRelative, - kStackRelativeIndirectIndexedY, - kStackRelativeIndirectIndexedYLong, - kStack, - kStackRelativeIndexedY, - }; - - AddressingMode InferAddressingModeFromOperand(const std::string& operand) { - if (operand[0] == '$') { - return AddressingMode::kAbsolute; - } else if (operand[0] == '#') { + AddressingMode DetermineMode(const std::vector& 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 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 + mnemonic_to_opcode_; }; } // namespace emu diff --git a/src/app/emu/debug/emu.cc b/src/app/emu/debug/emu.cc new file mode 100644 index 00000000..888ca42d --- /dev/null +++ b/src/app/emu/debug/emu.cc @@ -0,0 +1,215 @@ +#if defined(_WIN32) +#define main SDL_main +#elif __APPLE__ +#include "app/core/platform/app_delegate.h" +#endif + +#include +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "imgui/imgui.h" +#include "imgui_memory_editor.h" + +#include +#include +#include + +#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 window_; + std::unique_ptr 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_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_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(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; +} diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index 0d898dd9..4373642b 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -1,15 +1,17 @@ #include "app/emu/emulator.h" -#include -#include +#include "imgui/imgui.h" +#include "imgui_memory_editor.h" #include #include #include "app/core/constants.h" +#include "app/core/platform/file_dialog.h" #include "app/emu/snes.h" #include "app/gui/icons.h" #include "app/gui/input.h" +#include "app/gui/zeml.h" #include "app/rom.h" namespace yaze { @@ -17,11 +19,26 @@ namespace app { namespace emu { namespace { -bool ShouldDisplay(const InstructionEntry& entry, const char* filter, - bool showAll) { - // Implement logic to determine if the entry should be displayed based on the - // filter and showAll flag - return true; +bool ShouldDisplay(const InstructionEntry& entry, const char* filter) { + if (filter[0] == '\0') { + return true; + } + + // Supported fields: address, opcode, operands + if (entry.operands.find(filter) != std::string::npos) { + return true; + } + + if (absl::StrFormat("%06X", entry.address).find(filter) != + std::string::npos) { + return true; + } + + if (opcode_to_mnemonic.at(entry.opcode).find(filter) != std::string::npos) { + return true; + } + + return false; } } // namespace @@ -33,35 +50,105 @@ using ImGui::TableNextColumn; using ImGui::Text; void Emulator::Run() { + static bool loaded = false; if (!snes_.running() && rom()->is_loaded()) { - snes_.SetupMemory(*rom()); - snes_.Init(*rom()); + ppu_texture_ = + SDL_CreateTexture(rom()->renderer().get(), SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, 512, 480); + if (ppu_texture_ == NULL) { + printf("Failed to create texture: %s\n", SDL_GetError()); + return; + } + rom_data_ = rom()->vector(); + snes_.Init(rom_data_); + wanted_frames_ = 1.0 / (snes_.Memory().pal_timing() ? 50.0 : 60.0); + wanted_samples_ = 48000 / (snes_.Memory().pal_timing() ? 50 : 60); + loaded = true; + + count_frequency = SDL_GetPerformanceFrequency(); + last_count = SDL_GetPerformanceCounter(); + time_adder = 0.0; } RenderNavBar(); if (running_) { HandleEvents(); - if (!step_) { - snes_.Run(); + + uint64_t current_count = SDL_GetPerformanceCounter(); + uint64_t delta = current_count - last_count; + last_count = current_count; + float seconds = delta / (float)count_frequency; + time_adder += seconds; + // allow 2 ms earlier, to prevent skipping due to being just below wanted + while (time_adder >= wanted_frames_ - 0.002) { + time_adder -= wanted_frames_; + + if (loaded) { + if (turbo_mode_) { + snes_.RunFrame(); + } + snes_.RunFrame(); + + snes_.SetSamples(audio_buffer_, wanted_samples_); + if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples_ * 4 * 6) { + SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4); + } + + void* ppu_pixels_; + int ppu_pitch_; + if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) != + 0) { + printf("Failed to lock texture: %s\n", SDL_GetError()); + return; + } + snes_.SetPixels(static_cast(ppu_pixels_)); + SDL_UnlockTexture(ppu_texture_); + } } } - RenderEmulator(); + gui::zeml::Render(emulator_node_); +} + +void Emulator::RenderSnesPpu() { + ImVec2 size = ImVec2(512, 480); + if (snes_.running()) { + ImGui::BeginChild("EmulatorOutput", ImVec2(0, 480), true, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + ImGui::SetCursorPosX((ImGui::GetWindowSize().x - size.x) * 0.5f); + ImGui::SetCursorPosY((ImGui::GetWindowSize().y - size.y) * 0.5f); + ImGui::Image((void*)ppu_texture_, size, ImVec2(0, 0), ImVec2(1, 1)); + ImGui::EndChild(); + + } else { + ImGui::Text("Emulator output not available."); + ImGui::BeginChild("EmulatorOutput", ImVec2(0, 480), true, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + ImGui::SetCursorPosX(((ImGui::GetWindowSize().x * 0.5f) - size.x) * 0.5f); + ImGui::SetCursorPosY(((ImGui::GetWindowSize().y * 0.5f) - size.y) * 0.5f); + ImGui::Dummy(size); + ImGui::EndChild(); + } + ImGui::Separator(); } void Emulator::RenderNavBar() { - MENU_BAR() - if (ImGui::BeginMenu("Options")) { - MENU_ITEM("Input") {} - MENU_ITEM("Audio") {} - MENU_ITEM("Video") {} - ImGui::EndMenu(); - } - END_MENU_BAR() + std::string navbar_layout = R"( + BeginMenuBar { + BeginMenu title="Options" { + MenuItem title="Input" {} + MenuItem title="Audio" {} + MenuItem title="Video" {} + } + } + )"; + + static auto navbar_node = gui::zeml::Parse(navbar_layout); + gui::zeml::Render(navbar_node); if (ImGui::Button(ICON_MD_PLAY_ARROW)) { - loading_ = true; + running_ = true; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Start Emulation"); @@ -69,7 +156,7 @@ void Emulator::RenderNavBar() { SameLine(); if (ImGui::Button(ICON_MD_PAUSE)) { - snes_.SetCpuMode(1); + running_ = false; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Pause Emulation"); @@ -78,7 +165,7 @@ void Emulator::RenderNavBar() { if (ImGui::Button(ICON_MD_SKIP_NEXT)) { // Step through Code logic - snes_.StepRun(); + snes_.cpu().RunOpcode(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Step Through Code"); @@ -87,6 +174,7 @@ void Emulator::RenderNavBar() { if (ImGui::Button(ICON_MD_REFRESH)) { // Reset Emulator logic + snes_.Reset(true); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Reset Emulator"); @@ -125,13 +213,22 @@ void Emulator::RenderNavBar() { ImGui::SetTooltip("Settings"); } + static bool open_file = false; SameLine(); if (ImGui::Button(ICON_MD_INFO)) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("About Debugger"); - } + open_file = true; + // About Debugger logic } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("About Debugger"); + } + SameLine(); + ImGui::Checkbox("Logging", snes_.cpu().mutable_log_instructions()); + + SameLine(); + ImGui::Checkbox("Turbo", &turbo_mode_); + static bool show_memory_viewer = false; SameLine(); @@ -147,6 +244,18 @@ void Emulator::RenderNavBar() { RenderMemoryViewer(); ImGui::End(); } + + if (open_file) { + auto file_name = FileDialogWrapper::ShowOpenFileDialog(); + if (!file_name.empty()) { + std::ifstream file(file_name, std::ios::binary); + // Load the data directly into rom_data + rom_data_.assign(std::istreambuf_iterator(file), + std::istreambuf_iterator()); + 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& instructionLog) { - if (ImGui::CollapsingHeader("CPU Instruction Log")) { + const std::vector& 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(); } } diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index 76428138..8255109e 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -1,12 +1,13 @@ #ifndef YAZE_APP_CORE_EMULATOR_H #define YAZE_APP_CORE_EMULATOR_H -#include +#include "imgui/imgui.h" #include #include #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 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& 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 rom_data_; + + gui::zeml::Node emulator_node_; }; } // namespace emu diff --git a/src/app/emu/memory/dma.cc b/src/app/emu/memory/dma.cc index bd5c8b68..734acaae 100644 --- a/src/app/emu/memory/dma.cc +++ b/src/app/emu/memory/dma.cc @@ -6,72 +6,370 @@ namespace yaze { namespace app { namespace emu { namespace memory { +namespace dma { -void DirectMemoryAccess::StartDMATransfer(uint8_t channelMask) { - for (int i = 0; i < 8; ++i) { - if ((channelMask & (1 << i)) != 0) { - Channel& ch = channels[i]; +static const int bAdrOffsets[8][4] = {{0, 0, 0, 0}, {0, 1, 0, 1}, {0, 0, 0, 0}, + {0, 0, 1, 1}, {0, 1, 2, 3}, {0, 1, 0, 1}, + {0, 0, 0, 0}, {0, 0, 1, 1}}; - // Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, DASn) - // ... +static const int transferLength[8] = {1, 2, 2, 4, 4, 4, 2, 4}; - // Determine the transfer direction based on the DMAPn register - bool fromMemory = (ch.DMAPn & 0x80) != 0; +void Reset(MemoryImpl* memory) { + auto channel = memory->dma_channels(); + for (int i = 0; i < 8; i++) { + channel[i].bAdr = 0xff; + channel[i].aAdr = 0xffff; + channel[i].aBank = 0xff; + channel[i].size = 0xffff; + channel[i].indBank = 0xff; + channel[i].tableAdr = 0xffff; + channel[i].repCount = 0xff; + channel[i].unusedByte = 0xff; + channel[i].dmaActive = false; + channel[i].hdmaActive = false; + channel[i].mode = 7; + channel[i].fixed = true; + channel[i].decrement = true; + channel[i].indirect = true; + channel[i].fromB = true; + channel[i].unusedBit = true; + channel[i].doTransfer = false; + channel[i].terminated = false; + } + memory->set_dma_state(0); + memory->set_hdma_init_requested(false); + memory->set_hdma_run_requested(false); +} - // Determine the transfer size based on the DMAPn register - bool transferTwoBytes = (ch.DMAPn & 0x40) != 0; +uint8_t Read(MemoryImpl* memory, uint16_t adr) { + auto channel = memory->dma_channels(); + uint8_t c = (adr & 0x70) >> 4; + switch (adr & 0xf) { + case 0x0: { + uint8_t val = channel[c].mode; + val |= channel[c].fixed << 3; + val |= channel[c].decrement << 4; + val |= channel[c].unusedBit << 5; + val |= channel[c].indirect << 6; + val |= channel[c].fromB << 7; + return val; + } + case 0x1: { + return channel[c].bAdr; + } + case 0x2: { + return channel[c].aAdr & 0xff; + } + case 0x3: { + return channel[c].aAdr >> 8; + } + case 0x4: { + return channel[c].aBank; + } + case 0x5: { + return channel[c].size & 0xff; + } + case 0x6: { + return channel[c].size >> 8; + } + case 0x7: { + return channel[c].indBank; + } + case 0x8: { + return channel[c].tableAdr & 0xff; + } + case 0x9: { + return channel[c].tableAdr >> 8; + } + case 0xa: { + return channel[c].repCount; + } + case 0xb: + case 0xf: { + return channel[c].unusedByte; + } + default: { + return memory->open_bus(); + } + } +} - // Perform the DirectMemoryAccess transfer based on the channel parameters - std::cout << "Starting DirectMemoryAccess transfer for channel " << i - << std::endl; +void Write(MemoryImpl* memory, uint16_t adr, uint8_t val) { + auto channel = memory->dma_channels(); + uint8_t c = (adr & 0x70) >> 4; + switch (adr & 0xf) { + case 0x0: { + channel[c].mode = val & 0x7; + channel[c].fixed = val & 0x8; + channel[c].decrement = val & 0x10; + channel[c].unusedBit = val & 0x20; + channel[c].indirect = val & 0x40; + channel[c].fromB = val & 0x80; + break; + } + case 0x1: { + channel[c].bAdr = val; + break; + } + case 0x2: { + channel[c].aAdr = (channel[c].aAdr & 0xff00) | val; + break; + } + case 0x3: { + channel[c].aAdr = (channel[c].aAdr & 0xff) | (val << 8); + break; + } + case 0x4: { + channel[c].aBank = val; + break; + } + case 0x5: { + channel[c].size = (channel[c].size & 0xff00) | val; + break; + } + case 0x6: { + channel[c].size = (channel[c].size & 0xff) | (val << 8); + break; + } + case 0x7: { + channel[c].indBank = val; + break; + } + case 0x8: { + channel[c].tableAdr = (channel[c].tableAdr & 0xff00) | val; + break; + } + case 0x9: { + channel[c].tableAdr = (channel[c].tableAdr & 0xff) | (val << 8); + break; + } + case 0xa: { + channel[c].repCount = val; + break; + } + case 0xb: + case 0xf: { + channel[c].unusedByte = val; + break; + } + default: { + break; + } + } +} - for (uint16_t j = 0; j < ch.DASn; ++j) { - // Read a byte or two bytes from memory based on the transfer size - // ... +void DoDma(SNES* snes, MemoryImpl* memory, int cpuCycles) { + auto channel = memory->dma_channels(); + snes->cpu().set_int_delay(true); - // Write the data to the B-bus address (BBADn) if transferring from - // memory - // ... + // align to multiple of 8 + snes->SyncCycles(true, 8); - // Update the A1Tn register based on the transfer direction - if (fromMemory) { - ch.A1Tn += transferTwoBytes ? 2 : 1; - } else { - ch.A1Tn -= transferTwoBytes ? 2 : 1; + // full transfer overhead + WaitCycle(snes, memory); + for (int i = 0; i < 8; i++) { + if (!channel[i].dmaActive) continue; + // do channel i + WaitCycle(snes, memory); // overhead per channel + int offIndex = 0; + while (channel[i].dmaActive) { + WaitCycle(snes, memory); + TransferByte(snes, memory, channel[i].aAdr, channel[i].aBank, + channel[i].bAdr + bAdrOffsets[channel[i].mode][offIndex++], + channel[i].fromB); + offIndex &= 3; + if (!channel[i].fixed) { + channel[i].aAdr += channel[i].decrement ? -1 : 1; + } + channel[i].size--; + if (channel[i].size == 0) { + channel[i].dmaActive = false; + } + } + } + + // re-align to cpu cycles + snes->SyncCycles(false, cpuCycles); +} + +void HandleDma(SNES* snes, MemoryImpl* memory, int cpu_cycles) { + // if hdma triggered, do it, except if dmastate indicates dma will be done now + // (it will be done as part of the dma in that case) + if (memory->hdma_init_requested() && memory->dma_state() != 2) + InitHdma(snes, memory, true, cpu_cycles); + if (memory->hdma_run_requested() && memory->dma_state() != 2) + DoHdma(snes, memory, true, cpu_cycles); + if (memory->dma_state() == 1) { + memory->set_dma_state(2); + return; + } + if (memory->dma_state() == 2) { + // do dma + DoDma(snes, memory, cpu_cycles); + memory->set_dma_state(0); + } +} + +void WaitCycle(SNES* snes, MemoryImpl* memory) { + // run hdma if requested, no sync (already sycned due to dma) + if (memory->hdma_init_requested()) InitHdma(snes, memory, false, 0); + if (memory->hdma_run_requested()) DoHdma(snes, memory, false, 0); + + snes->RunCycles(8); +} + +void InitHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cpu_cycles) { + auto channel = memory->dma_channels(); + memory->set_hdma_init_requested(false); + bool hdmaEnabled = false; + // check if a channel is enabled, and do reset + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive) hdmaEnabled = true; + channel[i].doTransfer = false; + channel[i].terminated = false; + } + if (!hdmaEnabled) return; + snes->cpu().set_int_delay(true); + if (do_sync) snes->SyncCycles(true, 8); + + // full transfer overhead + snes->RunCycles(8); + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive) { + // terminate any dma + channel[i].dmaActive = false; + // load address, repCount, and indirect address if needed + snes->RunCycles(8); + channel[i].tableAdr = channel[i].aAdr; + channel[i].repCount = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++); + if (channel[i].repCount == 0) channel[i].terminated = true; + if (channel[i].indirect) { + snes->RunCycles(8); + channel[i].size = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++); + snes->RunCycles(8); + channel[i].size |= + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8; + } + channel[i].doTransfer = true; + } + } + if (do_sync) snes->SyncCycles(false, cpu_cycles); +} + +void DoHdma(SNES* snes, MemoryImpl* memory, bool do_sync, int cycles) { + auto channel = memory->dma_channels(); + memory->set_hdma_run_requested(false); + bool hdmaActive = false; + int lastActive = 0; + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive) { + hdmaActive = true; + if (!channel[i].terminated) lastActive = i; + } + } + + if (!hdmaActive) return; + snes->cpu().set_int_delay(true); + + if (do_sync) snes->SyncCycles(true, 8); + + // full transfer overhead + snes->RunCycles(8); + // do all copies + for (int i = 0; i < 8; i++) { + // terminate any dma + if (channel[i].hdmaActive) channel[i].dmaActive = false; + if (channel[i].hdmaActive && !channel[i].terminated) { + // do the hdma + if (channel[i].doTransfer) { + for (int j = 0; j < transferLength[channel[i].mode]; j++) { + snes->RunCycles(8); + if (channel[i].indirect) { + TransferByte(snes, memory, channel[i].size++, channel[i].indBank, + channel[i].bAdr + bAdrOffsets[channel[i].mode][j], + channel[i].fromB); + } else { + TransferByte(snes, memory, channel[i].tableAdr++, channel[i].aBank, + channel[i].bAdr + bAdrOffsets[channel[i].mode][j], + channel[i].fromB); + } } } - - // Update the channel registers after the transfer (e.g., A1Tn, DASn) - // ... } } - MDMAEN = channelMask; // Set the MDMAEN register to the channel mask -} - -void DirectMemoryAccess::EnableHDMATransfers(uint8_t channelMask) { - for (int i = 0; i < 8; ++i) { - if ((channelMask & (1 << i)) != 0) { - Channel& ch = channels[i]; - - // Validate channel parameters (e.g., DMAPn, BBADn, A1Tn, A2An, NLTRn) - // ... - - // Perform the HDMA setup based on the channel parameters - std::cout << "Enabling HDMA transfer for channel " << i << std::endl; - - // Read the HDMA table from memory starting at A1Tn - // ... - - // Update the A2An register based on the HDMA table - // ... - - // Update the NLTRn register based on the HDMA table - // ... + // do all updates + for (int i = 0; i < 8; i++) { + if (channel[i].hdmaActive && !channel[i].terminated) { + channel[i].repCount--; + channel[i].doTransfer = channel[i].repCount & 0x80; + snes->RunCycles(8); + uint8_t newRepCount = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr); + if ((channel[i].repCount & 0x7f) == 0) { + channel[i].repCount = newRepCount; + channel[i].tableAdr++; + if (channel[i].indirect) { + if (channel[i].repCount == 0 && i == lastActive) { + // if this is the last active channel, only fetch high, and use 0 + // for low + channel[i].size = 0; + } else { + snes->RunCycles(8); + channel[i].size = + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++); + } + snes->RunCycles(8); + channel[i].size |= + snes->Read((channel[i].aBank << 16) | channel[i].tableAdr++) << 8; + } + if (channel[i].repCount == 0) channel[i].terminated = true; + channel[i].doTransfer = true; + } } } - HDMAEN = channelMask; // Set the HDMAEN register to the channel mask + + if (do_sync) snes->SyncCycles(false, cycles); } +void TransferByte(SNES* snes, MemoryImpl* memory, uint16_t aAdr, uint8_t aBank, + uint8_t bAdr, bool fromB) { + // accessing 0x2180 via b-bus while a-bus accesses ram gives open bus + bool validB = + !(bAdr == 0x80 && + (aBank == 0x7e || aBank == 0x7f || + ((aBank < 0x40 || (aBank >= 0x80 && aBank < 0xc0)) && aAdr < 0x2000))); + // accesing b-bus, or dma regs via a-bus gives open bus + bool validA = !((aBank < 0x40 || (aBank >= 0x80 && aBank < 0xc0)) && + (aAdr == 0x420b || aAdr == 0x420c || + (aAdr >= 0x4300 && aAdr < 0x4380) || + (aAdr >= 0x2100 && aAdr < 0x2200))); + if (fromB) { + uint8_t val = validB ? snes->ReadBBus(bAdr) : memory->open_bus(); + if (validA) snes->Write((aBank << 16) | aAdr, val); + } else { + uint8_t val = + validA ? snes->Read((aBank << 16) | aAdr) : memory->open_bus(); + if (validB) snes->WriteBBus(bAdr, val); + } +} + +void StartDma(MemoryImpl* memory, uint8_t val, bool hdma) { + auto channel = memory->dma_channels(); + for (int i = 0; i < 8; i++) { + if (hdma) { + channel[i].hdmaActive = val & (1 << i); + } else { + channel[i].dmaActive = val & (1 << i); + } + } + if (!hdma) { + memory->set_dma_state(val != 0 ? 1 : 0); + } +} + +} // namespace dma } // namespace memory } // namespace emu } // namespace app diff --git a/src/app/emu/memory/dma.h b/src/app/emu/memory/dma.h index e200faaf..4cbd148e 100644 --- a/src/app/emu/memory/dma.h +++ b/src/app/emu/memory/dma.h @@ -3,56 +3,32 @@ #include +#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 diff --git a/src/app/emu/memory/dma_channel.h b/src/app/emu/memory/dma_channel.h new file mode 100644 index 00000000..028b4fc0 --- /dev/null +++ b/src/app/emu/memory/dma_channel.h @@ -0,0 +1,37 @@ +#ifndef YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H +#define YAZE_APP_EMU_MEMORY_DMA_CHANNEL_H + +#include + +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 \ No newline at end of file diff --git a/src/app/emu/memory/memory.cc b/src/app/emu/memory/memory.cc index a92dd7b6..56c1231f 100644 --- a/src/app/emu/memory/memory.cc +++ b/src/app/emu/memory/memory.cc @@ -1,6 +1,6 @@ #include "app/emu/memory/memory.h" -#include +#include "imgui/imgui.h" #include #include @@ -14,6 +14,210 @@ namespace app { namespace emu { namespace memory { +void MemoryImpl::Initialize(const std::vector& romData, bool verbose) { + verbose_ = verbose; + type_ = 1; + + auto location = 0x7FC0; // GetHeaderOffset(); + romSize = 0x400 << romData[location + 0x17]; + sramSize = 0x400 << romData[location + 0x18]; + rom_.resize(romSize); + + // Copy memory into rom_ + for (size_t i = 0; i < romSize; i++) { + rom_[i] = romData[i]; + } + ram_.resize(sramSize); + for (size_t i = 0; i < sramSize; i++) { + ram_[i] = 0; + } + + // Clear memory + memory_.resize(0x1000000); // 16 MB + std::fill(memory_.begin(), memory_.end(), 0); + + // Load ROM data into memory based on LoROM mapping + size_t romSize = romData.size(); + size_t romAddress = 0; + const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB + for (size_t bank = 0x00; bank <= 0x3F; ++bank) { + for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) { + if (romAddress < romSize) { + std::copy(romData.begin() + romAddress, + romData.begin() + romAddress + ROM_CHUNK_SIZE, + memory_.begin() + (bank << 16) + offset); + romAddress += ROM_CHUNK_SIZE; + } + } + } + +} + +memory::RomInfo MemoryImpl::ReadRomHeader() { + memory::RomInfo romInfo; + + uint32_t offset = GetHeaderOffset(); + + // Read cartridge title + char title[22]; + for (int i = 0; i < 21; ++i) { + title[i] = ReadByte(offset + i); + } + title[21] = '\0'; // Null-terminate the string + romInfo.title = std::string(title); + + // Read ROM speed and memory map mode + uint8_t romSpeedAndMapMode = ReadByte(offset + 0x15); + romInfo.romSpeed = (memory::RomSpeed)(romSpeedAndMapMode & 0x07); + romInfo.bankSize = (memory::BankSize)((romSpeedAndMapMode >> 5) & 0x01); + + // Read ROM type + romInfo.romType = (memory::RomType)ReadByte(offset + 0x16); + + // Read ROM size + romInfo.romSize = (memory::RomSize)ReadByte(offset + 0x17); + + // Read RAM size + romInfo.sramSize = (memory::SramSize)ReadByte(offset + 0x18); + + // Read country code + romInfo.countryCode = (memory::CountryCode)ReadByte(offset + 0x19); + + // Read license + romInfo.license = (memory::License)ReadByte(offset + 0x1A); + + // Read ROM version + romInfo.version = ReadByte(offset + 0x1B); + + // Read checksum complement + romInfo.checksumComplement = ReadWord(offset + 0x1E); + + // Read checksum + romInfo.checksum = ReadWord(offset + 0x1C); + + // Read NMI VBL vector + romInfo.nmiVblVector = ReadWord(offset + 0x3E); + + // Read reset vector + romInfo.resetVector = ReadWord(offset + 0x3C); + + return romInfo; +} + +uint8_t MemoryImpl::cart_read(uint8_t bank, uint16_t adr) { + switch (type_) { + case 0: + return open_bus_; + case 1: + return cart_readLorom(bank, adr); + case 2: + return cart_readHirom(bank, adr); + case 3: + return cart_readExHirom(bank, adr); + } + return open_bus_; +} + +void MemoryImpl::cart_write(uint8_t bank, uint16_t adr, uint8_t val) { + switch (type_) { + case 0: + break; + case 1: + cart_writeLorom(bank, adr, val); + break; + case 2: + cart_writeHirom(bank, adr, val); + break; + case 3: + cart_writeHirom(bank, adr, val); + break; + } +} + +uint8_t MemoryImpl::cart_readLorom(uint8_t bank, uint16_t adr) { + if (((bank >= 0x70 && bank < 0x7e) || bank >= 0xf0) && adr < 0x8000 && + sramSize > 0) { + // banks 70-7e and f0-ff, adr 0000-7fff + return ram_[(((bank & 0xf) << 15) | adr) & (sramSize - 1)]; + } + bank &= 0x7f; + if (adr >= 0x8000 || bank >= 0x40) { + // adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff + return rom_[((bank << 15) | (adr & 0x7fff)) & (romSize - 1)]; + } + return open_bus_; +} + +void MemoryImpl::cart_writeLorom(uint8_t bank, uint16_t adr, uint8_t val) { + if (((bank >= 0x70 && bank < 0x7e) || bank > 0xf0) && adr < 0x8000 && + sramSize > 0) { + // banks 70-7e and f0-ff, adr 0000-7fff + ram_[(((bank & 0xf) << 15) | adr) & (sramSize - 1)] = val; + } +} + +uint8_t MemoryImpl::cart_readHirom(uint8_t bank, uint16_t adr) { + bank &= 0x7f; + if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) { + // banks 00-3f and 80-bf, adr 6000-7fff + return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)]; + } + if (adr >= 0x8000 || bank >= 0x40) { + // adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff + return rom_[(((bank & 0x3f) << 16) | adr) & (romSize - 1)]; + } + return open_bus_; +} + +uint8_t MemoryImpl::cart_readExHirom(uint8_t bank, uint16_t adr) { + if ((bank & 0x7f) < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) { + // banks 00-3f and 80-bf, adr 6000-7fff + return ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)]; + } + bool secondHalf = bank < 0x80; + bank &= 0x7f; + if (adr >= 0x8000 || bank >= 0x40) { + // adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff + return rom_[(((bank & 0x3f) << 16) | (secondHalf ? 0x400000 : 0) | adr) & + (romSize - 1)]; + } + return open_bus_; +} + +void MemoryImpl::cart_writeHirom(uint8_t bank, uint16_t adr, uint8_t val) { + bank &= 0x7f; + if (bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && sramSize > 0) { + // banks 00-3f and 80-bf, adr 6000-7fff + ram_[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (sramSize - 1)] = val; + } +} + +uint32_t MemoryImpl::GetMappedAddress(uint32_t address) const { + uint8_t bank = address >> 16; + uint32_t offset = address & 0xFFFF; + + if (bank <= 0x3F) { + if (address <= 0x1FFF) { + return (0x7E << 16) + offset; // Shadow RAM + } else if (address <= 0x5FFF) { + return (bank << 16) + (offset - 0x2000) + 0x2000; // Hardware Registers + } else if (address <= 0x7FFF) { + return offset - 0x6000 + 0x6000; // Expansion RAM + } else { + // Return lorom mapping + return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM + } + } else if (bank == 0x7D) { + return offset + 0x7D0000; // SRAM + } else if (bank == 0x7E || bank == 0x7F) { + return offset + 0x7E0000; // System RAM + } else if (bank >= 0x80) { + // Handle HiROM and mirrored areas + } + + return address; // Return the original address if no mapping is defined +} + void DrawSnesMemoryMapping(const MemoryImpl& memory) { // Using those as a base value to create width/height that are factor of the // size of our font diff --git a/src/app/emu/memory/memory.h b/src/app/emu/memory/memory.h index 9a2c79f3..8564e8c8 100644 --- a/src/app/emu/memory/memory.h +++ b/src/app/emu/memory/memory.h @@ -2,11 +2,13 @@ #define MEM_H #include +#include #include #include #include #include "app/emu/debug/log.h" +#include "app/emu/memory/dma_channel.h" // LoROM (Mode 20): @@ -91,20 +93,16 @@ class RomInfo { uint16_t resetVector; }; -class Observer { - public: - virtual ~Observer() = default; - virtual void Notify(uint32_t address, uint8_t data) = 0; -}; +typedef struct CpuCallbacks { + std::function read_byte; + std::function write_byte; + std::function idle; +} CpuCallbacks; constexpr uint32_t kROMStart = 0x008000; constexpr uint32_t kROMSize = 0x200000; constexpr uint32_t kRAMStart = 0x7E0000; constexpr uint32_t kRAMSize = 0x20000; -constexpr uint32_t kVRAMStart = 0x210000; -constexpr uint32_t kVRAMSize = 0x10000; -constexpr uint32_t kOAMStart = 0x218000; -constexpr uint32_t kOAMSize = 0x220; /** * @brief Memory interface @@ -136,139 +134,86 @@ class Memory { virtual uint8_t operator[](int i) const = 0; virtual uint8_t at(int i) const = 0; -}; -enum class MemoryMapping { SNES_LOROM = 0, PC_ADDRESS = 1 }; + virtual uint8_t open_bus() const = 0; + virtual void set_open_bus(uint8_t value) = 0; + + virtual bool hdma_init_requested() const = 0; + virtual bool hdma_run_requested() const = 0; + virtual void init_hdma_request() = 0; + virtual void run_hdma_request() = 0; + virtual void set_hdma_run_requested(bool value) = 0; + virtual void set_hdma_init_requested(bool value) = 0; + virtual void set_pal_timing(bool value) = 0; + virtual void set_h_pos(uint16_t value) = 0; + virtual void set_v_pos(uint16_t value) = 0; + + // get h_pos and v_pos + virtual auto h_pos() const -> uint16_t = 0; + virtual auto v_pos() const -> uint16_t = 0; + // get pal timing + virtual auto pal_timing() const -> bool = 0; +}; /** * @class MemoryImpl * @brief Implementation of the Memory interface for emulating memory in a SNES * system. * - * The MemoryImpl class provides methods for initializing and accessing memory - * in a SNES system. It implements the Memory interface and inherits from the - * Loggable class. - * - * The class supports different memory mappings, including LoROM and PC_ADDRESS - * mappings. It provides methods for reading and writing bytes, words, and longs - * from/to memory. It also supports stack operations for pushing and popping - * values. - * - * The class maintains separate vectors for ROM, RAM, VRAM, and OAM memory - * regions. It provides methods for accessing these memory regions and - * retrieving their sizes. - * - * The class also allows adding observers to be notified when memory is read or - * written. - * - * @note This class assumes a 16-bit address space. */ class MemoryImpl : public Memory, public Loggable { public: - void Initialize(const std::vector& romData, bool verbose = false, - MemoryMapping mapping = MemoryMapping::SNES_LOROM) { - verbose_ = verbose; - mapping_ = mapping; - if (mapping == MemoryMapping::PC_ADDRESS) { - memory_.resize(romData.size()); - std::copy(romData.begin(), romData.end(), memory_.begin()); - return; + uint32_t romSize; + uint32_t sramSize; + void Initialize(const std::vector& romData, bool verbose = false); + + uint16_t GetHeaderOffset() { + uint8_t mapMode = memory_[(0x00 << 16) + 0xFFD5]; + uint16_t offset; + + switch (mapMode & 0x07) { + case 0: // LoROM + offset = 0x7FC0; + break; + case 1: // HiROM + offset = 0xFFC0; + break; + case 5: // ExHiROM + offset = 0x40; + break; + default: + throw std::invalid_argument( + "Unable to locate supported ROM mapping mode in the provided ROM " + "file. Please try another ROM file."); } - memory_.resize(0x1000000); // 16 MB - - const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB - const size_t SRAM_SIZE = 0x10000; // 64 KB - const size_t SYSTEM_RAM_SIZE = 0x20000; // 128 KB - const size_t EXPANSION_RAM_SIZE = 0x2000; // 8 KB - const size_t HARDWARE_REGISTERS_SIZE = 0x4000; // 16 KB - - // Clear memory - std::fill(memory_.begin(), memory_.end(), 0); - - // Load ROM data into memory based on LoROM mapping - size_t romSize = romData.size(); - size_t romAddress = 0; - for (size_t bank = 0x00; bank <= 0x3F; ++bank) { - for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) { - if (romAddress < romSize) { - std::copy(romData.begin() + romAddress, - romData.begin() + romAddress + ROM_CHUNK_SIZE, - memory_.begin() + (bank << 16) + offset); - romAddress += ROM_CHUNK_SIZE; - } - } - } - - // Initialize SRAM at banks 0x7D and 0xFD - std::fill(memory_.begin() + (0x7D << 16), memory_.begin() + (0x7E << 16), - 0); - std::fill(memory_.begin() + (0xFD << 16), memory_.begin() + (0xFE << 16), - 0); - - // Initialize System RAM at banks 0x7E and 0x7F - std::fill(memory_.begin() + (0x7E << 16), - memory_.begin() + (0x7E << 16) + SYSTEM_RAM_SIZE, 0); - - // Initialize Shadow RAM at banks 0x00-0x3F and 0x80-0xBF - for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) { - std::fill(memory_.begin() + (bank << 16), - memory_.begin() + (bank << 16) + 0x2000, 0); - } - - // Initialize Hardware Registers at banks 0x00-0x3F and 0x80-0xBF - for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) { - std::fill( - memory_.begin() + (bank << 16) + 0x2000, - memory_.begin() + (bank << 16) + 0x2000 + HARDWARE_REGISTERS_SIZE, 0); - } - - // Initialize Expansion RAM at banks 0x00-0x3F and 0x80-0xBF - for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) { - std::fill(memory_.begin() + (bank << 16) + 0x6000, - memory_.begin() + (bank << 16) + 0x6000 + EXPANSION_RAM_SIZE, - 0); - } - - // Initialize Reset and NMI Vectors at bank 0xFF - std::fill(memory_.begin() + (0xFF << 16) + 0xFF00, - memory_.begin() + (0xFF << 16) + 0xFFFF + 1, 0); - - // Copy data into rom_ vector - rom_.resize(kROMSize); - std::copy(memory_.begin() + kROMStart, - memory_.begin() + kROMStart + kROMSize, rom_.begin()); - - // Copy data into ram_ vector - ram_.resize(kRAMSize); - std::copy(memory_.begin() + kRAMStart, - memory_.begin() + kRAMStart + kRAMSize, ram_.begin()); - - // Copy data into vram_ vector - vram_.resize(kVRAMSize); - std::copy(memory_.begin() + kVRAMStart, - memory_.begin() + kVRAMStart + kVRAMSize, vram_.begin()); - - // Copy data into oam_ vector - oam_.resize(kOAMSize); - std::copy(memory_.begin() + kOAMStart, - memory_.begin() + kOAMStart + kOAMSize, oam_.begin()); + return offset; } + memory::RomInfo ReadRomHeader(); + + uint8_t cart_read(uint8_t bank, uint16_t adr); + void cart_write(uint8_t bank, uint16_t adr, uint8_t val); + + uint8_t cart_readLorom(uint8_t bank, uint16_t adr); + void cart_writeLorom(uint8_t bank, uint16_t adr, uint8_t val); + + uint8_t cart_readHirom(uint8_t bank, uint16_t adr); + uint8_t cart_readExHirom(uint8_t bank, uint16_t adr); + + void cart_writeHirom(uint8_t bank, uint16_t adr, uint8_t val); + uint8_t ReadByte(uint32_t address) const override { uint32_t mapped_address = GetMappedAddress(address); - NotifyObservers(mapped_address, /*data=*/0); return memory_.at(mapped_address); } uint16_t ReadWord(uint32_t address) const override { uint32_t mapped_address = GetMappedAddress(address); - NotifyObservers(mapped_address, /*data=*/0); return static_cast(memory_.at(mapped_address)) | (static_cast(memory_.at(mapped_address + 1)) << 8); } uint32_t ReadWordLong(uint32_t address) const override { uint32_t mapped_address = GetMappedAddress(address); - NotifyObservers(mapped_address, /*data=*/0); return static_cast(memory_.at(mapped_address)) | (static_cast(memory_.at(mapped_address + 1)) << 8) | (static_cast(memory_.at(mapped_address + 2)) << 16); @@ -276,7 +221,6 @@ class MemoryImpl : public Memory, public Loggable { std::vector ReadByteVector(uint32_t address, uint16_t length) const override { uint32_t mapped_address = GetMappedAddress(address); - NotifyObservers(mapped_address, /*data=*/0); return std::vector(memory_.begin() + mapped_address, memory_.begin() + mapped_address + length); } @@ -343,10 +287,9 @@ class MemoryImpl : public Memory, public Loggable { (static_cast(mid) << 8) | low; } - void AddObserver(Observer* observer) { observers_.push_back(observer); } - // Stack Pointer access. uint16_t SP() const override { return SP_; } + auto mutable_sp() -> uint16_t& { return SP_; } void SetSP(uint16_t value) override { SP_ = value; } void ClearMemory() override { std::fill(memory_.begin(), memory_.end(), 0); } @@ -363,61 +306,69 @@ class MemoryImpl : public Memory, public Loggable { auto begin() const { return memory_.begin(); } auto end() const { return memory_.end(); } auto data() const { return memory_.data(); } + void set_open_bus(uint8_t value) override { open_bus_ = value; } + auto open_bus() const -> uint8_t override { return open_bus_; } + auto hdma_init_requested() const -> bool override { + return hdma_init_requested_; + } + auto hdma_run_requested() const -> bool override { + return hdma_run_requested_; + } + void init_hdma_request() override { hdma_init_requested_ = true; } + void run_hdma_request() override { hdma_run_requested_ = true; } + void set_hdma_run_requested(bool value) override { + hdma_run_requested_ = value; + } + void set_hdma_init_requested(bool value) override { + hdma_init_requested_ = value; + } + void set_pal_timing(bool value) override { pal_timing_ = value; } + void set_h_pos(uint16_t value) override { h_pos_ = value; } + void set_v_pos(uint16_t value) override { v_pos_ = value; } + auto h_pos() const -> uint16_t override { return h_pos_; } + auto v_pos() const -> uint16_t override { return v_pos_; } + auto pal_timing() const -> bool override { return pal_timing_; } + + auto dma_state() -> uint8_t& { return dma_state_; } + void set_dma_state(uint8_t value) { dma_state_ = value; } + auto dma_channels() -> DmaChannel* { return channel; } // Define memory regions std::vector rom_; std::vector ram_; - std::vector vram_; - std::vector oam_; private: - uint32_t GetMappedAddress(uint32_t address) const { - uint8_t bank = address >> 16; - uint32_t offset = address & 0xFFFF; - - if (mapping_ == MemoryMapping::PC_ADDRESS) { - return address; - } - - if (bank <= 0x3F) { - if (address <= 0x1FFF) { - return (0x7E << 16) + offset; // Shadow RAM - } else if (address <= 0x5FFF) { - return (bank << 16) + (offset - 0x2000) + 0x2000; // Hardware Registers - } else if (address <= 0x7FFF) { - return offset - 0x6000 + 0x6000; // Expansion RAM - } else { - // Return lorom mapping - return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM - } - } else if (bank == 0x7D) { - return offset + 0x7D0000; // SRAM - } else if (bank == 0x7E || bank == 0x7F) { - return offset + 0x7E0000; // System RAM - } else if (bank >= 0x80) { - // Handle HiROM and mirrored areas - } - - return address; // Return the original address if no mapping is defined - } - - void NotifyObservers(uint32_t address, uint8_t data) const { - for (auto observer : observers_) { - observer->Notify(address, data); - } - } + uint32_t GetMappedAddress(uint32_t address) const; bool verbose_ = false; - std::vector observers_; + // DMA requests + bool hdma_run_requested_ = false; + bool hdma_init_requested_ = false; + + bool pal_timing_ = false; + + // Frame timing + uint16_t h_pos_ = 0; + uint16_t v_pos_ = 0; + + // Dma State + uint8_t dma_state_ = 0; + + // Dma Channels + DmaChannel channel[8]; + + // Open bus + uint8_t open_bus_ = 0; + + // Stack Pointer + uint16_t SP_ = 0; + + // Cart Type + uint8_t type_ = 1; // Memory (64KB) std::vector memory_; - - // Stack Pointer - uint16_t SP_ = 0x01FF; - - MemoryMapping mapping_ = MemoryMapping::SNES_LOROM; }; void DrawSnesMemoryMapping(const MemoryImpl& memory); diff --git a/src/app/emu/memory/mock_memory.h b/src/app/emu/memory/mock_memory.h index 6a3f68f6..44e8ac5e 100644 --- a/src/app/emu/memory/mock_memory.h +++ b/src/app/emu/memory/mock_memory.h @@ -72,6 +72,21 @@ class MockMemory : public Memory { MOCK_CONST_METHOD1(at, uint8_t(int i)); uint8_t operator[](int i) const override { return memory_[i]; } + MOCK_METHOD0(init_hdma_request, void()); + MOCK_METHOD0(run_hdma_request, void()); + MOCK_METHOD1(set_hdma_run_requested, void(bool value)); + MOCK_METHOD1(set_hdma_init_requested, void(bool value)); + MOCK_CONST_METHOD0(hdma_init_requested, bool()); + MOCK_CONST_METHOD0(hdma_run_requested, bool()); + MOCK_METHOD1(set_pal_timing, void(bool value)); + MOCK_CONST_METHOD0(pal_timing, bool()); + MOCK_CONST_METHOD0(h_pos, uint16_t()); + MOCK_CONST_METHOD0(v_pos, uint16_t()); + MOCK_METHOD1(set_h_pos, void(uint16_t value)); + MOCK_METHOD1(set_v_pos, void(uint16_t value)); + MOCK_METHOD1(set_open_bus, void(uint8_t value)); + MOCK_CONST_METHOD0(open_bus, uint8_t()); + void SetMemoryContents(const std::vector& data) { if (data.size() > memory_.size()) { memory_.resize(data.size()); diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index 2ac655f7..8797e9ce 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -1,7 +1,5 @@ #include "app/emu/snes.h" -#include - #include #include #include @@ -12,6 +10,7 @@ #include "app/emu/cpu/clock.h" #include "app/emu/cpu/cpu.h" #include "app/emu/debug/debugger.h" +#include "app/emu/memory/dma.h" #include "app/emu/memory/memory.h" #include "app/emu/video/ppu.h" #include "app/rom.h" @@ -21,354 +20,558 @@ namespace app { namespace emu { namespace { - -uint16_t GetHeaderOffset(const Memory& memory) { - uint8_t mapMode = memory[(0x00 << 16) + 0xFFD5]; - uint16_t offset; - - switch (mapMode & 0x07) { - case 0: // LoROM - offset = 0x7FC0; - break; - case 1: // HiROM - offset = 0xFFC0; - break; - case 5: // ExHiROM - offset = 0x40; - break; - default: - throw std::invalid_argument( - "Unable to locate supported ROM mapping mode in the provided ROM " - "file. Please try another ROM file."); - } - - return offset; +void input_latch(Input* input, bool value) { + input->latch_line_ = value; + if (input->latch_line_) input->latched_state_ = input->current_state_; } -void audio_callback(void* userdata, uint8_t* stream, int len) { - auto* apu = static_cast(userdata); - auto* buffer = reinterpret_cast(stream); - - for (int i = 0; i < len / 2; i++) { // Assuming 16-bit samples - buffer[i] = apu->GetNextSample(); // This function should be implemented in - // APU to fetch the next sample - } +uint8_t input_read(Input* input) { + if (input->latch_line_) input->latched_state_ = input->current_state_; + uint8_t ret = input->latched_state_ & 1; + input->latched_state_ >>= 1; + input->latched_state_ |= 0x8000; + return ret; } - } // namespace -RomInfo SNES::ReadRomHeader(uint32_t offset) { - RomInfo romInfo; - - // Read cartridge title - char title[22]; - for (int i = 0; i < 21; ++i) { - title[i] = cpu_.ReadByte(offset + i); - } - title[21] = '\0'; // Null-terminate the string - romInfo.title = std::string(title); - - // Read ROM speed and memory map mode - uint8_t romSpeedAndMapMode = cpu_.ReadByte(offset + 0x15); - romInfo.romSpeed = (RomSpeed)(romSpeedAndMapMode & 0x07); - romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01); - - // Read ROM type - romInfo.romType = (RomType)cpu_.ReadByte(offset + 0x16); - - // Read ROM size - romInfo.romSize = (RomSize)cpu_.ReadByte(offset + 0x17); - - // Read RAM size - romInfo.sramSize = (SramSize)cpu_.ReadByte(offset + 0x18); - - // Read country code - romInfo.countryCode = (CountryCode)cpu_.ReadByte(offset + 0x19); - - // Read license - romInfo.license = (License)cpu_.ReadByte(offset + 0x1A); - - // Read ROM version - romInfo.version = cpu_.ReadByte(offset + 0x1B); - - // Read checksum complement - romInfo.checksumComplement = cpu_.ReadWord(offset + 0x1E); - - // Read checksum - romInfo.checksum = cpu_.ReadWord(offset + 0x1C); - - // Read NMI VBL vector - romInfo.nmiVblVector = cpu_.ReadWord(offset + 0x3E); - - // Read reset vector - romInfo.resetVector = cpu_.ReadWord(offset + 0x3C); - - return romInfo; -} - -void SNES::Init(Rom& rom) { - // Perform a long jump into a FastROM bank (if the ROM speed is FastROM) - // Disable the emulation flag (switch to 65816 native mode) - cpu_.E = 0; - - // Initialize CPU - cpu_.Init(); - - // Read the ROM header - auto header_offset = GetHeaderOffset(memory_); - rom_info_ = ReadRomHeader((0x00 << 16) + header_offset); - cpu_.PB = 0x00; - cpu_.PC = 0x8000; - - // Initialize PPU +void SNES::Init(std::vector& rom_data) { + // Initialize the CPU, PPU, and APU ppu_.Init(); - - // Initialize APU apu_.Init(); - // Initialize SDL_Mixer to play the audio samples - // Mix_HookMusic(audio_callback, &apu); - - // Disable interrupts and rendering - memory_.WriteByte(0x4200, 0x00); // NMITIMEN - memory_.WriteByte(0x420C, 0x00); // HDMAEN - - // Disable screen - memory_.WriteByte(0x2100, 0x8F); // INIDISP - - // Fill Work-RAM with zeros using two 64KiB fixed address DMA transfers to - // WMDATA - // TODO: Make this load from work ram, potentially in Memory class - std::memset((void*)memory_.ram_.data(), 0, sizeof(memory_.ram_)); - - // Reset PPU registers to a known good state - memory_.WriteByte(0x4201, 0xFF); // WRIO - - // Objects - memory_.WriteByte(0x2101, 0x00); // OBSEL - memory_.WriteByte(0x2102, 0x00); // OAMADDL - memory_.WriteByte(0x2103, 0x00); // OAMADDH - - // Backgrounds - memory_.WriteByte(0x2105, 0x00); // BGMODE - memory_.WriteByte(0x2106, 0x00); // MOSAIC - - memory_.WriteByte(0x2107, 0x00); // BG1SC - memory_.WriteByte(0x2108, 0x00); // BG2SC - memory_.WriteByte(0x2109, 0x00); // BG3SC - memory_.WriteByte(0x210A, 0x00); // BG4SC - - memory_.WriteByte(0x210B, 0x00); // BG12NBA - memory_.WriteByte(0x210C, 0x00); // BG34NBA - - // Scroll Registers - memory_.WriteByte(0x210D, 0x00); // BG1HOFS - memory_.WriteByte(0x210E, 0xFF); // BG1VOFS - - memory_.WriteByte(0x210F, 0x00); // BG2HOFS - memory_.WriteByte(0x2110, 0xFF); // BG2VOFS - - memory_.WriteByte(0x2111, 0x00); // BG3HOFS - memory_.WriteByte(0x2112, 0xFF); // BG3VOFS - - memory_.WriteByte(0x2113, 0x00); // BG4HOFS - memory_.WriteByte(0x2114, 0xFF); // BG4VOFS - - // VRAM Registers - memory_.WriteByte(0x2115, 0x80); // VMAIN - - // Mode 7 - memory_.WriteByte(0x211A, 0x00); // M7SEL - memory_.WriteByte(0x211B, 0x01); // M7A - memory_.WriteByte(0x211C, 0x00); // M7B - memory_.WriteByte(0x211D, 0x00); // M7C - memory_.WriteByte(0x211E, 0x01); // M7D - memory_.WriteByte(0x211F, 0x00); // M7X - memory_.WriteByte(0x2120, 0x00); // M7Y - - // Windows - memory_.WriteByte(0x2123, 0x00); // W12SEL - memory_.WriteByte(0x2124, 0x00); // W34SEL - memory_.WriteByte(0x2125, 0x00); // WOBJSEL - memory_.WriteByte(0x2126, 0x00); // WH0 - memory_.WriteByte(0x2127, 0x00); // WH1 - memory_.WriteByte(0x2128, 0x00); // WH2 - memory_.WriteByte(0x2129, 0x00); // WH3 - memory_.WriteByte(0x212A, 0x00); // WBGLOG - memory_.WriteByte(0x212B, 0x00); // WOBJLOG - - // Layer Enable - memory_.WriteByte(0x212C, 0x00); // TM - memory_.WriteByte(0x212D, 0x00); // TS - memory_.WriteByte(0x212E, 0x00); // TMW - memory_.WriteByte(0x212F, 0x00); // TSW - - // Color Math - memory_.WriteByte(0x2130, 0x30); // CGWSEL - memory_.WriteByte(0x2131, 0x00); // CGADSUB - memory_.WriteByte(0x2132, 0xE0); // COLDATA - - // Misc - memory_.WriteByte(0x2133, 0x00); // SETINI - - // Psuedo-Init - memory_.WriteWord(0x2140, 0xBBAA); + // Load the ROM into memory and set up the memory mapping + memory_.Initialize(rom_data); + Reset(true); running_ = true; - scanline = 0; } -void SNES::Run() { - const double targetFPS = 60.0; // 60 frames per second - const double frame_time = 1.0 / targetFPS; - double frame_accumulated_time = 0.0; +void SNES::Reset(bool hard) { + cpu_.Reset(hard); + apu_.Reset(); + ppu_.Reset(); + memory::dma::Reset(&memory_); + input1.latch_line_ = false; + input2.latch_line_ = false; + input1.latched_state_ = 0; + input2.latched_state_ = 0; + if (hard) memset(ram, 0, sizeof(ram)); + ram_adr_ = 0; + memory_.set_h_pos(0); + memory_.set_v_pos(0); + frames_ = 0; + cycles_ = 0; + sync_cycle_ = 0; + apu_catchup_cycles_ = 0.0; + h_irq_enabled_ = false; + v_irq_enabled_ = false; + nmi_enabled_ = false; + h_timer_ = 0x1ff * 4; + v_timer_ = 0x1ff; + in_nmi_ = false; + irq_condition_ = false; + in_irq_ = false; + in_vblank_ = false; + memset(port_auto_read_, 0, sizeof(port_auto_read_)); + auto_joy_read_ = false; + auto_joy_timer_ = 0; + ppu_latch_ = false; + multiply_a_ = 0xff; + multiply_result_ = 0xFE01; + divide_a_ = 0xffFF; + divide_result_ = 0x101; + fast_mem_ = false; + memory_.set_open_bus(0); + next_horiz_event = 16; + InitAccessTime(false); +} - auto last_time = std::chrono::high_resolution_clock::now(); - - if (running_) { - auto current_time = std::chrono::high_resolution_clock::now(); - double delta_time = - std::chrono::duration(current_time - last_time).count(); - last_time = current_time; - - frame_accumulated_time += delta_time; - - // Update the CPU - cpu_.UpdateClock(delta_time); - cpu_.Update(GetCpuMode()); - - // Update the PPU - ppu_.UpdateClock(delta_time); - ppu_.Update(); - - // Update the APU - apu_.UpdateClock(delta_time); - apu_.Update(); - - if (frame_accumulated_time >= frame_time) { - // renderer.Render(); - frame_accumulated_time -= frame_time; - } - - HandleInput(); +void SNES::RunFrame() { + while (in_vblank_) { + cpu_.RunOpcode(); + } + uint32_t frame = frames_; + while (!in_vblank_ && frame == frames_) { + cpu_.RunOpcode(); } } -void SNES::StepRun() { - // Update the CPU - cpu_.UpdateClock(0.0); - cpu_.Update(Cpu::UpdateMode::Step); - - // Update the PPU - ppu_.UpdateClock(0.0); - ppu_.Update(); - - // Update the APU - apu_.UpdateClock(0.0); - apu_.Update(); - - HandleInput(); -} - -// Enable NMI Interrupts -void SNES::EnableVBlankInterrupts() { - v_blank_flag_ = false; - - // Clear the RDNMI VBlank flag - memory_.ReadByte(0x4210); // RDNMI - - // Enable vblank NMI interrupts and Joypad auto-read - memory_.WriteByte(0x4200, 0x81); // NMITIMEN -} - -// Wait until the VBlank routine has been processed -void SNES::WaitForVBlank() { - v_blank_flag_ = true; - - // Loop until `v_blank_flag_` is clear - while (v_blank_flag_) { - std::this_thread::yield(); - } -} - -// NMI Interrupt Service Routine -void SNES::NmiIsr() { - // Switch to a FastROM bank (assuming NmiIsr is in bank 0x80) - // ... - - // Push CPU registers to stack - cpu_.PHP(); - - // Reset DB and DP registers - cpu_.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00 - cpu_.D = 0; - - if (v_blank_flag_) { - VBlankRoutine(); - - // Clear `v_blank_flag_` - v_blank_flag_ = false; - } - - // Increment 32-bit frame_counter_ - frame_counter_++; - - // Restore CPU registers - cpu_.PHB(); -} - -// VBlank routine -void SNES::VBlankRoutine() { - // Read the joypad state - // ... - - // Update the PPU - // ... - - // Update the APU - // ... -} - -void SNES::StartApuDataTransfer() { - // 2. Setting the starting address - const uint16_t startAddress = 0x0200; - memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte - memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte - memory_.WriteByte(0x2141, 0xCC); // Any non-zero value - memory_.WriteByte(0x2140, 0xCC); // Signal to start - - const int DATA_SIZE = 0x1000; // 4 KiB - - // 3. Sending data (simplified) - // Assuming a buffer `audioData` containing the audio program/data - uint8_t audioData[DATA_SIZE]; // Define DATA_SIZE and populate audioData as - // needed - for (int i = 0; i < DATA_SIZE; ++i) { - memory_.WriteByte(0x2141, audioData[i]); - memory_.WriteByte(0x2140, i & 0xFF); - while (memory_.ReadByte(0x2140) != (i & 0xFF)) - ; // Wait for acknowledgment - } - - // 4. Running the SPC700 program - memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte - memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte - memory_.WriteByte(0x2141, 0x00); // Zero to start the program - memory_.WriteByte(0x2140, 0xCE); // Increment by 2 - while (memory_.ReadByte(0x2140) != 0xCE) - ; // Wait for acknowledgment -} +void SNES::CatchUpApu() { apu_.RunCycles(cycles_); } void SNES::HandleInput() { - // ... + memset(port_auto_read_, 0, sizeof(port_auto_read_)); + // latch controllers + input_latch(&input1, true); + input_latch(&input2, true); + input_latch(&input1, false); + input_latch(&input2, false); + for (int i = 0; i < 16; i++) { + uint8_t val = input_read(&input1); + port_auto_read_[0] |= ((val & 1) << (15 - i)); + port_auto_read_[2] |= (((val >> 1) & 1) << (15 - i)); + val = input_read(&input2); + port_auto_read_[1] |= ((val & 1) << (15 - i)); + port_auto_read_[3] |= (((val >> 1) & 1) << (15 - i)); + } } -void SNES::SaveState(const std::string& path) { - // ... +void SNES::RunCycle() { + cycles_ += 2; + + // check for h/v timer irq's + bool condition = ((v_irq_enabled_ || h_irq_enabled_) && + (memory_.v_pos() == v_timer_ || !v_irq_enabled_) && + (memory_.h_pos() == h_timer_ || !h_irq_enabled_)); + + if (!irq_condition_ && condition) { + in_irq_ = true; + cpu_.SetIrq(true); + } + irq_condition_ = condition; + + // increment position; must come after irq checks! (hagane, cybernator) + memory_.set_h_pos(memory_.h_pos() + 2); + + // handle positional stuff + if (memory_.h_pos() == next_horiz_event) { + switch (memory_.h_pos()) { + case 16: { + next_horiz_event = 512; + if (memory_.v_pos() == 0) memory_.init_hdma_request(); + } break; + case 512: { + next_horiz_event = 1104; + // render the line halfway of the screen for better compatibility + if (!in_vblank_ && memory_.v_pos() > 0) ppu_.RunLine(memory_.v_pos()); + } break; + case 1104: { + if (!in_vblank_) memory_.run_hdma_request(); + if (!memory_.pal_timing()) { + // line 240 of odd frame with no interlace is 4 cycles shorter + next_horiz_event = (memory_.v_pos() == 240 && !ppu_.even_frame && + !ppu_.frame_interlace) + ? 1360 + : 1364; + } else { + // line 311 of odd frame with interlace is 4 cycles longer + next_horiz_event = (memory_.v_pos() != 311 || ppu_.even_frame || + !ppu_.frame_interlace) + ? 1364 + : 1368; + } + } break; + case 1360: + case 1364: + case 1368: { // this is the end (of the h-line) + next_horiz_event = 16; + + memory_.set_h_pos(0); + memory_.set_v_pos(memory_.v_pos() + 1); + if (!memory_.pal_timing()) { + // even interlace frame is 263 lines + if ((memory_.v_pos() == 262 && + (!ppu_.frame_interlace || !ppu_.even_frame)) || + memory_.v_pos() == 263) { + memory_.set_v_pos(0); + frames_++; + } + } else { + // even interlace frame is 313 lines + if ((memory_.v_pos() == 312 && + (!ppu_.frame_interlace || !ppu_.even_frame)) || + memory_.v_pos() == 313) { + memory_.set_v_pos(0); + frames_++; + } + } + + // end of hblank, do most memory_.v_pos()-tests + bool starting_vblank = false; + if (memory_.v_pos() == 0) { + // end of vblank + in_vblank_ = false; + in_nmi_ = false; + ppu_.HandleFrameStart(); + } else if (memory_.v_pos() == 225) { + // ask the ppu if we start vblank now or at memory_.v_pos() 240 + // (overscan) + starting_vblank = !ppu_.CheckOverscan(); + } else if (memory_.v_pos() == 240) { + // if we are not yet in vblank, we had an overscan frame, set + // starting_vblank + if (!in_vblank_) starting_vblank = true; + } + if (starting_vblank) { + // catch up the apu at end of emulated frame (we end frame @ start of + // vblank) + CatchUpApu(); + // notify dsp of frame-end, because sometimes dma will extend much + // further past vblank (or even into the next frame) Megaman X2 + // (titlescreen animation), Tales of Phantasia (game demo), Actraiser + // 2 (fade-in @ bootup) + apu_.dsp().NewFrame(); + // we are starting vblank + ppu_.HandleVblank(); + in_vblank_ = true; + in_nmi_ = true; + if (auto_joy_read_) { + // TODO: this starts a little after start of vblank + auto_joy_timer_ = 4224; + HandleInput(); + } + if (nmi_enabled_) { + cpu_.Nmi(); + } + } + } break; + } + } + // handle auto_joy_read_-timer + if (auto_joy_timer_ > 0) auto_joy_timer_ -= 2; } -void SNES::LoadState(const std::string& path) { - // ... +void SNES::RunCycles(int cycles) { + if (memory_.h_pos() + cycles >= 536 && memory_.h_pos() < 536) { + // if we go past 536, add 40 cycles for dram refersh + cycles += 40; + } + for (int i = 0; i < cycles; i += 2) { + RunCycle(); + } +} + +void SNES::SyncCycles(bool start, int sync_cycles) { + int count = 0; + if (start) { + sync_cycle_ = cycles_; + count = sync_cycles - (cycles_ % sync_cycles); + } else { + count = sync_cycles - ((cycles_ - sync_cycle_) % sync_cycles); + } + RunCycles(count); +} + +uint8_t SNES::ReadBBus(uint8_t adr) { + if (adr < 0x40) { + return ppu_.Read(adr, ppu_latch_); + } + if (adr < 0x80) { + CatchUpApu(); // catch up the apu before reading + return apu_.out_ports_[adr & 0x3]; + } + if (adr == 0x80) { + uint8_t ret = ram[ram_adr_++]; + ram_adr_ &= 0x1ffff; + return ret; + } + return memory_.open_bus(); +} + +uint8_t SNES::ReadReg(uint16_t adr) { + switch (adr) { + case 0x4210: { + uint8_t val = 0x2; // CPU version (4 bit) + val |= in_nmi_ << 7; + in_nmi_ = false; + return val | (memory_.open_bus() & 0x70); + } + case 0x4211: { + uint8_t val = in_irq_ << 7; + in_irq_ = false; + cpu_.SetIrq(false); + return val | (memory_.open_bus() & 0x7f); + } + case 0x4212: { + uint8_t val = (auto_joy_timer_ > 0); + val |= (memory_.h_pos() < 4 || memory_.h_pos() >= 1096) << 6; + val |= in_vblank_ << 7; + return val | (memory_.open_bus() & 0x3e); + } + case 0x4213: { + return ppu_latch_ << 7; // IO-port + } + case 0x4214: { + return divide_result_ & 0xff; + } + case 0x4215: { + return divide_result_ >> 8; + } + case 0x4216: { + return multiply_result_ & 0xff; + } + case 0x4217: { + return multiply_result_ >> 8; + } + case 0x4218: + case 0x421a: + case 0x421c: + case 0x421e: { + return port_auto_read_[(adr - 0x4218) / 2] & 0xff; + } + case 0x4219: + case 0x421b: + case 0x421d: + case 0x421f: { + return port_auto_read_[(adr - 0x4219) / 2] >> 8; + } + default: { + return memory_.open_bus(); + } + } +} + +uint8_t SNES::Rread(uint32_t adr) { + uint8_t bank = adr >> 16; + adr &= 0xffff; + if (bank == 0x7e || bank == 0x7f) { + return ram[((bank & 1) << 16) | adr]; // ram + } + if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) { + if (adr < 0x2000) { + return ram[adr]; // ram mirror + } + if (adr >= 0x2100 && adr < 0x2200) { + return ReadBBus(adr & 0xff); // B-bus + } + if (adr == 0x4016) { + return input_read(&input1) | (memory_.open_bus() & 0xfc); + } + if (adr == 0x4017) { + return input_read(&input2) | (memory_.open_bus() & 0xe0) | 0x1c; + } + if (adr >= 0x4200 && adr < 0x4220) { + return ReadReg(adr); // internal registers + } + if (adr >= 0x4300 && adr < 0x4380) { + return memory::dma::Read(&memory_, adr); // dma registers + } + } + // read from cart + return memory_.cart_read(bank, adr); +} + +uint8_t SNES::Read(uint32_t adr) { + uint8_t val = Rread(adr); + memory_.set_open_bus(val); + return val; +} + +void SNES::WriteBBus(uint8_t adr, uint8_t val) { + if (adr < 0x40) { + ppu_.Write(adr, val); + return; + } + if (adr < 0x80) { + CatchUpApu(); // catch up the apu before writing + apu_.in_ports_[adr & 0x3] = val; + return; + } + switch (adr) { + case 0x80: { + ram[ram_adr_++] = val; + ram_adr_ &= 0x1ffff; + break; + } + case 0x81: { + ram_adr_ = (ram_adr_ & 0x1ff00) | val; + break; + } + case 0x82: { + ram_adr_ = (ram_adr_ & 0x100ff) | (val << 8); + break; + } + case 0x83: { + ram_adr_ = (ram_adr_ & 0x0ffff) | ((val & 1) << 16); + break; + } + } +} + +void SNES::WriteReg(uint16_t adr, uint8_t val) { + switch (adr) { + case 0x4200: { + auto_joy_read_ = val & 0x1; + if (!auto_joy_read_) auto_joy_timer_ = 0; + h_irq_enabled_ = val & 0x10; + v_irq_enabled_ = val & 0x20; + if (!h_irq_enabled_ && !v_irq_enabled_) { + in_irq_ = false; + cpu_.SetIrq(false); + } + // if nmi is enabled while in_nmi_ is still set, immediately generate nmi + if (!nmi_enabled_ && (val & 0x80) && in_nmi_) { + cpu_.Nmi(); + } + nmi_enabled_ = val & 0x80; + cpu_.set_int_delay(true); + break; + } + case 0x4201: { + if (!(val & 0x80) && ppu_latch_) { + // latch the ppu + ppu_.LatchHV(); + } + ppu_latch_ = val & 0x80; + break; + } + case 0x4202: { + multiply_a_ = val; + break; + } + case 0x4203: { + multiply_result_ = multiply_a_ * val; + break; + } + case 0x4204: { + divide_a_ = (divide_a_ & 0xff00) | val; + break; + } + case 0x4205: { + divide_a_ = (divide_a_ & 0x00ff) | (val << 8); + break; + } + case 0x4206: { + if (val == 0) { + divide_result_ = 0xffff; + multiply_result_ = divide_a_; + } else { + divide_result_ = divide_a_ / val; + multiply_result_ = divide_a_ % val; + } + break; + } + case 0x4207: { + h_timer_ = (h_timer_ & 0x100) | val; + break; + } + case 0x4208: { + h_timer_ = (h_timer_ & 0x0ff) | ((val & 1) << 8); + break; + } + case 0x4209: { + v_timer_ = (v_timer_ & 0x100) | val; + break; + } + case 0x420a: { + v_timer_ = (v_timer_ & 0x0ff) | ((val & 1) << 8); + break; + } + case 0x420b: { + memory::dma::StartDma(&memory_, val, false); + break; + } + case 0x420c: { + memory::dma::StartDma(&memory_, val, true); + break; + } + case 0x420d: { + fast_mem_ = val & 0x1; + break; + } + default: { + break; + } + } +} + +void SNES::Write(uint32_t adr, uint8_t val) { + memory_.set_open_bus(val); + uint8_t bank = adr >> 16; + adr &= 0xffff; + if (bank == 0x7e || bank == 0x7f) { + ram[((bank & 1) << 16) | adr] = val; // ram + } + if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) { + if (adr < 0x2000) { + ram[adr] = val; // ram mirror + } + if (adr >= 0x2100 && adr < 0x2200) { + WriteBBus(adr & 0xff, val); // B-bus + } + if (adr == 0x4016) { + input_latch(&input1, val & 1); // input latch + input_latch(&input2, val & 1); + } + if (adr >= 0x4200 && adr < 0x4220) { + WriteReg(adr, val); // internal registers + } + if (adr >= 0x4300 && adr < 0x4380) { + memory::dma::Write(&memory_, adr, val); // dma registers + } + } + + // write to cart + memory_.cart_write(bank, adr, val); +} + +int SNES::GetAccessTime(uint32_t adr) { + uint8_t bank = adr >> 16; + adr &= 0xffff; + if ((bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) && adr < 0x8000) { + // 00-3f,80-bf:0-7fff + if (adr < 0x2000 || adr >= 0x6000) return 8; // 0-1fff, 6000-7fff + if (adr < 0x4000 || adr >= 0x4200) return 6; // 2000-3fff, 4200-5fff + return 12; // 4000-41ff + } + // 40-7f,co-ff:0000-ffff, 00-3f,80-bf:8000-ffff + return (fast_mem_ && bank >= 0x80) ? 6 + : 8; // depends on setting in banks 80+ +} + +uint8_t SNES::CpuRead(uint32_t adr) { + cpu_.set_int_delay(false); + const int cycles = access_time[adr] - 4; + memory::dma::HandleDma(this, &memory_, cycles); + RunCycles(cycles); + uint8_t rv = Read(adr); + memory::dma::HandleDma(this, &memory_, 4); + RunCycles(4); + return rv; +} + +void SNES::CpuWrite(uint32_t adr, uint8_t val) { + cpu_.set_int_delay(false); + const int cycles = access_time[adr]; + memory::dma::HandleDma(this, &memory_, cycles); + RunCycles(cycles); + Write(adr, val); +} + +void SNES::CpuIdle(bool waiting) { + cpu_.set_int_delay(false); + memory::dma::HandleDma(this, &memory_, 6); + RunCycles(6); +} + +void SNES::SetSamples(int16_t* sample_data, int wanted_samples) { + apu_.dsp().GetSamples(sample_data, wanted_samples, memory_.pal_timing()); +} + +void SNES::SetPixels(uint8_t* pixel_data) { ppu_.PutPixels(pixel_data); } + +void SNES::SetButtonState(int player, int button, bool pressed) { + // set key in controller + if (player == 1) { + if (pressed) { + input1.current_state_ |= 1 << button; + } else { + input1.current_state_ &= ~(1 << button); + } + } else { + if (pressed) { + input2.current_state_ |= 1 << button; + } else { + input2.current_state_ &= ~(1 << button); + } + } +} + +void SNES::InitAccessTime(bool recalc) { + int start = (recalc) ? 0x800000 : 0; // recalc only updates fast rom + access_time.resize(0x1000000); + for (int i = start; i < 0x1000000; i++) { + access_time[i] = GetAccessTime(i); + } } } // namespace emu diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index 84ba8026..2dee6099 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -11,7 +11,6 @@ #include "app/emu/cpu/clock.h" #include "app/emu/cpu/cpu.h" #include "app/emu/debug/debugger.h" -#include "app/emu/memory/dma.h" #include "app/emu/memory/memory.h" #include "app/emu/video/ppu.h" #include "app/rom.h" @@ -20,99 +19,122 @@ namespace yaze { namespace app { namespace emu { -using namespace memory; +struct Input { + uint8_t type; + // latchline + bool latch_line_; + // for controller + uint16_t current_state_; // actual state + uint16_t latched_state_; +}; -class SNES : public DirectMemoryAccess { +class SNES { public: SNES() = default; ~SNES() = default; - RomInfo ReadRomHeader(uint32_t offset); - // Initialization - void Init(Rom& rom); + void Init(std::vector& rom_data); + void Reset(bool hard = false); - // Main emulation loop - void Run(); - - // Step through a single instruction - void StepRun(); - - // Enable NMI Interrupts - void EnableVBlankInterrupts(); - - // Wait until the VBlank routine has been processed - void WaitForVBlank(); - - // NMI Interrupt Service Routine - void NmiIsr(); - - // VBlank routine - void VBlankRoutine(); - - // Boot the APU with the IPL ROM - void BootApuWithIPL(); - void StartApuDataTransfer(); + // Emulation + void RunFrame(); + void CatchUpApu(); // Controller input handling void HandleInput(); - // Save/Load game state - void SaveState(const std::string& path); - void LoadState(const std::string& path); + // Clock cycling and synchronization + void RunCycle(); + void RunCycles(int cycles); + void SyncCycles(bool start, int sync_cycles); + uint8_t ReadBBus(uint8_t adr); + uint8_t ReadReg(uint16_t adr); + uint8_t Rread(uint32_t adr); + uint8_t Read(uint32_t adr); + + void WriteBBus(uint8_t adr, uint8_t val); + void WriteReg(uint16_t adr, uint8_t val); + void Write(uint32_t adr, uint8_t val); + + int GetAccessTime(uint32_t adr); + uint8_t CpuRead(uint32_t adr); + void CpuWrite(uint32_t adr, uint8_t val); + void CpuIdle(bool waiting); + + void SetSamples(int16_t* sample_data, int wanted_samples); + void SetPixels(uint8_t* pixel_data); + void SetButtonState(int player, int button, bool pressed); bool running() const { return running_; } - auto cpu() -> Cpu& { return cpu_; } auto ppu() -> video::Ppu& { return ppu_; } - auto Memory() -> MemoryImpl* { return &memory_; } + auto apu() -> audio::Apu& { return apu_; } + auto Memory() -> memory::MemoryImpl& { return memory_; } + auto get_ram() -> uint8_t* { return ram; } + auto mutable_cycles() -> uint64_t& { return cycles_; } + void InitAccessTime(bool recalc); - void SetCpuMode(int mode) { cpu_mode_ = mode; } - Cpu::UpdateMode GetCpuMode() const { - return static_cast(cpu_mode_); - } - - void SetupMemory(Rom& rom) { - // Setup observers for the memory space - memory_.AddObserver(&apu_); - memory_.AddObserver(&ppu_); - - // Load the ROM into memory and set up the memory mapping - rom_data = rom.vector(); - memory_.Initialize(rom_data); - } + std::vector access_time; private: - void WriteToRegister(uint16_t address, uint8_t value) { - memory_.WriteByte(address, value); - } - // Components of the SNES - MemoryImpl memory_; ClockImpl clock_; - audio::AudioRamImpl audio_ram_; - - Cpu cpu_{memory_, clock_}; - video::Ppu ppu_{memory_, clock_}; - audio::Apu apu_{memory_, audio_ram_, clock_}; - - // Helper classes - RomInfo rom_info_; Debugger debugger; + memory::MemoryImpl memory_; + + memory::CpuCallbacks cpu_callbacks_ = { + [&](uint32_t adr) { return CpuRead(adr); }, + [&](uint32_t adr, uint8_t val) { CpuWrite(adr, val); }, + [&](bool waiting) { CpuIdle(waiting); }, + }; + Cpu cpu_{memory_, clock_, cpu_callbacks_}; + video::Ppu ppu_{memory_, clock_}; + audio::Apu apu_{memory_}; // Currently loaded ROM std::vector rom_data; - // Byte flag to indicate if the VBlank routine should be executed or not - std::atomic v_blank_flag_; - - // 32-bit counter to track the number of NMI interrupts - std::atomic frame_counter_; - - // Other private member variables + // Emulation state bool running_ = false; - int scanline; - int cpu_mode_ = 0; + + // ram + uint8_t ram[0x20000]; + uint32_t ram_adr_; + + // Frame timing + uint32_t frames_ = 0; + uint64_t cycles_ = 0; + uint64_t sync_cycle_ = 0; + double apu_catchup_cycles_; + uint32_t next_horiz_event; + + // Nmi / Irq + bool h_irq_enabled_ = false; + bool v_irq_enabled_ = false; + bool nmi_enabled_ = false; + uint16_t h_timer_ = 0; + uint16_t v_timer_ = 0; + bool in_nmi_ = false; + bool irq_condition_ = false; + bool in_irq_ = false; + bool in_vblank_; + + // Multiplication / Division + uint8_t multiply_a_; + uint16_t multiply_result_; + uint16_t divide_a_; + uint16_t divide_result_; + + // Joypad State + Input input1; + Input input2; + uint16_t port_auto_read_[4]; // as read by auto-joypad read + bool auto_joy_read_ = false; + uint16_t auto_joy_timer_ = 0; + bool ppu_latch_; + + bool fast_mem_ = false; }; } // namespace emu diff --git a/src/app/emu/video/ppu.cc b/src/app/emu/video/ppu.cc index 3b94e39e..f7fa8c32 100644 --- a/src/app/emu/video/ppu.cc +++ b/src/app/emu/video/ppu.cc @@ -13,421 +13,1044 @@ namespace video { using namespace PpuRegisters; -void Ppu::Update() { - auto cycles_to_run = clock_.GetCycleCount(); +// array for layer definitions per mode: +// 0-7: mode 0-7; 8: mode 1 + l3prio; 9: mode 7 + extbg - UpdateInternalState(cycles_to_run); +// 0-3; layers 1-4; 4: sprites; 5: nonexistent +static const int kLayersPerMode[10][12] = { + {4, 0, 1, 4, 0, 1, 4, 2, 3, 4, 2, 3}, {4, 0, 1, 4, 0, 1, 4, 2, 4, 2, 5, 5}, + {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5}, {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5}, + {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5}, {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5}, + {4, 0, 4, 4, 0, 4, 5, 5, 5, 5, 5, 5}, {4, 4, 4, 0, 4, 5, 5, 5, 5, 5, 5, 5}, + {2, 4, 0, 1, 4, 0, 1, 4, 4, 2, 5, 5}, {4, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5, 5}}; - // Render however many scanlines we're supposed to. - if (current_scanline_ < visibleScanlines) { - // Render the current scanline - RenderScanline(); +static const int kPrioritysPerMode[10][12] = { + {3, 1, 1, 2, 0, 0, 1, 1, 1, 0, 0, 0}, {3, 1, 1, 2, 0, 0, 1, 1, 0, 0, 5, 5}, + {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5}, {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5}, + {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5}, {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5}, + {3, 1, 2, 1, 0, 0, 5, 5, 5, 5, 5, 5}, {3, 2, 1, 0, 0, 5, 5, 5, 5, 5, 5, 5}, + {1, 3, 1, 1, 2, 0, 0, 1, 0, 0, 5, 5}, {3, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5, 5}}; - // Increment the current scanline - current_scanline_++; +static const int kLayerCountPerMode[10] = {12, 10, 8, 8, 8, 8, 6, 5, 10, 7}; + +static const int kBitDepthsPerMode[10][4] = { + {2, 2, 2, 2}, {4, 4, 2, 5}, {4, 4, 5, 5}, {8, 4, 5, 5}, {8, 2, 5, 5}, + {4, 2, 5, 5}, {4, 5, 5, 5}, {8, 5, 5, 5}, {4, 4, 2, 5}, {8, 7, 5, 5}}; + +static const int kSpriteSizes[8][2] = {{8, 16}, {8, 32}, {8, 64}, {16, 32}, + {16, 64}, {32, 64}, {16, 32}, {16, 32}}; + +void Ppu::Update() {} + +void Ppu::Reset() { + memset(vram, 0, sizeof(vram)); + vram_pointer = 0; + vram_increment_on_high_ = false; + vram_increment_ = 1; + vram_remap_mode_ = 0; + vram_read_buffer_ = 0; + memset(cgram, 0, sizeof(cgram)); + cgram_pointer_ = 0; + cgram_second_write_ = false; + cgram_buffer_ = 0; + memset(oam, 0, sizeof(oam)); + memset(high_oam_, 0, sizeof(high_oam_)); + oam_adr_ = 0; + oam_adr_written_ = 0; + oam_in_high_ = false; + oam_in_high_written_ = false; + oam_second_write_ = false; + oam_buffer_ = 0; + obj_priority_ = false; + obj_tile_adr1_ = 0; + obj_tile_adr2_ = 0; + obj_size_ = 0; + obj_pixel_buffer_.fill(0); + obj_priority_buffer_.fill(0); + time_over_ = false; + range_over_ = false; + obj_interlace_ = false; + for (int i = 0; i < 4; i++) { + bg_layer_[i].hScroll = 0; + bg_layer_[i].vScroll = 0; + bg_layer_[i].tilemapWider = false; + bg_layer_[i].tilemapHigher = false; + bg_layer_[i].tilemapAdr = 0; + bg_layer_[i].tileAdr = 0; + bg_layer_[i].bigTiles = false; + bg_layer_[i].mosaicEnabled = false; + } + scroll_prev_ = 0; + scroll_prev2_ = 0; + mosaic_size_ = 1; + mosaic_startline_ = 1; + for (int i = 0; i < 5; i++) { + layer_[i].mainScreenEnabled = false; + layer_[i].subScreenEnabled = false; + layer_[i].mainScreenWindowed = false; + layer_[i].subScreenWindowed = false; + } + memset(m7matrix, 0, sizeof(m7matrix)); + m7prev = 0; + m7largeField = false; + m7charFill = false; + m7xFlip = false; + m7yFlip = false; + m7extBg = false; + m7startX = 0; + m7startY = 0; + for (int i = 0; i < 6; i++) { + windowLayer[i].window1enabled = false; + windowLayer[i].window2enabled = false; + windowLayer[i].window1inversed = false; + windowLayer[i].window2inversed = false; + windowLayer[i].maskLogic = 0; + } + window1left = 0; + window1right = 0; + window2left = 0; + window2right = 0; + clip_mode_ = 0; + prevent_math_mode_ = 0; + add_subscreen_ = false; + subtract_color_ = false; + half_color_ = false; + memset(math_enabled_array_, 0, sizeof(math_enabled_array_)); + fixed_color_r_ = 0; + fixed_color_g_ = 0; + fixed_color_b_ = 0; + forced_blank_ = true; + brightness = 0; + mode = 0; + bg3priority = false; + even_frame = false; + pseudo_hires_ = false; + overscan_ = false; + frame_overscan_ = false; + interlace = false; + frame_interlace = false; + direct_color_ = false; + h_count_ = 0; + v_count_ = 0; + h_count_second_ = false; + v_count_second_ = false; + counters_latched_ = false; + ppu1_open_bus_ = 0; + ppu2_open_bus_ = 0; + memset(pixelBuffer, 0, sizeof(pixelBuffer)); +} + +void Ppu::HandleFrameStart() { + // called at (0, 0) + mosaic_startline_ = 1; + range_over_ = false; + time_over_ = false; + even_frame = !even_frame; +} + +void Ppu::RunLine(int line) { + // called for lines 1-224/239 + // evaluate sprites + obj_pixel_buffer_.fill(0); + if (!forced_blank_) EvaluateSprites(line - 1); + // actual line + if (mode == 7) CalculateMode7Starts(line); + for (int x = 0; x < 256; x++) { + HandlePixel(x, line); } } -void Ppu::UpdateInternalState(int cycles) { - // Update the Ppu's internal state based on the number of cycles - cycle_count_ += cycles; - - // Check if it's time to move to the next scanline - if (cycle_count_ >= cyclesPerScanline) { - current_scanline_++; - cycle_count_ -= cyclesPerScanline; - - // If we've reached the end of the frame, reset to the first scanline - if (current_scanline_ >= totalScanlines) { - current_scanline_ = 0; +void Ppu::HandlePixel(int x, int y) { + int r = 0, r2 = 0; + int g = 0, g2 = 0; + int b = 0, b2 = 0; + if (!forced_blank_) { + int mainLayer = GetPixel(x, y, false, &r, &g, &b); + bool colorWindowState = GetWindowState(5, x); + if (clip_mode_ == 3 || (clip_mode_ == 2 && colorWindowState) || + (clip_mode_ == 1 && !colorWindowState)) { + r = 0; + g = 0; + b = 0; + } + int secondLayer = 5; // backdrop + bool mathEnabled = mainLayer < 6 && math_enabled_array_[mainLayer] && + !(prevent_math_mode_ == 3 || + (prevent_math_mode_ == 2 && colorWindowState) || + (prevent_math_mode_ == 1 && !colorWindowState)); + if ((mathEnabled && add_subscreen_) || pseudo_hires_ || mode == 5 || + mode == 6) { + secondLayer = GetPixel(x, y, true, &r2, &g2, &b2); + } + // TODO: subscreen pixels can be clipped to black as well + // TODO: math for subscreen pixels (add/sub sub to main) + if (mathEnabled) { + if (subtract_color_) { + r -= (add_subscreen_ && secondLayer != 5) ? r2 : fixed_color_r_; + g -= (add_subscreen_ && secondLayer != 5) ? g2 : fixed_color_g_; + b -= (add_subscreen_ && secondLayer != 5) ? b2 : fixed_color_b_; + } else { + r += (add_subscreen_ && secondLayer != 5) ? r2 : fixed_color_r_; + g += (add_subscreen_ && secondLayer != 5) ? g2 : fixed_color_g_; + b += (add_subscreen_ && secondLayer != 5) ? b2 : fixed_color_b_; + } + if (half_color_ && (secondLayer != 5 || !add_subscreen_)) { + r >>= 1; + g >>= 1; + b >>= 1; + } + if (r > 31) r = 31; + if (g > 31) g = 31; + if (b > 31) b = 31; + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + } + if (!(pseudo_hires_ || mode == 5 || mode == 6)) { + r2 = r; + g2 = g; + b2 = b; } } + int row = (y - 1) + (even_frame ? 0 : 239); + pixelBuffer[row * 2048 + x * 8 + 0 + pixelOutputFormat] = + ((b2 << 3) | (b2 >> 2)) * brightness / 15; + pixelBuffer[row * 2048 + x * 8 + 1 + pixelOutputFormat] = + ((g2 << 3) | (g2 >> 2)) * brightness / 15; + pixelBuffer[row * 2048 + x * 8 + 2 + pixelOutputFormat] = + ((r2 << 3) | (r2 >> 2)) * brightness / 15; + pixelBuffer[row * 2048 + x * 8 + 4 + pixelOutputFormat] = + ((b << 3) | (b >> 2)) * brightness / 15; + pixelBuffer[row * 2048 + x * 8 + 5 + pixelOutputFormat] = + ((g << 3) | (g >> 2)) * brightness / 15; + pixelBuffer[row * 2048 + x * 8 + 6 + pixelOutputFormat] = + ((r << 3) | (r >> 2)) * brightness / 15; } -void Ppu::RenderScanline() { - for (int y = 0; y < 240; ++y) { - for (int x = 0; x < 256; ++x) { - // Calculate the color index based on the x and y coordinates - uint8_t color_index = (x + y) % 8; - - // Set the pixel in the frame buffer to the calculated color index - frame_buffer_[y * 256 + x] = color_index; +int Ppu::GetPixel(int x, int y, bool subscreen, int* r, int* g, int* b) { + // figure out which color is on this location on main- or subscreen, sets it + // in r, g, b + // returns which layer it is: 0-3 for bg layer, 4 or 6 for sprites (depending + // on palette), 5 for backdrop + int actMode = mode == 1 && bg3priority ? 8 : mode; + actMode = mode == 7 && m7extBg ? 9 : actMode; + int layer = 5; + int pixel = 0; + for (int i = 0; i < kLayerCountPerMode[actMode]; i++) { + int curLayer = kLayersPerMode[actMode][i]; + int curPriority = kPrioritysPerMode[actMode][i]; + bool layerActive = false; + if (!subscreen) { + layerActive = layer_[curLayer].mainScreenEnabled && + (!layer_[curLayer].mainScreenWindowed || + !GetWindowState(curLayer, x)); + } else { + layerActive = + layer_[curLayer].subScreenEnabled && + (!layer_[curLayer].subScreenWindowed || !GetWindowState(curLayer, x)); + } + if (layerActive) { + if (curLayer < 4) { + // bg layer + int lx = x; + int ly = y; + if (bg_layer_[curLayer].mosaicEnabled && mosaic_size_ > 1) { + lx -= lx % mosaic_size_; + ly -= (ly - mosaic_startline_) % mosaic_size_; + } + if (mode == 7) { + pixel = GetPixelForMode7(lx, curLayer, curPriority); + } else { + lx += bg_layer_[curLayer].hScroll; + if (mode == 5 || mode == 6) { + lx *= 2; + lx += (subscreen || bg_layer_[curLayer].mosaicEnabled) ? 0 : 1; + if (interlace) { + ly *= 2; + ly += (even_frame || bg_layer_[curLayer].mosaicEnabled) ? 0 : 1; + } + } + ly += bg_layer_[curLayer].vScroll; + if (mode == 2 || mode == 4 || mode == 6) { + HandleOPT(curLayer, &lx, &ly); + } + pixel = + GetPixelForBgLayer(lx & 0x3ff, ly & 0x3ff, curLayer, curPriority); + } + } else { + // get a pixel from the sprite buffer + pixel = 0; + if (obj_priority_buffer_[x] == curPriority) + pixel = obj_pixel_buffer_[x]; + } + } + if (pixel > 0) { + layer = curLayer; + break; } } - - // Fetch the tile data from VRAM, tile map data from memory, and palette data - // from CGRAM - // UpdateTileData(); // Fetches the tile data from VRAM and stores it in - // an internal buffer - UpdateTileMapData(); // Fetches the tile map data from memory and stores it - // in an internal buffer - UpdatePaletteData(); // Fetches the palette data from CGRAM and stores it in - // an internal buffer - - // Render the background layers, taking into account the current mode and - // layer priorities - for (int layer = 1; layer <= 4; ++layer) { - RenderBackground(layer); // Renders the specified background layer into an - // internal layer buffer + if (direct_color_ && layer < 4 && kBitDepthsPerMode[actMode][layer] == 8) { + *r = ((pixel & 0x7) << 2) | ((pixel & 0x100) >> 7); + *g = ((pixel & 0x38) >> 1) | ((pixel & 0x200) >> 8); + *b = ((pixel & 0xc0) >> 3) | ((pixel & 0x400) >> 8); + } else { + uint16_t color = cgram[pixel & 0xff]; + *r = color & 0x1f; + *g = (color >> 5) & 0x1f; + *b = (color >> 10) & 0x1f; } - - // Render the sprite layer, taking into account sprite priorities and - // transparency - RenderSprites(); // Renders the sprite layer into an internal sprite buffer - - // Apply effects to the layers, such as scaling, rotation, and blending - ApplyEffects(); // Applies effects to the layers based on the current mode - // and register settings - - // Combine the layers into a single image and store it in the frame buffer - ComposeLayers(); // Combines the layers into a single image and stores it in - // the frame buffer - - // Display the frame buffer on the screen - DisplayFrameBuffer(); + if (layer == 4 && pixel < 0xc0) + layer = 6; // sprites with palette color < 0xc0 + return layer; } -void Ppu::Notify(uint32_t address, uint8_t data) { - // Handle communication in the Ppu. - if (address >= 0x2100 && address <= 0x213F) { - // Handle register notification - switch (address) { - case INIDISP: - enable_forced_blanking_ = (data >> 7) & 0x01; - break; - case OBJSEL: - oam_size_.base_selection = (data >> 2) & 0x03; - oam_size_.name_selection = (data >> 4) & 0x07; - oam_size_.object_size = data & 0x03; - break; - case OAMADDL: - oam_address_.oam_address_low = data; - break; - case OAMADDH: - oam_address_.oam_address_msb = data & 0x01; - oam_address_.oam_priority_rotation = (data >> 1) & 0x01; - break; - case OAMDATA: - // Write the data to OAM - break; - case BGMODE: - // Update the Ppu mode settings - UpdateModeSettings(); - break; - case MOSAIC: - mosaic_.bg_enable = (data >> 7) & 0x01; - mosaic_.mosaic_size = data & 0x0F; - break; - case BG1SC: - bgsc_[0] = BGSC(data); - break; - case BG2SC: - bgsc_[1] = BGSC(data); - break; - case BG3SC: - bgsc_[2] = BGSC(data); - break; - case BG4SC: - bgsc_[3] = BGSC(data); - break; - case BG12NBA: - bgnba_[0] = BGNBA(data); - break; - case BG34NBA: - bgnba_[1] = BGNBA(data); - break; - case BG1HOFS: - bghofs_[0].horizontal_scroll = data; - break; - case BG2HOFS: - bghofs_[1].horizontal_scroll = data; - break; - case BG3HOFS: - bghofs_[2].horizontal_scroll = data; - break; - case BG4HOFS: - bghofs_[3].horizontal_scroll = data; - break; - case BG1VOFS: - bgvofs_[0].vertical_scroll = data; - break; - case BG2VOFS: - bgvofs_[1].vertical_scroll = data; - break; - case BG3VOFS: - bgvofs_[2].vertical_scroll = data; - break; - case BG4VOFS: - bgvofs_[3].vertical_scroll = data; - break; - case VMAIN: - vmain_.increment_size = data & 0x03; - vmain_.remapping = (data >> 2) & 0x03; - vmain_.address_increment_mode = (data >> 4) & 0x01; - break; - case VMADDL: - vmaddl_.address_low = data; - break; - case VMADDH: - vmaddh_.address_high = data; - break; - case M7SEL: - m7sel_.flip_horizontal = data & 0x01; - m7sel_.flip_vertical = (data >> 1) & 0x01; - m7sel_.fill = (data >> 2) & 0x01; - m7sel_.tilemap_repeat = (data >> 3) & 0x01; - break; - case M7A: - m7a_.matrix_a = data; - break; - case M7B: - m7b_.matrix_b = data; - break; - case M7C: - m7c_.matrix_c = data; - break; - case M7D: - m7d_.matrix_d = data; - break; - case M7X: - m7x_.center_x = data; - break; - case M7Y: - m7y_.center_y = data; - break; - case CGADD: - cgadd_.address = data; - break; - case CGDATA: - // Write the data to CGRAM - break; - case W12SEL: - w12sel_.enable_bg1_a = data & 0x01; - w12sel_.invert_bg1_a = (data >> 1) & 0x01; - w12sel_.enable_bg1_b = (data >> 2) & 0x01; - w12sel_.invert_bg1_b = (data >> 3) & 0x01; - w12sel_.enable_bg2_c = (data >> 4) & 0x01; - w12sel_.invert_bg2_c = (data >> 5) & 0x01; - w12sel_.enable_bg2_d = (data >> 6) & 0x01; - w12sel_.invert_bg2_d = (data >> 7) & 0x01; - break; - case W34SEL: - w34sel_.enable_bg3_e = data & 0x01; - w34sel_.invert_bg3_e = (data >> 1) & 0x01; - w34sel_.enable_bg3_f = (data >> 2) & 0x01; - w34sel_.invert_bg3_f = (data >> 3) & 0x01; - w34sel_.enable_bg4_g = (data >> 4) & 0x01; - w34sel_.invert_bg4_g = (data >> 5) & 0x01; - w34sel_.enable_bg4_h = (data >> 6) & 0x01; - w34sel_.invert_bg4_h = (data >> 7) & 0x01; - break; - case WOBJSEL: - wobjsel_.enable_obj_i = data & 0x01; - wobjsel_.invert_obj_i = (data >> 1) & 0x01; - wobjsel_.enable_obj_j = (data >> 2) & 0x01; - wobjsel_.invert_obj_j = (data >> 3) & 0x01; - wobjsel_.enable_color_k = (data >> 4) & 0x01; - wobjsel_.invert_color_k = (data >> 5) & 0x01; - wobjsel_.enable_color_l = (data >> 6) & 0x01; - wobjsel_.invert_color_l = (data >> 7) & 0x01; - break; - case WH0: - wh0_.left_position = data; - break; - case WH1: - wh1_.right_position = data; - break; - case WH2: - wh2_.left_position = data; - break; - case WH3: - wh3_.right_position = data; - break; - case TM: - tm_.enable_layer = (data >> 5) & 0x01; // - break; - case TS: - ts_.enable_layer = (data >> 5) & 0x01; - break; - case TMW: - tmw_.enable_window = (data >> 5) & 0x01; - break; - case TSW: - tsw_.enable_window = (data >> 5) & 0x01; - break; - } +int Ppu::GetPixelForMode7(int x, int layer, bool priority) { + uint8_t rx = m7xFlip ? 255 - x : x; + int xPos = (m7startX + m7matrix[0] * rx) >> 8; + int yPos = (m7startY + m7matrix[2] * rx) >> 8; + bool outsideMap = xPos < 0 || xPos >= 1024 || yPos < 0 || yPos >= 1024; + xPos &= 0x3ff; + yPos &= 0x3ff; + if (!m7largeField) outsideMap = false; + uint8_t tile = outsideMap ? 0 : vram[(yPos >> 3) * 128 + (xPos >> 3)] & 0xff; + uint8_t pixel = outsideMap && !m7charFill + ? 0 + : vram[tile * 64 + (yPos & 7) * 8 + (xPos & 7)] >> 8; + if (layer == 1) { + if (((bool)(pixel & 0x80)) != priority) return 0; + return pixel & 0x7f; } + return pixel; } -void Ppu::UpdateModeSettings() { - // Read the Ppu mode settings from the Ppu registers - uint8_t modeRegister = memory_.ReadByte(PpuRegisters::INIDISP); - - // Mode is stored in the lower 3 bits - auto mode = static_cast(modeRegister & 0x07); - - // Update the tilemap, tile data, and palette settings - switch (mode) { - case BackgroundMode::Mode0: - // Mode 0: 4 layers, each 2bpp (4 colors) - break; - - case BackgroundMode::Mode1: - // Mode 1: 2 layers, 4bpp (16 colors), 1 layer, 2bpp (4 colors) - break; - - case BackgroundMode::Mode2: - // Mode 2: 2 layers, 4bpp (16 colors), 1 layer for offset-per-tile - break; - - case BackgroundMode::Mode3: - // Mode 3: 1 layer, 8bpp (256 colors), 1 layer, 4bpp (16 colors) - break; - - case BackgroundMode::Mode4: - // Mode 4: 1 layer, 8bpp (256 colors), 1 layer, 2bpp (4 colors) - break; - - case BackgroundMode::Mode5: - // Mode 5: 1 layer, 4bpp (16 colors), 1 layer, 2bpp (4 colors) hi-res - break; - - case BackgroundMode::Mode6: - // Mode 6: 1 layer, 4bpp (16 colors), 1 layer for offset-per-tile, hi-res - break; - - case BackgroundMode::Mode7: - // Mode 7: 1 layer, 8bpp (256 colors), rotation/scaling - break; - - default: - // Invalid mode setting, handle the error or set default settings - // ... - break; +bool Ppu::GetWindowState(int layer, int x) { + if (!windowLayer[layer].window1enabled && + !windowLayer[layer].window2enabled) { + return false; } - - // Update the internal state of the Ppu based on the mode settings - // Update tile data, tilemaps, sprites, and palette based on the mode settings - UpdateTileData(); - UpdatePaletteData(); -} - -// Internal methods to handle Ppu rendering and operations -void Ppu::UpdateTileData() { - // Fetch tile data from VRAM and store it in the internal buffer - for (uint16_t address = 0; address < tile_data_size_; ++address) { - tile_data_[address] = memory_.ReadByte(vram_base_address_ + address); + if (windowLayer[layer].window1enabled && !windowLayer[layer].window2enabled) { + bool test = x >= window1left && x <= window1right; + return windowLayer[layer].window1inversed ? !test : test; } - - // Update the tilemap entries based on the fetched tile data - for (uint16_t entryIndex = 0; entryIndex < tilemap_.entries.size(); - ++entryIndex) { - uint16_t tilemapAddress = - tilemap_base_address_ + entryIndex * sizeof(TilemapEntry); - // Assume ReadWord reads a 16-bit value from VRAM - uint16_t tileData = memory_.ReadWord(tilemapAddress); - - // Extract tilemap entry attributes from the tile data - TilemapEntry entry; - // Tile number is stored in the lower 10 bits - entry.tileNumber = tileData & 0x03FF; - - // Palette is stored in bits 10-12 - entry.palette = (tileData >> 10) & 0x07; - - // Priority is stored in bit 13 - entry.priority = (tileData >> 13) & 0x01; - - // Horizontal flip is stored in bit 14 - entry.hFlip = (tileData >> 14) & 0x01; - - // Vertical flip is stored in bit 15 - entry.vFlip = (tileData >> 15) & 0x01; - - tilemap_.entries[entryIndex] = entry; + if (!windowLayer[layer].window1enabled && windowLayer[layer].window2enabled) { + bool test = x >= window2left && x <= window2right; + return windowLayer[layer].window2inversed ? !test : test; } - - // Update the sprites based on the fetched tile data - for (uint16_t spriteIndex = 0; spriteIndex < sprites_.size(); ++spriteIndex) { - uint16_t spriteAddress = spriteIndex * sizeof(SpriteAttributes); - uint16_t spriteData = memory_.ReadWord(spriteAddress); - - // Extract sprite attributes from the sprite data - SpriteAttributes sprite; - - sprite.x = memory_.ReadByte(spriteAddress); - sprite.y = memory_.ReadByte(spriteAddress + 1); - - // Tile number is stored in the lower 9 - sprite.tile = spriteData & 0x01FF; - - // bits Palette is stored in bits 9-11 - sprite.palette = (spriteData >> 9) & 0x07; - - // Priority is stored in bits 12-13 - sprite.priority = (spriteData >> 12) & 0x03; - - // Horizontal flip is stored in bit 14 - sprite.hFlip = (spriteData >> 14) & 0x01; - - // Vertical flip is stored in bit 15 - sprite.vFlip = (spriteData >> 15) & 0x01; - - sprites_[spriteIndex] = sprite; - } -} - -void Ppu::UpdateTileMapData() {} - -void Ppu::RenderBackground(int layer) { - auto bg1_tilemap_info = BGSC(0); - auto bg1_chr_data = BGNBA(0); - auto bg2_tilemap_info = BGSC(0); - auto bg2_chr_data = BGNBA(0); - auto bg3_tilemap_info = BGSC(0); - auto bg3_chr_data = BGNBA(0); - auto bg4_tilemap_info = BGSC(0); - auto bg4_chr_data = BGNBA(0); - - switch (layer) { + bool test1 = x >= window1left && x <= window1right; + bool test2 = x >= window2left && x <= window2right; + if (windowLayer[layer].window1inversed) test1 = !test1; + if (windowLayer[layer].window2inversed) test2 = !test2; + switch (windowLayer[layer].maskLogic) { + case 0: + return test1 || test2; case 1: - // Render the first background layer - bg1_tilemap_info = BGSC(memory_.ReadByte(BG1SC)); - bg1_chr_data = BGNBA(memory_.ReadByte(BG12NBA)); - break; + return test1 && test2; case 2: - // Render the second background layer - bg2_tilemap_info = BGSC(memory_.ReadByte(BG2SC)); - bg2_chr_data = BGNBA(memory_.ReadByte(BG12NBA)); - break; + return test1 != test2; case 3: - // Render the third background layer - bg3_tilemap_info = BGSC(memory_.ReadByte(BG3SC)); - bg3_chr_data = BGNBA(memory_.ReadByte(BG34NBA)); - break; - case 4: - // Render the fourth background layer - bg4_tilemap_info = BGSC(memory_.ReadByte(BG4SC)); - bg4_chr_data = BGNBA(memory_.ReadByte(BG34NBA)); - break; - default: - // Invalid layer, do nothing - break; + return test1 == test2; + } + return false; +} + +void Ppu::HandleOPT(int layer, int* lx, int* ly) { + int x = *lx; + int y = *ly; + int column = 0; + if (mode == 6) { + column = ((x - (x & 0xf)) - ((bg_layer_[layer].hScroll * 2) & 0xfff0)) >> 4; + } else { + column = ((x - (x & 0x7)) - (bg_layer_[layer].hScroll & 0xfff8)) >> 3; + } + if (column > 0) { + // fetch offset values from layer 3 tilemap + int valid = layer == 0 ? 0x2000 : 0x4000; + uint16_t hOffset = GetOffsetValue(column - 1, 0); + uint16_t vOffset = 0; + if (mode == 4) { + if (hOffset & 0x8000) { + vOffset = hOffset; + hOffset = 0; + } + } else { + vOffset = GetOffsetValue(column - 1, 1); + } + if (mode == 6) { + // TODO: not sure if correct + if (hOffset & valid) + *lx = (((hOffset & 0x3f8) + (column * 8)) * 2) | (x & 0xf); + } else { + if (hOffset & valid) *lx = ((hOffset & 0x3f8) + (column * 8)) | (x & 0x7); + } + // TODO: not sure if correct for interlace + if (vOffset & valid) + *ly = (vOffset & 0x3ff) + (y - bg_layer_[layer].vScroll); } } -void Ppu::RenderSprites() {} +uint16_t Ppu::GetOffsetValue(int col, int row) { + int x = col * 8 + bg_layer_[2].hScroll; + int y = row * 8 + bg_layer_[2].vScroll; + int tileBits = bg_layer_[2].bigTiles ? 4 : 3; + int tileHighBit = bg_layer_[2].bigTiles ? 0x200 : 0x100; + uint16_t tilemapAdr = + bg_layer_[2].tilemapAdr + + (((y >> tileBits) & 0x1f) << 5 | ((x >> tileBits) & 0x1f)); + if ((x & tileHighBit) && bg_layer_[2].tilemapWider) tilemapAdr += 0x400; + if ((y & tileHighBit) && bg_layer_[2].tilemapHigher) + tilemapAdr += bg_layer_[2].tilemapWider ? 0x800 : 0x400; + return vram[tilemapAdr & 0x7fff]; +} -void Ppu::UpdatePaletteData() {} +int Ppu::GetPixelForBgLayer(int x, int y, int layer, bool priority) { + // figure out address of tilemap word and read it + bool wideTiles = bg_layer_[layer].bigTiles || mode == 5 || mode == 6; + int tileBitsX = wideTiles ? 4 : 3; + int tileHighBitX = wideTiles ? 0x200 : 0x100; + int tileBitsY = bg_layer_[layer].bigTiles ? 4 : 3; + int tileHighBitY = bg_layer_[layer].bigTiles ? 0x200 : 0x100; + uint16_t tilemapAdr = + bg_layer_[layer].tilemapAdr + + (((y >> tileBitsY) & 0x1f) << 5 | ((x >> tileBitsX) & 0x1f)); + if ((x & tileHighBitX) && bg_layer_[layer].tilemapWider) tilemapAdr += 0x400; + if ((y & tileHighBitY) && bg_layer_[layer].tilemapHigher) + tilemapAdr += bg_layer_[layer].tilemapWider ? 0x800 : 0x400; + uint16_t tile = vram[tilemapAdr & 0x7fff]; + // check priority, get palette + if (((bool)(tile & 0x2000)) != priority) return 0; // wrong priority + int paletteNum = (tile & 0x1c00) >> 10; + // figure out position within tile + int row = (tile & 0x8000) ? 7 - (y & 0x7) : (y & 0x7); + int col = (tile & 0x4000) ? (x & 0x7) : 7 - (x & 0x7); + int tileNum = tile & 0x3ff; + if (wideTiles) { + // if unflipped right half of tile, or flipped left half of tile + if (((bool)(x & 8)) ^ ((bool)(tile & 0x4000))) tileNum += 1; + } + if (bg_layer_[layer].bigTiles) { + // if unflipped bottom half of tile, or flipped upper half of tile + if (((bool)(y & 8)) ^ ((bool)(tile & 0x8000))) tileNum += 0x10; + } + // read tiledata, ajust palette for mode 0 + int bitDepth = kBitDepthsPerMode[mode][layer]; + if (mode == 0) paletteNum += 8 * layer; + // plane 1 (always) + int paletteSize = 4; + uint16_t plane1 = vram[(bg_layer_[layer].tileAdr + + ((tileNum & 0x3ff) * 4 * bitDepth) + row) & + 0x7fff]; + int pixel = (plane1 >> col) & 1; + pixel |= ((plane1 >> (8 + col)) & 1) << 1; + // plane 2 (for 4bpp, 8bpp) + if (bitDepth > 2) { + paletteSize = 16; + uint16_t plane2 = vram[(bg_layer_[layer].tileAdr + + ((tileNum & 0x3ff) * 4 * bitDepth) + 8 + row) & + 0x7fff]; + pixel |= ((plane2 >> col) & 1) << 2; + pixel |= ((plane2 >> (8 + col)) & 1) << 3; + } + // plane 3 & 4 (for 8bpp) + if (bitDepth > 4) { + paletteSize = 256; + uint16_t plane3 = vram[(bg_layer_[layer].tileAdr + + ((tileNum & 0x3ff) * 4 * bitDepth) + 16 + row) & + 0x7fff]; + pixel |= ((plane3 >> col) & 1) << 4; + pixel |= ((plane3 >> (8 + col)) & 1) << 5; + uint16_t plane4 = vram[(bg_layer_[layer].tileAdr + + ((tileNum & 0x3ff) * 4 * bitDepth) + 24 + row) & + 0x7fff]; + pixel |= ((plane4 >> col) & 1) << 6; + pixel |= ((plane4 >> (8 + col)) & 1) << 7; + } + // return cgram index, or 0 if transparent, palette number in bits 10-8 for + // 8-color layers + return pixel == 0 ? 0 : paletteSize * paletteNum + pixel; +} -void Ppu::ApplyEffects() {} +void Ppu::EvaluateSprites(int line) { + // TODO: rectangular sprites, wierdness with sprites at -256 + uint8_t index = obj_priority_ ? (oam_adr_ & 0xfe) : 0; + int spritesFound = 0; + int tilesFound = 0; + uint8_t foundSprites[32] = {}; + // iterate over oam to find sprites in range + for (int i = 0; i < 128; i++) { + uint8_t y = oam[index] >> 8; + // check if the sprite is on this line and get the sprite size + uint8_t row = line - y; + int spriteSize = + kSpriteSizes[obj_size_] + [(high_oam_[index >> 3] >> ((index & 7) + 1)) & 1]; + int spriteHeight = obj_interlace_ ? spriteSize / 2 : spriteSize; + if (row < spriteHeight) { + // in y-range, get the x location, using the high bit as well + int x = oam[index] & 0xff; + x |= ((high_oam_[index >> 3] >> (index & 7)) & 1) << 8; + if (x > 255) x -= 512; + // if in x-range, record + if (x > -spriteSize) { + // break if we found 32 sprites already + spritesFound++; + if (spritesFound > 32) { + range_over_ = true; + spritesFound = 32; + break; + } + foundSprites[spritesFound - 1] = index; + } + } + index += 2; + } + // iterate over found sprites backwards to fetch max 34 tile slivers + for (int i = spritesFound; i > 0; i--) { + index = foundSprites[i - 1]; + uint8_t y = oam[index] >> 8; + uint8_t row = line - y; + int spriteSize = + kSpriteSizes[obj_size_] + [(high_oam_[index >> 3] >> ((index & 7) + 1)) & 1]; + int x = oam[index] & 0xff; + x |= ((high_oam_[index >> 3] >> (index & 7)) & 1) << 8; + if (x > 255) x -= 512; + if (x > -spriteSize) { + // update row according to obj-interlace + if (obj_interlace_) row = row * 2 + (even_frame ? 0 : 1); + // get some data for the sprite and y-flip row if needed + int tile = oam[index + 1] & 0xff; + int palette = (oam[index + 1] & 0xe00) >> 9; + bool hFlipped = oam[index + 1] & 0x4000; + if (oam[index + 1] & 0x8000) row = spriteSize - 1 - row; + // fetch all tiles in x-range + for (int col = 0; col < spriteSize; col += 8) { + if (col + x > -8 && col + x < 256) { + // break if we found > 34 8*1 slivers already + tilesFound++; + if (tilesFound > 34) { + time_over_ = true; + break; + } + // figure out which tile this uses, looping within 16x16 pages, and + // get it's data + int usedCol = hFlipped ? spriteSize - 1 - col : col; + uint8_t usedTile = (((tile >> 4) + (row / 8)) << 4) | + (((tile & 0xf) + (usedCol / 8)) & 0xf); + uint16_t objAdr = + (oam[index + 1] & 0x100) ? obj_tile_adr2_ : obj_tile_adr1_; + uint16_t plane1 = + vram[(objAdr + usedTile * 16 + (row & 0x7)) & 0x7fff]; + uint16_t plane2 = + vram[(objAdr + usedTile * 16 + 8 + (row & 0x7)) & 0x7fff]; + // go over each pixel + for (int px = 0; px < 8; px++) { + int shift = hFlipped ? px : 7 - px; + int pixel = (plane1 >> shift) & 1; + pixel |= ((plane1 >> (8 + shift)) & 1) << 1; + pixel |= ((plane2 >> shift) & 1) << 2; + pixel |= ((plane2 >> (8 + shift)) & 1) << 3; + // draw it in the buffer if there is a pixel here + int screenCol = col + x + px; + if (pixel > 0 && screenCol >= 0 && screenCol < 256) { + obj_pixel_buffer_[screenCol] = 0x80 + 16 * palette + pixel; + obj_priority_buffer_[screenCol] = (oam[index + 1] & 0x3000) >> 12; + } + } + } + } + if (tilesFound > 34) + break; // break out of sprite-loop if max tiles found + } + } +} -void Ppu::ComposeLayers() {} +void Ppu::CalculateMode7Starts(int y) { + // expand 13-bit values to signed values + int hScroll = ((int16_t)(m7matrix[6] << 3)) >> 3; + int vScroll = ((int16_t)(m7matrix[7] << 3)) >> 3; + int xCenter = ((int16_t)(m7matrix[4] << 3)) >> 3; + int yCenter = ((int16_t)(m7matrix[5] << 3)) >> 3; + // do calculation + int clippedH = hScroll - xCenter; + int clippedV = vScroll - yCenter; + clippedH = (clippedH & 0x2000) ? (clippedH | ~1023) : (clippedH & 1023); + clippedV = (clippedV & 0x2000) ? (clippedV | ~1023) : (clippedV & 1023); + if (bg_layer_[0].mosaicEnabled && mosaic_size_ > 1) { + y -= (y - mosaic_startline_) % mosaic_size_; + } + uint8_t ry = m7yFlip ? 255 - y : y; + m7startX = (((m7matrix[0] * clippedH) & ~63) + ((m7matrix[1] * ry) & ~63) + + ((m7matrix[1] * clippedV) & ~63) + (xCenter << 8)); + m7startY = (((m7matrix[2] * clippedH) & ~63) + ((m7matrix[3] * ry) & ~63) + + ((m7matrix[3] * clippedV) & ~63) + (yCenter << 8)); +} -void Ppu::DisplayFrameBuffer() { - if (!screen_->is_active()) { - screen_->Create(256, 240, 24, frame_buffer_); - rom()->RenderBitmap(screen_.get()); +void Ppu::HandleVblank() { + // called either right after CheckOverscan at (0,225), or at (0,240) + if (!forced_blank_) { + oam_adr_ = oam_adr_written_; + oam_in_high_ = oam_in_high_written_; + oam_second_write_ = false; + } + frame_interlace = interlace; // set if we have a interlaced frame +} + +uint8_t Ppu::Read(uint8_t adr, bool latch) { + switch (adr) { + case 0x04: + case 0x14: + case 0x24: + case 0x05: + case 0x15: + case 0x25: + case 0x06: + case 0x16: + case 0x26: + case 0x08: + case 0x18: + case 0x28: + case 0x09: + case 0x19: + case 0x29: + case 0x0a: + case 0x1a: + case 0x2a: { + return ppu1_open_bus_; + } + case 0x34: + case 0x35: + case 0x36: { + int result = m7matrix[0] * (m7matrix[1] >> 8); + ppu1_open_bus_ = (result >> (8 * (adr - 0x34))) & 0xff; + return ppu1_open_bus_; + } + case 0x37: { + // TODO: only when ppulatch is set + if (latch) { + LatchHV(); + } + return memory_.open_bus(); + } + case 0x38: { + uint8_t ret = 0; + if (oam_in_high_) { + ret = high_oam_[((oam_adr_ & 0xf) << 1) | oam_second_write_]; + if (oam_second_write_) { + oam_adr_++; + if (oam_adr_ == 0) oam_in_high_ = false; + } + } else { + if (!oam_second_write_) { + ret = oam[oam_adr_] & 0xff; + } else { + ret = oam[oam_adr_++] >> 8; + if (oam_adr_ == 0) oam_in_high_ = true; + } + } + oam_second_write_ = !oam_second_write_; + ppu1_open_bus_ = ret; + return ret; + } + case 0x39: { + uint16_t val = vram_read_buffer_; + if (!vram_increment_on_high_) { + vram_read_buffer_ = vram[GetVramRemap() & 0x7fff]; + vram_pointer += vram_increment_; + } + ppu1_open_bus_ = val & 0xff; + return val & 0xff; + } + case 0x3a: { + uint16_t val = vram_read_buffer_; + if (vram_increment_on_high_) { + vram_read_buffer_ = vram[GetVramRemap() & 0x7fff]; + vram_pointer += vram_increment_; + } + ppu1_open_bus_ = val >> 8; + return val >> 8; + } + case 0x3b: { + uint8_t ret = 0; + if (!cgram_second_write_) { + ret = cgram[cgram_pointer_] & 0xff; + } else { + ret = ((cgram[cgram_pointer_++] >> 8) & 0x7f) | (ppu2_open_bus_ & 0x80); + } + cgram_second_write_ = !cgram_second_write_; + ppu2_open_bus_ = ret; + return ret; + } + case 0x3c: { + uint8_t val = 0; + if (h_count_second_) { + val = ((h_count_ >> 8) & 1) | (ppu2_open_bus_ & 0xfe); + } else { + val = h_count_ & 0xff; + } + h_count_second_ = !h_count_second_; + ppu2_open_bus_ = val; + return val; + } + case 0x3d: { + uint8_t val = 0; + if (v_count_second_) { + val = ((v_count_ >> 8) & 1) | (ppu2_open_bus_ & 0xfe); + } else { + val = v_count_ & 0xff; + } + v_count_second_ = !v_count_second_; + ppu2_open_bus_ = val; + return val; + } + case 0x3e: { + uint8_t val = 0x1; // ppu1 version (4 bit) + val |= ppu1_open_bus_ & 0x10; + val |= range_over_ << 6; + val |= time_over_ << 7; + ppu1_open_bus_ = val; + return val; + } + case 0x3f: { + uint8_t val = 0x3; // ppu2 version (4 bit) + val |= memory_.pal_timing() << 4; // ntsc/pal + val |= ppu2_open_bus_ & 0x20; + val |= counters_latched_ << 6; + val |= even_frame << 7; + if (latch) { + counters_latched_ = false; + h_count_second_ = false; + v_count_second_ = false; + } + ppu2_open_bus_ = val; + return val; + } + default: { + return memory_.open_bus(); + } + } +} + +void Ppu::Write(uint8_t adr, uint8_t val) { + switch (adr) { + case 0x00: { + // TODO: oam address reset when written on first line of vblank, (and when + // forced blank is disabled?) + brightness = val & 0xf; + forced_blank_ = val & 0x80; + break; + } + case 0x01: { + obj_size_ = val >> 5; + obj_tile_adr1_ = (val & 7) << 13; + obj_tile_adr2_ = obj_tile_adr1_ + (((val & 0x18) + 8) << 9); + break; + } + case 0x02: { + oam_adr_ = val; + oam_adr_written_ = oam_adr_; + oam_in_high_ = oam_in_high_written_; + oam_second_write_ = false; + break; + } + case 0x03: { + obj_priority_ = val & 0x80; + oam_in_high_ = val & 1; + oam_in_high_written_ = oam_in_high_; + oam_adr_ = oam_adr_written_; + oam_second_write_ = false; + break; + } + case 0x04: { + if (oam_in_high_) { + high_oam_[((oam_adr_ & 0xf) << 1) | oam_second_write_] = val; + if (oam_second_write_) { + oam_adr_++; + if (oam_adr_ == 0) oam_in_high_ = false; + } + } else { + if (!oam_second_write_) { + oam_buffer_ = val; + } else { + oam[oam_adr_++] = (val << 8) | oam_buffer_; + if (oam_adr_ == 0) oam_in_high_ = true; + } + } + oam_second_write_ = !oam_second_write_; + break; + } + case 0x05: { + mode = val & 0x7; + bg3priority = val & 0x8; + bg_layer_[0].bigTiles = val & 0x10; + bg_layer_[1].bigTiles = val & 0x20; + bg_layer_[2].bigTiles = val & 0x40; + bg_layer_[3].bigTiles = val & 0x80; + break; + } + case 0x06: { + // TODO: mosaic line reset specifics + bg_layer_[0].mosaicEnabled = val & 0x1; + bg_layer_[1].mosaicEnabled = val & 0x2; + bg_layer_[2].mosaicEnabled = val & 0x4; + bg_layer_[3].mosaicEnabled = val & 0x8; + mosaic_size_ = (val >> 4) + 1; + mosaic_startline_ = memory_.v_pos(); + break; + } + case 0x07: + case 0x08: + case 0x09: + case 0x0a: { + bg_layer_[adr - 7].tilemapWider = val & 0x1; + bg_layer_[adr - 7].tilemapHigher = val & 0x2; + bg_layer_[adr - 7].tilemapAdr = (val & 0xfc) << 8; + break; + } + case 0x0b: { + bg_layer_[0].tileAdr = (val & 0xf) << 12; + bg_layer_[1].tileAdr = (val & 0xf0) << 8; + break; + } + case 0x0c: { + bg_layer_[2].tileAdr = (val & 0xf) << 12; + bg_layer_[3].tileAdr = (val & 0xf0) << 8; + break; + } + case 0x0d: { + m7matrix[6] = ((val << 8) | m7prev) & 0x1fff; + m7prev = val; + // fallthrough to normal layer BG-HOFS + } + case 0x0f: + case 0x11: + case 0x13: { + bg_layer_[(adr - 0xd) / 2].hScroll = + ((val << 8) | (scroll_prev_ & 0xf8) | (scroll_prev2_ & 0x7)) & 0x3ff; + scroll_prev_ = val; + scroll_prev2_ = val; + break; + } + case 0x0e: { + m7matrix[7] = ((val << 8) | m7prev) & 0x1fff; + m7prev = val; + // fallthrough to normal layer BG-VOFS + } + case 0x10: + case 0x12: + case 0x14: { + bg_layer_[(adr - 0xe) / 2].vScroll = ((val << 8) | scroll_prev_) & 0x3ff; + scroll_prev_ = val; + break; + } + case 0x15: { + if ((val & 3) == 0) { + vram_increment_ = 1; + } else if ((val & 3) == 1) { + vram_increment_ = 32; + } else { + vram_increment_ = 128; + } + vram_remap_mode_ = (val & 0xc) >> 2; + vram_increment_on_high_ = val & 0x80; + break; + } + case 0x16: { + vram_pointer = (vram_pointer & 0xff00) | val; + vram_read_buffer_ = vram[GetVramRemap() & 0x7fff]; + break; + } + case 0x17: { + vram_pointer = (vram_pointer & 0x00ff) | (val << 8); + vram_read_buffer_ = vram[GetVramRemap() & 0x7fff]; + break; + } + case 0x18: { + // TODO: vram access during rendering (also cgram and oam) + uint16_t vramAdr = GetVramRemap(); + vram[vramAdr & 0x7fff] = (vram[vramAdr & 0x7fff] & 0xff00) | val; + if (!vram_increment_on_high_) vram_pointer += vram_increment_; + break; + } + case 0x19: { + uint16_t vramAdr = GetVramRemap(); + vram[vramAdr & 0x7fff] = (vram[vramAdr & 0x7fff] & 0x00ff) | (val << 8); + if (vram_increment_on_high_) vram_pointer += vram_increment_; + break; + } + case 0x1a: { + m7largeField = val & 0x80; + m7charFill = val & 0x40; + m7yFlip = val & 0x2; + m7xFlip = val & 0x1; + break; + } + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: { + m7matrix[adr - 0x1b] = (val << 8) | m7prev; + m7prev = val; + break; + } + case 0x1f: + case 0x20: { + m7matrix[adr - 0x1b] = ((val << 8) | m7prev) & 0x1fff; + m7prev = val; + break; + } + case 0x21: { + cgram_pointer_ = val; + cgram_second_write_ = false; + break; + } + case 0x22: { + if (!cgram_second_write_) { + cgram_buffer_ = val; + } else { + cgram[cgram_pointer_++] = (val << 8) | cgram_buffer_; + } + cgram_second_write_ = !cgram_second_write_; + break; + } + case 0x23: + case 0x24: + case 0x25: { + windowLayer[(adr - 0x23) * 2].window1inversed = val & 0x1; + windowLayer[(adr - 0x23) * 2].window1enabled = val & 0x2; + windowLayer[(adr - 0x23) * 2].window2inversed = val & 0x4; + windowLayer[(adr - 0x23) * 2].window2enabled = val & 0x8; + windowLayer[(adr - 0x23) * 2 + 1].window1inversed = val & 0x10; + windowLayer[(adr - 0x23) * 2 + 1].window1enabled = val & 0x20; + windowLayer[(adr - 0x23) * 2 + 1].window2inversed = val & 0x40; + windowLayer[(adr - 0x23) * 2 + 1].window2enabled = val & 0x80; + break; + } + case 0x26: { + window1left = val; + break; + } + case 0x27: { + window1right = val; + break; + } + case 0x28: { + window2left = val; + break; + } + case 0x29: { + window2right = val; + break; + } + case 0x2a: { + windowLayer[0].maskLogic = val & 0x3; + windowLayer[1].maskLogic = (val >> 2) & 0x3; + windowLayer[2].maskLogic = (val >> 4) & 0x3; + windowLayer[3].maskLogic = (val >> 6) & 0x3; + break; + } + case 0x2b: { + windowLayer[4].maskLogic = val & 0x3; + windowLayer[5].maskLogic = (val >> 2) & 0x3; + break; + } + case 0x2c: { + layer_[0].mainScreenEnabled = val & 0x1; + layer_[1].mainScreenEnabled = val & 0x2; + layer_[2].mainScreenEnabled = val & 0x4; + layer_[3].mainScreenEnabled = val & 0x8; + layer_[4].mainScreenEnabled = val & 0x10; + break; + } + case 0x2d: { + layer_[0].subScreenEnabled = val & 0x1; + layer_[1].subScreenEnabled = val & 0x2; + layer_[2].subScreenEnabled = val & 0x4; + layer_[3].subScreenEnabled = val & 0x8; + layer_[4].subScreenEnabled = val & 0x10; + break; + } + case 0x2e: { + layer_[0].mainScreenWindowed = val & 0x1; + layer_[1].mainScreenWindowed = val & 0x2; + layer_[2].mainScreenWindowed = val & 0x4; + layer_[3].mainScreenWindowed = val & 0x8; + layer_[4].mainScreenWindowed = val & 0x10; + break; + } + case 0x2f: { + layer_[0].subScreenWindowed = val & 0x1; + layer_[1].subScreenWindowed = val & 0x2; + layer_[2].subScreenWindowed = val & 0x4; + layer_[3].subScreenWindowed = val & 0x8; + layer_[4].subScreenWindowed = val & 0x10; + break; + } + case 0x30: { + direct_color_ = val & 0x1; + add_subscreen_ = val & 0x2; + prevent_math_mode_ = (val & 0x30) >> 4; + clip_mode_ = (val & 0xc0) >> 6; + break; + } + case 0x31: { + subtract_color_ = val & 0x80; + half_color_ = val & 0x40; + for (int i = 0; i < 6; i++) { + math_enabled_array_[i] = val & (1 << i); + } + break; + } + case 0x32: { + if (val & 0x80) fixed_color_b_ = val & 0x1f; + if (val & 0x40) fixed_color_g_ = val & 0x1f; + if (val & 0x20) fixed_color_r_ = val & 0x1f; + break; + } + case 0x33: { + interlace = val & 0x1; + obj_interlace_ = val & 0x2; + overscan_ = val & 0x4; + pseudo_hires_ = val & 0x8; + m7extBg = val & 0x40; + break; + } + default: { + break; + } + } +} + +uint16_t Ppu::GetVramRemap() { + uint16_t adr = vram_pointer; + switch (vram_remap_mode_) { + case 0: + return adr; + case 1: + return (adr & 0xff00) | ((adr & 0xe0) >> 5) | ((adr & 0x1f) << 3); + case 2: + return (adr & 0xfe00) | ((adr & 0x1c0) >> 6) | ((adr & 0x3f) << 3); + case 3: + return (adr & 0xfc00) | ((adr & 0x380) >> 7) | ((adr & 0x7f) << 3); + } + return adr; +} + +void Ppu::PutPixels(uint8_t* pixels) { + for (int y = 0; y < (frame_overscan_ ? 239 : 224); y++) { + int dest = y * 2 + (frame_overscan_ ? 2 : 16); + int y1 = y, y2 = y + 239; + if (!frame_interlace) { + y1 = y + (even_frame ? 0 : 239); + y2 = y1; + } + memcpy(pixels + (dest * 2048), &pixelBuffer[y1 * 2048], 2048); + memcpy(pixels + ((dest + 1) * 2048), &pixelBuffer[y2 * 2048], 2048); + } + // clear top 2 lines, and following 14 and last 16 lines if not overscanning + memset(pixels, 0, 2048 * 2); + if (!frame_overscan_) { + memset(pixels + (2 * 2048), 0, 2048 * 14); + memset(pixels + (464 * 2048), 0, 2048 * 16); } } diff --git a/src/app/emu/video/ppu.h b/src/app/emu/video/ppu.h index 68f9bbb3..65418001 100644 --- a/src/app/emu/video/ppu.h +++ b/src/app/emu/video/ppu.h @@ -25,39 +25,6 @@ class PpuInterface { // Memory Interactions virtual void Write(uint16_t address, uint8_t data) = 0; virtual uint8_t Read(uint16_t address) const = 0; - - // Rendering Controls - virtual void RenderFrame() = 0; - virtual void RenderScanline() = 0; - virtual void RenderBackground(int layer) = 0; - virtual void RenderSprites() = 0; - - // State Management - virtual void Init() = 0; - virtual void Reset() = 0; - virtual void Update(double deltaTime) = 0; - virtual void UpdateClock(double deltaTime) = 0; - virtual void UpdateInternalState(int cycles) = 0; - - // Data Access - virtual const std::vector& GetFrameBuffer() const = 0; - virtual std::shared_ptr GetScreen() const = 0; - - // Mode and Setting Updates - virtual void UpdateModeSettings() = 0; - virtual void UpdateTileData() = 0; - virtual void UpdateTileMapData() = 0; - virtual void UpdatePaletteData() = 0; - - // Layer Composition - virtual void ApplyEffects() = 0; - virtual void ComposeLayers() = 0; - - // Display Output - virtual void DisplayFrameBuffer() = 0; - - // Notification (Observer pattern) - virtual void Notify(uint32_t address, uint8_t data) = 0; }; // Enum representing different background modes @@ -247,6 +214,32 @@ struct Tile { uint8_t priority; // Priority of this tile }; +typedef struct Layer { + bool mainScreenEnabled; + bool subScreenEnabled; + bool mainScreenWindowed; + bool subScreenWindowed; +} Layer; + +typedef struct BgLayer { + uint16_t hScroll; + uint16_t vScroll; + bool tilemapWider; + bool tilemapHigher; + uint16_t tilemapAdr; + uint16_t tileAdr; + bool bigTiles; + bool mosaicEnabled; +} BgLayer; + +typedef struct WindowLayer { + bool window1enabled; + bool window2enabled; + bool window1inversed; + bool window2inversed; + uint8_t maskLogic; +} WindowLayer; + struct BackgroundLayer { enum class Size { SIZE_32x32, SIZE_64x32, SIZE_32x64, SIZE_64x64 }; @@ -263,72 +256,196 @@ struct BackgroundLayer { bool enabled; // Whether the background layer is enabled }; -const int kPpuClockSpeed = 5369318; // 5.369318 MHz - -class Ppu : public Observer, public SharedRom { +class Ppu : public SharedRom { public: // Initializes the PPU with the necessary resources and dependencies Ppu(memory::Memory& memory, Clock& clock) : memory_(memory), clock_(clock) {} // Initialize the frame buffer void Init() { - clock_.SetFrequency(kPpuClockSpeed); frame_buffer_.resize(256 * 240, 0); - screen_ = std::make_shared(256, 240, 8, 0x100); - screen_->set_active(false); + pixelOutputFormat = 1; } // Resets the PPU to its initial state - void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); } + void Reset(); // Runs the PPU for one frame. void Update(); void UpdateClock(double delta_time) { clock_.UpdateClock(delta_time); } - void UpdateInternalState(int cycles); - // Renders a scanline of the screen - void RenderScanline(); + void HandleFrameStart(); + void RunLine(int line); + void HandlePixel(int x, int y); - void Notify(uint32_t address, uint8_t data) override; + void LatchHV() { + h_count_ = memory_.h_pos() / 4; + v_count_ = memory_.v_pos(); + counters_latched_ = true; + } + + int GetPixel(int x, int y, bool sub, int* r, int* g, int* b); + + void EvaluateSprites(int line); + + void CalculateMode7Starts(int y); + + bool GetWindowState(int layer, int x); + + // if we are overscanning this frame (determined at 0,225) + bool frame_overscan_ = false; + bool overscan_ = false; + + // settings + bool forced_blank_; + uint8_t brightness; + uint8_t mode; + bool bg3priority; + bool even_frame; + bool pseudo_hires_; + bool interlace; + bool frame_interlace; // if we are interlacing this frame (determined at + // start vblank) + bool direct_color_; + + bool CheckOverscan() { + frame_overscan_ = overscan_; + return frame_overscan_; + } + + void HandleVblank(); + void HandleOPT(int layer, int* lx, int* ly); + uint16_t GetOffsetValue(int col, int row); + int GetPixelForBgLayer(int x, int y, int layer, bool priority); + + uint8_t Read(uint8_t adr, bool latch); + void Write(uint8_t adr, uint8_t val); + + uint16_t GetVramRemap(); + + void PutPixels(uint8_t* pixel_data); // Returns the pixel data for the current frame const std::vector& GetFrameBuffer() const { return frame_buffer_; } - auto GetScreen() const { return screen_; } - private: - // Updates internal state based on PPU register settings - void UpdateModeSettings(); + int GetPixelForMode7(int x, int layer, bool priority); - // Internal methods to handle PPU rendering and operations - void UpdateTileData(); + const int cyclesPerScanline = 341; // SNES PPU has 341 cycles per scanline + const int totalScanlines = 262; // SNES PPU has 262 scanlines per frame + const int visibleScanlines = 224; // SNES PPU renders 224 visible scanlines - // Fetches the tile map data from memory and stores it in an internal buffer - void UpdateTileMapData(); + bool enable_forced_blanking_ = false; - // Renders a background layer - void RenderBackground(int layer); + int cycle_count_ = 0; + int current_scanline_ = 0; - // Renders sprites (also known as objects) - void RenderSprites(); + // vram access + uint16_t vram[0x8000]; + uint16_t vram_pointer; + bool vram_increment_on_high_; + uint16_t vram_increment_; + uint8_t vram_remap_mode_; + uint16_t vram_read_buffer_; - // Fetches the palette data from CGRAM and stores it in an internal buffer - void UpdatePaletteData(); + // cgram access + uint16_t cgram[0x100]; + uint8_t cgram_pointer_; + bool cgram_second_write_; + uint8_t cgram_buffer_; - // Applies effects to the layers based on the current mode and register - void ApplyEffects(); + // oam access + uint16_t oam[0x100]; + uint8_t high_oam_[0x20]; + uint8_t oam_adr_; + uint8_t oam_adr_written_; + bool oam_in_high_; + bool oam_in_high_written_; + bool oam_second_write_; + uint8_t oam_buffer_; - // Combines the layers into a single image and stores it in the frame buffer - void ComposeLayers(); + // Objects / Sprites + bool time_over_ = false; + bool range_over_ = false; + bool obj_interlace_; + bool obj_priority_; + uint16_t obj_tile_adr1_; + uint16_t obj_tile_adr2_; + uint8_t obj_size_; + std::array obj_pixel_buffer_; + std::array obj_priority_buffer_; - // Sends the frame buffer to the display hardware (e.g., SDL2) - void DisplayFrameBuffer(); + // Color Math + uint8_t clip_mode_ = 0; + uint8_t prevent_math_mode_ = 0; + bool math_enabled_array_[6] = {false, false, false, false, false, false}; + bool add_subscreen_ = false; + bool subtract_color_; + bool half_color_; + uint8_t fixed_color_r_; + uint8_t fixed_color_g_; + uint8_t fixed_color_b_; + + // layers + Layer layer_[5]; + + // mode 7 + int16_t m7matrix[8]; // a, b, c, d, x, y, h, v + uint8_t m7prev; + bool m7largeField; + bool m7charFill; + bool m7xFlip; + bool m7yFlip; + bool m7extBg; + + // mode 7 internal + int32_t m7startX; + int32_t m7startY; + + // windows + WindowLayer windowLayer[6]; + uint8_t window1left; + uint8_t window1right; + uint8_t window2left; + uint8_t window2right; + + // Background Layers + std::array bg_layers_; + uint8_t mosaic_startline_ = 1; + + BgLayer bg_layer_[4]; + uint8_t scroll_prev_; + uint8_t scroll_prev2_; + uint8_t mosaic_size_; + + // pixel buffer (xbgr) + // times 2 for even and odd frame + uint8_t pixelBuffer[512 * 4 * 239 * 2]; + uint8_t pixelOutputFormat = 0; + + // latching + uint16_t h_count_; + uint16_t v_count_; + bool h_count_second_; + bool v_count_second_; + bool counters_latched_; + uint8_t ppu1_open_bus_; + uint8_t ppu2_open_bus_; + + uint16_t tile_data_size_; + uint16_t vram_base_address_; + uint16_t tilemap_base_address_; + uint16_t screen_brightness_ = 0x00; - // =========================================================== - // Member variables to store internal PPU state and resources Memory& memory_; Clock& clock_; + Tilemap tilemap_; + BackgroundMode bg_mode_; + std::vector sprites_; + std::vector tile_data_; + std::vector frame_buffer_; + // PPU registers OAMSize oam_size_; OAMAddress oam_address_; @@ -337,55 +454,6 @@ class Ppu : public Observer, public SharedRom { std::array bgnba_; std::array bghofs_; std::array bgvofs_; - struct VMAIN vmain_; - struct VMADDL vmaddl_; - struct VMADDH vmaddh_; - // struct VMDATAL vmdatal_; - // struct VMDATAH vmdatah_; - struct M7SEL m7sel_; - struct M7A m7a_; - struct M7B m7b_; - struct M7C m7c_; - struct M7D m7d_; - struct M7X m7x_; - struct M7Y m7y_; - struct CGADD cgadd_; - struct CGDATA cgdata_; - struct W12SEL w12sel_; - struct W34SEL w34sel_; - struct WOBJSEL wobjsel_; - struct WH0 wh0_; - struct WH1 wh1_; - struct WH2 wh2_; - struct WH3 wh3_; - struct WBGLOG wbglog_; - struct WOBJLOG wobjlog_; - struct TM tm_; - struct TS ts_; - struct TSW tsw_; - struct TMW tmw_; - struct SETINI setini_; - - Tilemap tilemap_; - BackgroundMode bg_mode_; - std::array bg_layers_; - std::vector sprites_; - std::vector tile_data_; - std::vector frame_buffer_; - std::shared_ptr screen_; - - uint16_t tile_data_size_; - uint16_t vram_base_address_; - uint16_t tilemap_base_address_; - uint16_t screen_brightness_ = 0x00; - - bool enable_forced_blanking_ = false; - - int cycle_count_ = 0; - int current_scanline_ = 0; - const int cyclesPerScanline = 341; // SNES PPU has 341 cycles per scanline - const int totalScanlines = 262; // SNES PPU has 262 scanlines per frame - const int visibleScanlines = 224; // SNES PPU renders 224 visible scanlines }; } // namespace video diff --git a/src/app/gfx/bitmap.cc b/src/app/gfx/bitmap.cc index e61d97b9..312e37da 100644 --- a/src/app/gfx/bitmap.cc +++ b/src/app/gfx/bitmap.cc @@ -46,6 +46,20 @@ void PngReadCallback(png_structp png_ptr, png_bytep outBytes, png_error(png_ptr, "Read error in PngReadCallback"); } } + +Uint32 GetSnesPixelFormat(int format) { + switch (format) { + case 0: + return SDL_PIXELFORMAT_INDEX8; + case 1: + return SNES_PIXELFORMAT_2BPP; + case 2: + return SNES_PIXELFORMAT_4BPP; + case 3: + return SNES_PIXELFORMAT_8BPP; + } + return SDL_PIXELFORMAT_INDEX8; +} } // namespace bool ConvertSurfaceToPNG(SDL_Surface *surface, std::vector &buffer) { @@ -184,24 +198,7 @@ void ConvertPngToSurface(const std::vector &png_data, } Bitmap::Bitmap(int width, int height, int depth, int data_size) { - Create(width, height, depth, data_size); -} - -// Reserves data to later draw to surface via pointer -void Bitmap::Create(int width, int height, int depth, int size) { - active_ = true; - width_ = width; - height_ = height; - depth_ = depth; - data_size_ = size; - data_.reserve(size); - pixel_data_ = data_.data(); - surface_ = std::unique_ptr( - SDL_CreateRGBSurfaceWithFormat(0, width, height, depth, - SDL_PIXELFORMAT_INDEX8), - SDL_Surface_Deleter()); - surface_->pixels = pixel_data_; - GrayscalePalette(surface_->format->palette); + Create(width, height, depth, Bytes(data_size, 0)); } void Bitmap::Create(int width, int height, int depth, const Bytes &data) { @@ -210,6 +207,7 @@ void Bitmap::Create(int width, int height, int depth, const Bytes &data) { height_ = height; depth_ = depth; data_ = data; + data_size_ = data.size(); pixel_data_ = data_.data(); surface_ = std::unique_ptr( SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_, @@ -217,11 +215,36 @@ void Bitmap::Create(int width, int height, int depth, const Bytes &data) { SDL_Surface_Deleter()); surface_->pixels = pixel_data_; GrayscalePalette(surface_->format->palette); + active_ = true; +} + +void Bitmap::Create(int width, int height, int depth, int format, + const Bytes &data) { + active_ = true; + width_ = width; + height_ = height; + depth_ = depth; + data_ = data; + data_size_ = data.size(); + pixel_data_ = data_.data(); + surface_ = std::unique_ptr( + SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_, + GetSnesPixelFormat(format)), + SDL_Surface_Deleter()); + surface_->pixels = pixel_data_; + active_ = true; +} + +void Bitmap::Reformat(int format) { + surface_ = std::unique_ptr( + SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_, + GetSnesPixelFormat(format)), + SDL_Surface_Deleter()); + surface_->pixels = pixel_data_; + active_ = true; } -// Creates the texture that will be displayed to the screen. void Bitmap::CreateTexture(SDL_Renderer *renderer) { - // Ensure width and height are non-zero if (width_ <= 0 || height_ <= 0) { SDL_Log("Invalid texture dimensions: width=%d, height=%d\n", width_, height_); @@ -239,20 +262,16 @@ void Bitmap::CreateTexture(SDL_Renderer *renderer) { SDL_Surface *converted_surface = SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0); if (converted_surface) { - // Create texture from the converted surface converted_surface_ = std::unique_ptr( converted_surface, SDL_Surface_Deleter()); } else { - // Handle the error SDL_Log("SDL_ConvertSurfaceFormat failed: %s\n", SDL_GetError()); } SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels, &converted_surface_->pitch); - memcpy(texture_pixels, converted_surface_->pixels, converted_surface_->h * converted_surface_->pitch); - SDL_UnlockTexture(texture_.get()); } @@ -260,33 +279,24 @@ void Bitmap::UpdateTexture(SDL_Renderer *renderer, bool use_sdl_update) { SDL_Surface *converted_surface = SDL_ConvertSurfaceFormat(surface_.get(), SDL_PIXELFORMAT_ARGB8888, 0); if (converted_surface) { - // Create texture from the converted surface converted_surface_ = std::unique_ptr( converted_surface, SDL_Surface_Deleter()); } else { - // Handle the error SDL_Log("SDL_ConvertSurfaceFormat failed: %s\n", SDL_GetError()); } SDL_LockTexture(texture_.get(), nullptr, (void **)&texture_pixels, &converted_surface_->pitch); - - try { - if (use_sdl_update) { - SDL_UpdateTexture(texture_.get(), nullptr, converted_surface_->pixels, - converted_surface_->pitch); - } else { - memcpy(texture_pixels, converted_surface_->pixels, - converted_surface_->h * converted_surface_->pitch); - } - } catch (const std::exception &e) { - SDL_Log("Exception: %s\n", e.what()); + if (use_sdl_update) { + SDL_UpdateTexture(texture_.get(), nullptr, converted_surface_->pixels, + converted_surface_->pitch); + } else { + memcpy(texture_pixels, converted_surface_->pixels, + converted_surface_->h * converted_surface_->pitch); } - SDL_UnlockTexture(texture_.get()); } -// Creates the texture that will be displayed to the screen. void Bitmap::CreateTexture(std::shared_ptr renderer) { texture_ = std::shared_ptr{ SDL_CreateTextureFromSurface(renderer.get(), surface_.get()), @@ -294,8 +304,6 @@ void Bitmap::CreateTexture(std::shared_ptr renderer) { } void Bitmap::UpdateTexture(std::shared_ptr renderer) { - // SDL_DestroyTexture(texture_.get()); - // texture_ = nullptr; texture_ = std::shared_ptr{ SDL_CreateTextureFromSurface(renderer.get(), surface_.get()), SDL_Texture_Deleter{}}; @@ -345,17 +353,10 @@ absl::Status Bitmap::ApplyPalette(const SnesPalette &palette) { for (int i = 0; i < palette.size(); ++i) { ASSIGN_OR_RETURN(gfx::SnesColor pal_color, palette.GetColor(i)); - if (pal_color.is_transparent()) { - sdlPalette->colors[i].r = 0; - sdlPalette->colors[i].g = 0; - sdlPalette->colors[i].b = 0; - sdlPalette->colors[i].a = 0; - } else { - sdlPalette->colors[i].r = pal_color.rgb().x; - sdlPalette->colors[i].g = pal_color.rgb().y; - sdlPalette->colors[i].b = pal_color.rgb().z; - sdlPalette->colors[i].a = pal_color.rgb().w; - } + sdlPalette->colors[i].r = pal_color.rgb().x; + sdlPalette->colors[i].g = pal_color.rgb().y; + sdlPalette->colors[i].b = pal_color.rgb().z; + sdlPalette->colors[i].a = pal_color.rgb().w; } SDL_LockSurface(surface_.get()); @@ -421,25 +422,6 @@ void Bitmap::ApplyPalette(const std::vector &palette) { SDL_LockSurface(surface_.get()); } -void Bitmap::InitializeFromData(uint32_t width, uint32_t height, uint32_t depth, - const Bytes &data) { - active_ = true; - width_ = width; - height_ = height; - depth_ = depth; - data_ = data; - data_size_ = data.size(); - pixel_data_ = data_.data(); - - surface_ = std::unique_ptr( - SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_, - SDL_PIXELFORMAT_INDEX8), - SDL_Surface_Deleter()); - - surface_->pixels = pixel_data_; - GrayscalePalette(surface_->format->palette); -} - } // namespace gfx } // namespace app } // namespace yaze diff --git a/src/app/gfx/bitmap.h b/src/app/gfx/bitmap.h index 53fdfc89..fa8577d7 100644 --- a/src/app/gfx/bitmap.h +++ b/src/app/gfx/bitmap.h @@ -22,6 +22,25 @@ namespace app { */ namespace gfx { +constexpr Uint32 SNES_PIXELFORMAT_2BPP = SDL_DEFINE_PIXELFORMAT( + /*type=*/SDL_PIXELTYPE_INDEX8, /*order=*/0, + /*layouts=*/0, /*bits=*/2, /*bytes=*/1); + +constexpr Uint32 SNES_PIXELFORMAT_4BPP = SDL_DEFINE_PIXELFORMAT( + /*type=*/SDL_PIXELTYPE_INDEX8, /*order=*/0, + /*layouts=*/0, /*bits=*/4, /*bytes=*/1); + +constexpr Uint32 SNES_PIXELFORMAT_8BPP = SDL_DEFINE_PIXELFORMAT( + /*type=*/SDL_PIXELTYPE_INDEX8, /*order=*/0, + /*layouts=*/0, /*bits=*/8, /*bytes=*/1); + +// SDL_PIXELFORMAT_INDEX8 = +// SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1), + +constexpr int kFormat2bppIndexed = 1; +constexpr int kFormat4bppIndexed = 2; +constexpr int kFormat8bppIndexed = 3; + /** * @brief Convert SDL_Surface to PNG image data. */ @@ -48,7 +67,7 @@ class Bitmap { Bitmap(int width, int height, int depth, int data_size); Bitmap(int width, int height, int depth, const Bytes &data) : width_(width), height_(height), depth_(depth), data_(data) { - InitializeFromData(width, height, depth, data); + Create(width, height, depth, data); } Bitmap(int width, int height, int depth, const Bytes &data, const SnesPalette &palette) @@ -57,22 +76,19 @@ class Bitmap { depth_(depth), data_(data), palette_(palette) { - InitializeFromData(width, height, depth, data); - ApplyPalette(palette); + Create(width, height, depth, data); + if (!ApplyPalette(palette).ok()) { + std::cerr << "Error applying palette in bitmap constructor." << std::endl; + } } - /** - * @brief Creates a bitmap object and reserves space for graphical data. - */ - void Create(int width, int height, int depth, int data_size); - /** * @brief Creates a bitmap object with the provided graphical data. */ void Create(int width, int height, int depth, const Bytes &data); + void Create(int width, int height, int depth, int format, const Bytes &data); - void InitializeFromData(uint32_t width, uint32_t height, uint32_t depth, - const Bytes &data); + void Reformat(int format); /** * @brief Creates the underlying SDL_Texture to be displayed. @@ -191,23 +207,7 @@ class Bitmap { } void Cleanup() { - // Reset texture_ - if (texture_) { - texture_.reset(); - } - - // Reset surface_ and its pixel data - if (surface_) { - surface_->pixels = nullptr; - surface_.reset(); - } - - // Reset data_ - data_.clear(); - - // Reset other members if necessary active_ = false; - pixel_data_ = nullptr; width_ = 0; height_ = 0; depth_ = 0; @@ -222,6 +222,7 @@ class Bitmap { return surface_->format->palette; } auto palette() const { return palette_; } + auto mutable_palette() { return &palette_; } auto palette_size() const { return palette_.size(); } int width() const { return width_; } @@ -250,7 +251,6 @@ class Bitmap { void operator()(SDL_Texture *p) const { if (p != nullptr) { SDL_DestroyTexture(p); - p = nullptr; } } }; @@ -258,9 +258,7 @@ class Bitmap { struct SDL_Surface_Deleter { void operator()(SDL_Surface *p) const { if (p != nullptr) { - p->pixels = nullptr; SDL_FreeSurface(p); - p = nullptr; } } }; @@ -293,15 +291,21 @@ using BitmapTable = std::unordered_map; */ class BitmapManager { private: - std::unordered_map> bitmap_cache_; + std::unordered_map bitmap_cache_; public: void LoadBitmap(int id, const Bytes &data, int width, int height, int depth) { - bitmap_cache_[id] = - std::make_shared(width, height, depth, data); + bitmap_cache_[id].Create(width, height, depth, data); } - std::shared_ptr const &operator[](int id) { + gfx::Bitmap &operator[](int id) { + auto it = bitmap_cache_.find(id); + if (it != bitmap_cache_.end()) { + return it->second; + } + return bitmap_cache_.begin()->second; + } + gfx::Bitmap &shared_bitmap(int id) { auto it = bitmap_cache_.find(id); if (it != bitmap_cache_.end()) { return it->second; @@ -309,22 +313,14 @@ class BitmapManager { throw std::runtime_error( absl::StrCat("Bitmap with id ", id, " not found.")); } - std::shared_ptr const &shared_bitmap(int id) { - auto it = bitmap_cache_.find(id); - if (it != bitmap_cache_.end()) { - return it->second; - } - throw std::runtime_error( - absl::StrCat("Bitmap with id ", id, " not found.")); - } - auto mutable_bitmap(int id) { return bitmap_cache_[id]; } + auto mutable_bitmap(int id) { return &bitmap_cache_[id]; } void clear_cache() { bitmap_cache_.clear(); } + auto size() const { return bitmap_cache_.size(); } + auto at(int id) const { return bitmap_cache_.at(id); } - using value_type = std::pair>; - using iterator = - std::unordered_map>::iterator; - using const_iterator = - std::unordered_map>::const_iterator; + using value_type = std::pair; + using iterator = std::unordered_map::iterator; + using const_iterator = std::unordered_map::const_iterator; iterator begin() noexcept { return bitmap_cache_.begin(); } iterator end() noexcept { return bitmap_cache_.end(); } diff --git a/src/app/gfx/snes_color.cc b/src/app/gfx/snes_color.cc index d822bb4d..f2dfb239 100644 --- a/src/app/gfx/snes_color.cc +++ b/src/app/gfx/snes_color.cc @@ -1,7 +1,7 @@ #include "app/gfx/snes_color.h" -#include +#include "imgui/imgui.h" #include #include @@ -101,4 +101,4 @@ std::vector GetColFileData(uint8_t* data) { } // namespace gfx } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/gfx/snes_color.h b/src/app/gfx/snes_color.h index 5923d27b..dcaea639 100644 --- a/src/app/gfx/snes_color.h +++ b/src/app/gfx/snes_color.h @@ -1,7 +1,7 @@ #ifndef YAZE_APP_GFX_SNES_COLOR_H_ #define YAZE_APP_GFX_SNES_COLOR_H_ -#include +#include "imgui/imgui.h" #include #include @@ -31,10 +31,10 @@ std::vector Convert(const std::vector& palette); /** * @brief SNES Color container - * - * Used for displaying the color to the screen and writing + * + * Used for displaying the color to the screen and writing * the color to the Rom file in the correct format. - * + * * SNES colors may be represented in one of three formats: * - Color data from the rom in a snes_color struct * - Color data for displaying to the UI via ImVec4 @@ -49,7 +49,10 @@ class SnesColor { color.blue = val.z / 255; snes_ = ConvertRGBtoSNES(color); } - + explicit SnesColor(const uint16_t val) : snes_(val) { + snes_color color = ConvertSNEStoRGB(val); + rgb_ = ImVec4(color.red, color.green, color.blue, 0.f); + } explicit SnesColor(const snes_color val) : rgb_(val.red, val.green, val.blue, 255.f), snes_(ConvertRGBtoSNES(val)), diff --git a/src/app/gfx/snes_palette.cc b/src/app/gfx/snes_palette.cc index 8ce92f6d..b5388c3d 100644 --- a/src/app/gfx/snes_palette.cc +++ b/src/app/gfx/snes_palette.cc @@ -1,7 +1,7 @@ #include "snes_palette.h" #include -#include +#include "imgui/imgui.h" #include #include @@ -30,7 +30,7 @@ absl::Status LoadOverworldMainPalettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 6; i++) { RETURN_IF_ERROR(palette_groups.overworld_main.AddPalette( - gfx::ReadPaletteFromRom(core::overworldPaletteMain + (i * (35 * 2)), + gfx::ReadPaletteFromRom(kOverworldPaletteMain + (i * (35 * 2)), /*num_colors*/ 35, data))) } return absl::OkStatus(); @@ -40,10 +40,9 @@ absl::Status LoadOverworldAuxiliaryPalettes( const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 20; i++) { - RETURN_IF_ERROR( - palette_groups.overworld_aux.AddPalette(gfx::ReadPaletteFromRom( - core::overworldPaletteAuxialiary + (i * (21 * 2)), - /*num_colors*/ 21, data))) + RETURN_IF_ERROR(palette_groups.overworld_aux.AddPalette( + gfx::ReadPaletteFromRom(kOverworldPaletteAux + (i * (21 * 2)), + /*num_colors*/ 21, data))) } return absl::OkStatus(); } @@ -54,7 +53,7 @@ absl::Status LoadOverworldAnimatedPalettes( for (int i = 0; i < 14; i++) { RETURN_IF_ERROR( palette_groups.overworld_animated.AddPalette(gfx::ReadPaletteFromRom( - core::overworldPaletteAnimated + (i * (7 * 2)), 7, data))) + kOverworldPaletteAnimated + (i * (7 * 2)), 7, data))) } return absl::OkStatus(); } @@ -64,7 +63,7 @@ absl::Status LoadHUDPalettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 2; i++) { RETURN_IF_ERROR(palette_groups.hud.AddPalette( - gfx::ReadPaletteFromRom(core::hudPalettes + (i * 64), 32, data))) + gfx::ReadPaletteFromRom(kHudPalettes + (i * 64), 32, data))) } return absl::OkStatus(); } @@ -73,9 +72,9 @@ absl::Status LoadGlobalSpritePalettes(const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); RETURN_IF_ERROR(palette_groups.global_sprites.AddPalette( - gfx::ReadPaletteFromRom(core::globalSpritePalettesLW, 60, data))) + gfx::ReadPaletteFromRom(kGlobalSpritesLW, 60, data))) RETURN_IF_ERROR(palette_groups.global_sprites.AddPalette( - gfx::ReadPaletteFromRom(core::globalSpritePalettesDW, 60, data))) + gfx::ReadPaletteFromRom(globalSpritePalettesDW, 60, data))) return absl::OkStatus(); } @@ -84,7 +83,7 @@ absl::Status LoadArmorPalettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 5; i++) { RETURN_IF_ERROR(palette_groups.armors.AddPalette( - gfx::ReadPaletteFromRom(core::armorPalettes + (i * 30), 15, data))) + gfx::ReadPaletteFromRom(kArmorPalettes + (i * 30), 15, data))) } return absl::OkStatus(); } @@ -94,7 +93,7 @@ absl::Status LoadSwordPalettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 4; i++) { RETURN_IF_ERROR(palette_groups.swords.AddPalette( - gfx::ReadPaletteFromRom(core::swordPalettes + (i * 6), 3, data))) + gfx::ReadPaletteFromRom(kSwordPalettes + (i * 6), 3, data))) } return absl::OkStatus(); } @@ -104,7 +103,7 @@ absl::Status LoadShieldPalettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 3; i++) { RETURN_IF_ERROR(palette_groups.shields.AddPalette( - gfx::ReadPaletteFromRom(core::shieldPalettes + (i * 8), 4, data))) + gfx::ReadPaletteFromRom(kShieldPalettes + (i * 8), 4, data))) } return absl::OkStatus(); } @@ -114,7 +113,7 @@ absl::Status LoadSpriteAux1Palettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 12; i++) { RETURN_IF_ERROR(palette_groups.sprites_aux1.AddPalette( - gfx::ReadPaletteFromRom(core::spritePalettesAux1 + (i * 14), 7, data))) + gfx::ReadPaletteFromRom(kSpritesPalettesAux1 + (i * 14), 7, data))) } return absl::OkStatus(); } @@ -124,7 +123,7 @@ absl::Status LoadSpriteAux2Palettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 11; i++) { RETURN_IF_ERROR(palette_groups.sprites_aux2.AddPalette( - gfx::ReadPaletteFromRom(core::spritePalettesAux2 + (i * 14), 7, data))) + gfx::ReadPaletteFromRom(kSpritesPalettesAux2 + (i * 14), 7, data))) } return absl::OkStatus(); } @@ -134,7 +133,7 @@ absl::Status LoadSpriteAux3Palettes(const Bytes& rom_data, auto data = rom_data.data(); for (int i = 0; i < 24; i++) { RETURN_IF_ERROR(palette_groups.sprites_aux3.AddPalette( - gfx::ReadPaletteFromRom(core::spritePalettesAux3 + (i * 14), 7, data))) + gfx::ReadPaletteFromRom(kSpritesPalettesAux3 + (i * 14), 7, data))) } return absl::OkStatus(); } @@ -143,9 +142,8 @@ absl::Status LoadDungeonMainPalettes(const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); for (int i = 0; i < 20; i++) { - RETURN_IF_ERROR( - palette_groups.dungeon_main.AddPalette(gfx::ReadPaletteFromRom( - core::dungeonMainPalettes + (i * 180), 90, data))) + RETURN_IF_ERROR(palette_groups.dungeon_main.AddPalette( + gfx::ReadPaletteFromRom(kDungeonMainPalettes + (i * 180), 90, data))) } return absl::OkStatus(); } @@ -153,11 +151,11 @@ absl::Status LoadDungeonMainPalettes(const Bytes& rom_data, absl::Status LoadGrassColors(const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) { RETURN_IF_ERROR(palette_groups.grass.AddColor( - gfx::ReadColorFromRom(core::hardcodedGrassLW, rom_data.data()))) + gfx::ReadColorFromRom(kHardcodedGrassLW, rom_data.data()))) RETURN_IF_ERROR(palette_groups.grass.AddColor( - gfx::ReadColorFromRom(core::hardcodedGrassDW, rom_data.data()))) + gfx::ReadColorFromRom(hardcodedGrassDW, rom_data.data()))) RETURN_IF_ERROR(palette_groups.grass.AddColor( - gfx::ReadColorFromRom(core::hardcodedGrassSpecial, rom_data.data()))) + gfx::ReadColorFromRom(hardcodedGrassSpecial, rom_data.data()))) return absl::OkStatus(); } @@ -165,9 +163,9 @@ absl::Status Load3DObjectPalettes(const Bytes& rom_data, gfx::PaletteGroupMap& palette_groups) { auto data = rom_data.data(); RETURN_IF_ERROR(palette_groups.object_3d.AddPalette( - gfx::ReadPaletteFromRom(core::triforcePalette, 8, data))) + gfx::ReadPaletteFromRom(kTriforcePalette, 8, data))) RETURN_IF_ERROR(palette_groups.object_3d.AddPalette( - gfx::ReadPaletteFromRom(core::crystalPalette, 8, data))) + gfx::ReadPaletteFromRom(crystalPalette, 8, data))) return absl::OkStatus(); } @@ -177,28 +175,28 @@ absl::Status LoadOverworldMiniMapPalettes( for (int i = 0; i < 2; i++) { RETURN_IF_ERROR( palette_groups.overworld_mini_map.AddPalette(gfx::ReadPaletteFromRom( - core::overworldMiniMapPalettes + (i * 256), 128, data))) + kOverworldMiniMapPalettes + (i * 256), 128, data))) } return absl::OkStatus(); } } // namespace palette_group_internal const absl::flat_hash_map kPaletteGroupAddressMap = { - {"ow_main", core::overworldPaletteMain}, - {"ow_aux", core::overworldPaletteAuxialiary}, - {"ow_animated", core::overworldPaletteAnimated}, - {"hud", core::hudPalettes}, - {"global_sprites", core::globalSpritePalettesLW}, - {"armors", core::armorPalettes}, - {"swords", core::swordPalettes}, - {"shields", core::shieldPalettes}, - {"sprites_aux1", core::spritePalettesAux1}, - {"sprites_aux2", core::spritePalettesAux2}, - {"sprites_aux3", core::spritePalettesAux3}, - {"dungeon_main", core::dungeonMainPalettes}, - {"grass", core::hardcodedGrassLW}, - {"3d_object", core::triforcePalette}, - {"ow_mini_map", core::overworldMiniMapPalettes}, + {"ow_main", kOverworldPaletteMain}, + {"ow_aux", kOverworldPaletteAux}, + {"ow_animated", kOverworldPaletteAnimated}, + {"hud", kHudPalettes}, + {"global_sprites", kGlobalSpritesLW}, + {"armors", kArmorPalettes}, + {"swords", kSwordPalettes}, + {"shields", kShieldPalettes}, + {"sprites_aux1", kSpritesPalettesAux1}, + {"sprites_aux2", kSpritesPalettesAux2}, + {"sprites_aux3", kSpritesPalettesAux3}, + {"dungeon_main", kDungeonMainPalettes}, + {"grass", kHardcodedGrassLW}, + {"3d_object", kTriforcePalette}, + {"ow_mini_map", kOverworldMiniMapPalettes}, }; const absl::flat_hash_map kPaletteGroupColorCounts = { @@ -391,4 +389,4 @@ absl::Status LoadAllPalettes(const Bytes& rom_data, PaletteGroupMap& groups) { } // namespace gfx } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/gfx/snes_palette.h b/src/app/gfx/snes_palette.h index 18f7b712..9128c2b1 100644 --- a/src/app/gfx/snes_palette.h +++ b/src/app/gfx/snes_palette.h @@ -2,7 +2,7 @@ #define YAZE_APP_GFX_PALETTE_H #include -#include +#include "imgui/imgui.h" #include #include @@ -21,6 +21,73 @@ namespace yaze { namespace app { namespace gfx { +constexpr int kNumPalettes = 14; + +enum PaletteCategory { + kSword, + kShield, + kClothes, + kWorldColors, + kAreaColors, + kGlobalSprites, + kSpritesAux1, + kSpritesAux2, + kSpritesAux3, + kDungeons, + kWorldMap, + kDungeonMap, + kTriforce, + kCrystal +}; + +static constexpr absl::string_view kPaletteCategoryNames[] = { + "Sword", "Shield", "Clothes", "World Colors", + "Area Colors", "Global Sprites", "Sprites Aux1", "Sprites Aux2", + "Sprites Aux3", "Dungeons", "World Map", "Dungeon Map", + "Triforce", "Crystal"}; + +static constexpr absl::string_view kPaletteGroupNames[] = { + "swords", "shields", "armors", "ow_main", + "ow_aux", "global_sprites", "sprites_aux1", "sprites_aux2", + "sprites_aux3", "dungeon_main", "ow_mini_map", "ow_mini_map", + "3d_object", "3d_object"}; + +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", +}; + +constexpr int kOverworldPaletteMain = 0xDE6C8; +constexpr int kOverworldPaletteAux = 0xDE86C; +constexpr int kOverworldPaletteAnimated = 0xDE604; +constexpr int kGlobalSpritesLW = 0xDD218; +constexpr int globalSpritePalettesDW = 0xDD290; +// Green, Blue, Red, Bunny, Electrocuted (15 colors each) +constexpr int kArmorPalettes = 0xDD308; +constexpr int kSpritesPalettesAux1 = 0xDD39E; // 7 colors each +constexpr int kSpritesPalettesAux2 = 0xDD446; // 7 colors each +constexpr int kSpritesPalettesAux3 = 0xDD4E0; // 7 colors each +constexpr int kSwordPalettes = 0xDD630; // 3 colors each - 4 entries +constexpr int kShieldPalettes = 0xDD648; // 4 colors each - 3 entries +constexpr int kHudPalettes = 0xDD660; +constexpr int dungeonMapPalettes = 0xDD70A; // 21 colors +constexpr int kDungeonMainPalettes = 0xDD734; //(15*6) colors each - 20 entries +constexpr int dungeonMapBgPalettes = 0xDE544; // 16*6 +// Mirrored Value at 0x75645 : 0x75625 +constexpr int kHardcodedGrassLW = 0x5FEA9; +constexpr int hardcodedGrassDW = 0x05FEB3; // 0x7564F +constexpr int hardcodedGrassSpecial = 0x75640; +constexpr int kOverworldMiniMapPalettes = 0x55B27; +constexpr int kTriforcePalette = 0x64425; +constexpr int crystalPalette = 0xF4CD3; +// 2 bytes for each overworld area (320) +constexpr int customAreaSpecificBGPalette = 0x140000; +constexpr int customAreaSpecificBGASM = 0x140150; +// 1 byte, not 0 if enabled +constexpr int customAreaSpecificBGEnabled = 0x140140; + /** * @brief Primitive of a SNES color palette. */ @@ -84,6 +151,11 @@ class SnesPalette { size_++; } + void AddColor(uint16_t color) { + colors.emplace_back(color); + size_++; + } + absl::StatusOr GetColor(int i) const { if (i > size_) { return absl::InvalidArgumentError("SnesPalette: Index out of bounds"); @@ -99,6 +171,7 @@ class SnesPalette { } auto size() const { return colors.size(); } + auto empty() const { return colors.empty(); } SnesColor& operator[](int i) { if (i > size_) { @@ -274,22 +347,22 @@ struct PaletteGroupMap { } template - void for_each(Func&& func) { - func(overworld_main); - func(overworld_aux); - func(overworld_animated); - func(hud); - func(global_sprites); - func(armors); - func(swords); - func(shields); - func(sprites_aux1); - func(sprites_aux2); - func(sprites_aux3); - func(dungeon_main); - func(grass); - func(object_3d); - func(overworld_mini_map); + absl::Status for_each(Func&& func) { + RETURN_IF_ERROR(func(overworld_aux)); + RETURN_IF_ERROR(func(overworld_animated)); + RETURN_IF_ERROR(func(hud)); + RETURN_IF_ERROR(func(global_sprites)); + RETURN_IF_ERROR(func(armors)); + RETURN_IF_ERROR(func(swords)); + RETURN_IF_ERROR(func(shields)); + RETURN_IF_ERROR(func(sprites_aux1)); + RETURN_IF_ERROR(func(sprites_aux2)); + RETURN_IF_ERROR(func(sprites_aux3)); + RETURN_IF_ERROR(func(dungeon_main)); + RETURN_IF_ERROR(func(grass)); + RETURN_IF_ERROR(func(object_3d)); + RETURN_IF_ERROR(func(overworld_mini_map)); + return absl::OkStatus(); } }; @@ -360,4 +433,4 @@ struct Paletteset { } // namespace app } // namespace yaze -#endif // YAZE_APP_GFX_PALETTE_H \ No newline at end of file +#endif // YAZE_APP_GFX_PALETTE_H diff --git a/src/app/gfx/snes_tile.h b/src/app/gfx/snes_tile.h index cbd5e2f2..a19c5718 100644 --- a/src/app/gfx/snes_tile.h +++ b/src/app/gfx/snes_tile.h @@ -176,4 +176,4 @@ class OamTile { } // namespace app } // namespace yaze -#endif // YAZE_APP_GFX_SNES_TILE_H \ No newline at end of file +#endif // YAZE_APP_GFX_SNES_TILE_H diff --git a/src/app/gui/asset_browser.cc b/src/app/gui/asset_browser.cc new file mode 100644 index 00000000..fe6979e7 --- /dev/null +++ b/src/app/gui/asset_browser.cc @@ -0,0 +1,345 @@ +#include "asset_browser.h" + +#include "absl/strings/str_format.h" + +namespace yaze { +namespace app { +namespace gui { + +using namespace ImGui; + +const ImGuiTableSortSpecs* AssetObject::s_current_sort_specs = NULL; + +void GfxSheetAssetBrowser::Draw(gfx::BitmapManager* bmp_manager) { + PushItemWidth(GetFontSize() * 10); + SeparatorText("Contents"); + Checkbox("Show Type Overlay", &ShowTypeOverlay); + SameLine(); + Checkbox("Allow Sorting", &AllowSorting); + SameLine(); + Checkbox("Stretch Spacing", &StretchSpacing); + SameLine(); + Checkbox("Allow dragging unselected item", &AllowDragUnselected); + SameLine(); + Checkbox("Allow box-selection", &AllowBoxSelect); + SameLine(); + SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f"); + SameLine(); + SliderInt("Icon Spacing", &IconSpacing, 0, 32); + SameLine(); + SliderInt("Icon Hit Spacing", &IconHitSpacing, 0, 32); + PopItemWidth(); + + // Filter by types + static bool filter_type[4] = {true, true, true, true}; + Text("Filter by type:"); + SameLine(); + Checkbox("Unsorted", &filter_type[0]); + SameLine(); + Checkbox("Dungeon", &filter_type[1]); + SameLine(); + Checkbox("Overworld", &filter_type[2]); + SameLine(); + Checkbox("Sprite", &filter_type[3]); + + // Show a table with ONLY one header row to showcase the idea/possibility of + // using this to provide a sorting UI + if (AllowSorting) { + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGuiTableFlags table_flags_for_sort_specs = + ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders; + if (BeginTable("for_sort_specs_only", 2, table_flags_for_sort_specs, + ImVec2(0.0f, GetFrameHeight()))) { + TableSetupColumn("Index"); + TableSetupColumn("Type"); + TableHeadersRow(); + if (ImGuiTableSortSpecs* sort_specs = TableGetSortSpecs()) + if (sort_specs->SpecsDirty || RequestSort) { + AssetObject::SortWithSortSpecs(sort_specs, Items.Data, Items.Size); + sort_specs->SpecsDirty = RequestSort = false; + } + EndTable(); + } + PopStyleVar(); + } + + ImGuiIO& io = GetIO(); + SetNextWindowContentSize(ImVec2( + 0.0f, LayoutOuterPadding + + LayoutLineCount * (LayoutItemSize.x + LayoutItemSpacing))); + if (BeginChild("Assets", ImVec2(0.0f, -GetTextLineHeightWithSpacing()), + ImGuiChildFlags_Border, ImGuiWindowFlags_NoMove)) { + ImDrawList* draw_list = GetWindowDrawList(); + + const float avail_width = GetContentRegionAvail().x; + UpdateLayoutSizes(avail_width); + + // Calculate and store start position. + ImVec2 start_pos = GetCursorScreenPos(); + start_pos = ImVec2(start_pos.x + LayoutOuterPadding, + start_pos.y + LayoutOuterPadding); + SetCursorScreenPos(start_pos); + + // Multi-select + ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | + ImGuiMultiSelectFlags_ClearOnClickVoid; + + // - Enable box-select (in 2D mode, so that changing box-select rectangle + // X1/X2 boundaries will affect clipped items) + if (AllowBoxSelect) ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d; + + // - This feature allows dragging an unselected item without selecting it + // (rarely used) + if (AllowDragUnselected) + ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; + + // - Enable keyboard wrapping on X axis + // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping + // api yet, so this flag is provided as a courtesy to avoid doing: + // NavMoveRequestTryWrapping(GetCurrentWindow(), + // ImGuiNavMoveFlags_WrapX); + // When we finish implementing a more general API for this, we will + // obsolete this flag in favor of the new system) + ms_flags |= ImGuiMultiSelectFlags_NavWrapX; + + ImGuiMultiSelectIO* ms_io = + BeginMultiSelect(ms_flags, Selection.Size, Items.Size); + + // Use custom selection adapter: store ID in selection (recommended) + Selection.UserData = this; + Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_, + int idx) { + GfxSheetAssetBrowser* self = (GfxSheetAssetBrowser*)self_->UserData; + return self->Items[idx].ID; + }; + Selection.ApplyRequests(ms_io); + + const bool want_delete = + (Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && + (Selection.Size > 0)) || + RequestDelete; + const int item_curr_idx_to_focus = + want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1; + RequestDelete = false; + + // Push LayoutSelectableSpacing (which is LayoutItemSpacing minus + // hit-spacing, if we decide to have hit gaps between items) Altering + // style ItemSpacing may seem unnecessary as we position every items using + // SetCursorScreenPos()... But it is necessary for two reasons: + // - Selectables uses it by default to visually fill the space between two + // items. + // - The vertical spacing would be measured by Clipper to calculate line + // height if we didn't provide it explicitly (here we do). + PushStyleVar(ImGuiStyleVar_ItemSpacing, + ImVec2(LayoutSelectableSpacing, LayoutSelectableSpacing)); + + // Rendering parameters + const ImU32 icon_type_overlay_colors[5] = { + 0, IM_COL32(200, 70, 70, 255), IM_COL32(70, 170, 70, 255), + IM_COL32(70, 70, 200, 255), IM_COL32(200, 200, 200, 255)}; + const ImU32 icon_bg_color = GetColorU32(ImGuiCol_MenuBarBg); + const ImVec2 icon_type_overlay_size = ImVec2(5.0f, 5.0f); + const bool display_label = (LayoutItemSize.x >= CalcTextSize("999").x); + + const int column_count = LayoutColumnCount; + ImGuiListClipper clipper; + clipper.Begin(LayoutLineCount, LayoutItemStep.y); + + // Ensure focused item line is not clipped. + if (item_curr_idx_to_focus != -1) + clipper.IncludeItemByIndex(item_curr_idx_to_focus / column_count); + + // Ensure RangeSrc item line is not clipped. + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem / column_count); + + while (clipper.Step()) { + for (int line_idx = clipper.DisplayStart; line_idx < clipper.DisplayEnd; + line_idx++) { + const int item_min_idx_for_current_line = line_idx * column_count; + const int item_max_idx_for_current_line = + IM_MIN((line_idx + 1) * column_count, Items.Size); + for (int item_idx = item_min_idx_for_current_line; + item_idx < item_max_idx_for_current_line; ++item_idx) { + AssetObject* item_data = &Items[item_idx]; + PushID((int)item_data->ID); + + // Position item + ImVec2 pos = + ImVec2(start_pos.x + (item_idx % column_count) * LayoutItemStep.x, + start_pos.y + line_idx * LayoutItemStep.y); + SetCursorScreenPos(pos); + + SetNextItemSelectionUserData(item_idx); + bool item_is_selected = Selection.Contains((ImGuiID)item_data->ID); + bool item_is_visible = IsRectVisible(LayoutItemSize); + Selectable("", item_is_selected, ImGuiSelectableFlags_None, + LayoutItemSize); + + // Update our selection state immediately (without waiting for + // EndMultiSelect() requests) because we use this to alter the color + // of our text/icon. + if (IsItemToggledSelection()) item_is_selected = !item_is_selected; + + // Focus (for after deletion) + if (item_curr_idx_to_focus == item_idx) SetKeyboardFocusHere(-1); + + // Drag and drop + if (BeginDragDropSource()) { + // Create payload with full selection OR single unselected item. + // (the later is only possible when using + // ImGuiMultiSelectFlags_SelectOnClickRelease) + if (GetDragDropPayload() == NULL) { + ImVector payload_items; + void* it = NULL; + ImGuiID id = 0; + if (!item_is_selected) + payload_items.push_back(item_data->ID); + else + while (Selection.GetNextSelectedItem(&it, &id)) + payload_items.push_back(id); + SetDragDropPayload("ASSETS_BROWSER_ITEMS", payload_items.Data, + (size_t)payload_items.size_in_bytes()); + } + + // Display payload content in tooltip, by extracting it from the + // payload data (we could read from selection, but it is more + // correct and reusable to read from payload) + const ImGuiPayload* payload = GetDragDropPayload(); + const int payload_count = + (int)payload->DataSize / (int)sizeof(ImGuiID); + Text("%d assets", payload_count); + + EndDragDropSource(); + } + + // Render icon (a real app would likely display an image/thumbnail + // here) Because we use ImGuiMultiSelectFlags_BoxSelect2d, clipping + // vertical may occasionally be larger, so we coarse-clip our + // rendering as well. + if (item_is_visible) { + ImVec2 box_min(pos.x - 1, pos.y - 1); + ImVec2 box_max(box_min.x + LayoutItemSize.x + 2, + box_min.y + LayoutItemSize.y + 2); // Dubious + draw_list->AddRectFilled(box_min, box_max, + icon_bg_color); // Background color + + if (display_label) { + ImU32 label_col = GetColorU32( + item_is_selected ? ImGuiCol_Text : ImGuiCol_TextDisabled); + draw_list->AddImage( + (void*)bmp_manager->mutable_bitmap(item_data->ID)->texture(), + box_min, box_max, ImVec2(0, 0), ImVec2(1, 1), + GetColorU32(ImVec4(1, 1, 1, 1))); + draw_list->AddText(ImVec2(box_min.x, box_max.y - GetFontSize()), + label_col, + absl::StrFormat("%X", item_data->ID).c_str()); + } + if (ShowTypeOverlay && item_data->Type != 0) { + ImU32 type_col = icon_type_overlay_colors + [item_data->Type % IM_ARRAYSIZE(icon_type_overlay_colors)]; + draw_list->AddRectFilled( + ImVec2(box_max.x - 2 - icon_type_overlay_size.x, + box_min.y + 2), + ImVec2(box_max.x - 2, + box_min.y + 2 + icon_type_overlay_size.y), + type_col); + } + } + + PopID(); + } + } + } + clipper.End(); + PopStyleVar(); // ImGuiStyleVar_ItemSpacing + + // Context menu + if (BeginPopupContextWindow()) { + Text("Selection: %d items", Selection.Size); + Separator(); + if (BeginMenu("Set Type")) { + if (MenuItem("Unsorted")) { + void* it = NULL; + ImGuiID id = 0; + while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 0; + } + if (MenuItem("Dungeon")) { + void* it = NULL; + ImGuiID id = 0; + while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 1; + } + if (MenuItem("Overworld")) { + void* it = NULL; + ImGuiID id = 0; + while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 2; + } + if (MenuItem("Sprite")) { + void* it = NULL; + ImGuiID id = 0; + while (Selection.GetNextSelectedItem(&it, &id)) Items[id].Type = 3; + } + EndMenu(); + } + Separator(); + if (MenuItem("Delete", "Del", false, Selection.Size > 0)) + RequestDelete = true; + EndPopup(); + } + + ms_io = EndMultiSelect(); + Selection.ApplyRequests(ms_io); + if (want_delete) + Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus); + + // Zooming with CTRL+Wheel + if (IsWindowAppearing()) ZoomWheelAccum = 0.0f; + if (IsWindowHovered() && io.MouseWheel != 0.0f && + IsKeyDown(ImGuiMod_Ctrl) && IsAnyItemActive() == false) { + ZoomWheelAccum += io.MouseWheel; + if (fabsf(ZoomWheelAccum) >= 1.0f) { + // Calculate hovered item index from mouse location + // FIXME: Locking aiming on 'hovered_item_idx' (with a cool-down + // timer) would ensure zoom keeps on it. + const float hovered_item_nx = + (io.MousePos.x - start_pos.x + LayoutItemSpacing * 0.5f) / + LayoutItemStep.x; + const float hovered_item_ny = + (io.MousePos.y - start_pos.y + LayoutItemSpacing * 0.5f) / + LayoutItemStep.y; + const int hovered_item_idx = + ((int)hovered_item_ny * LayoutColumnCount) + (int)hovered_item_nx; + // SetTooltip("%f,%f -> item %d", hovered_item_nx, + // hovered_item_ny, hovered_item_idx); // Move those 4 lines in block + // above for easy debugging + + // Zoom + IconSize *= powf(1.1f, (float)(int)ZoomWheelAccum); + IconSize = IM_CLAMP(IconSize, 16.0f, 128.0f); + ZoomWheelAccum -= (int)ZoomWheelAccum; + UpdateLayoutSizes(avail_width); + + // Manipulate scroll to that we will land at the same Y location of + // currently hovered item. + // - Calculate next frame position of item under mouse + // - Set new scroll position to be used in next BeginChild() + // call. + float hovered_item_rel_pos_y = + ((float)(hovered_item_idx / LayoutColumnCount) + + fmodf(hovered_item_ny, 1.0f)) * + LayoutItemStep.y; + hovered_item_rel_pos_y += GetStyle().WindowPadding.y; + float mouse_local_y = io.MousePos.y - GetWindowPos().y; + SetScrollY(hovered_item_rel_pos_y - mouse_local_y); + } + } + } + EndChild(); + + Text("Selected: %d/%d items", Selection.Size, Items.Size); +} + +} // namespace gui +} // namespace app +} // namespace yaze diff --git a/src/app/gui/asset_browser.h b/src/app/gui/asset_browser.h new file mode 100644 index 00000000..ac79930a --- /dev/null +++ b/src/app/gui/asset_browser.h @@ -0,0 +1,248 @@ +#ifndef YAZE_APP_GUI_ASSET_BROWSER_H +#define YAZE_APP_GUI_ASSET_BROWSER_H + +#include "imgui/imgui.h" + +#include + +#include "app/gfx/bitmap.h" + +#define IM_MIN(A, B) (((A) < (B)) ? (A) : (B)) +#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) +#define IM_CLAMP(V, MN, MX) ((V) < (MN) ? (MN) : (V) > (MX) ? (MX) : (V)) + +namespace yaze { +namespace app { +namespace gui { + +// Extra functions to add deletion support to ImGuiSelectionBasicStorage +struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage { + // Find which item should be Focused after deletion. + // Call _before_ item submission. Retunr an index in the before-deletion item + // list, your item loop should call SetKeyboardFocusHere() on it. The + // subsequent ApplyDeletionPostLoop() code will use it to apply Selection. + // - We cannot provide this logic in core Dear ImGui because we don't have + // access to selection data. + // - We don't actually manipulate the ImVector<> here, only in + // ApplyDeletionPostLoop(), but using similar API for consistency and + // flexibility. + // - Important: Deletion only works if the underlying ImGuiID for your items + // are stable: aka not depend on their index, but on e.g. item id/ptr. + // FIXME-MULTISELECT: Doesn't take account of the possibility focus target + // will be moved during deletion. Need refocus or scroll offset. + int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, int items_count) { + if (Size == 0) return -1; + + // If focused item is not selected... + const int focused_idx = + (int)ms_io->NavIdItem; // Index of currently focused item + if (ms_io->NavIdSelected == + false) // This is merely a shortcut, == + // Contains(adapter->IndexToStorage(items, focused_idx)) + { + ms_io->RangeSrcReset = + true; // Request to recover RangeSrc from NavId next frame. Would be + // ok to reset even when NavIdSelected==true, but it would take + // an extra frame to recover RangeSrc when deleting a selected + // item. + return focused_idx; // Request to focus same item after deletion. + } + + // If focused item is selected: land on first unselected item after focused + // item. + for (int idx = focused_idx + 1; idx < items_count; idx++) + if (!Contains(GetStorageIdFromIndex(idx))) return idx; + + // If focused item is selected: otherwise return last unselected item before + // focused item. + for (int idx = IM_MIN(focused_idx, items_count) - 1; idx >= 0; idx--) + if (!Contains(GetStorageIdFromIndex(idx))) return idx; + + return -1; + } + + // Rewrite item list (delete items) + update selection. + // - Call after EndMultiSelect() + // - We cannot provide this logic in core Dear ImGui because we don't have + // access to your items, nor to selection data. + template + void ApplyDeletionPostLoop(ImGuiMultiSelectIO* ms_io, + ImVector& items, + int item_curr_idx_to_select) { + // Rewrite item list (delete items) + convert old selection index (before + // deletion) to new selection index (after selection). If NavId was not part + // of selection, we will stay on same item. + ImVector new_items; + new_items.reserve(items.Size - Size); + int item_next_idx_to_select = -1; + for (int idx = 0; idx < items.Size; idx++) { + if (!Contains(GetStorageIdFromIndex(idx))) + new_items.push_back(items[idx]); + if (item_curr_idx_to_select == idx) + item_next_idx_to_select = new_items.Size - 1; + } + items.swap(new_items); + + // Update selection + Clear(); + if (item_next_idx_to_select != -1 && ms_io->NavIdSelected) + SetItemSelected(GetStorageIdFromIndex(item_next_idx_to_select), true); + } +}; + +struct AssetObject { + ImGuiID ID; + int Type; + + AssetObject(ImGuiID id, int type) { + ID = id; + Type = type; + } + + static const ImGuiTableSortSpecs* s_current_sort_specs; + + static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, + AssetObject* items, int items_count) { + // Store in variable accessible by the sort function. + s_current_sort_specs = sort_specs; + if (items_count > 1) + qsort(items, (size_t)items_count, sizeof(items[0]), + AssetObject::CompareWithSortSpecs); + s_current_sort_specs = NULL; + } + + // Compare function to be used by qsort() + static int __cdecl CompareWithSortSpecs(const void* lhs, const void* rhs) { + const AssetObject* a = (const AssetObject*)lhs; + const AssetObject* b = (const AssetObject*)rhs; + for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) { + const ImGuiTableColumnSortSpecs* sort_spec = + &s_current_sort_specs->Specs[n]; + int delta = 0; + if (sort_spec->ColumnIndex == 0) + delta = ((int)a->ID - (int)b->ID); + else if (sort_spec->ColumnIndex == 1) + delta = (a->Type - b->Type); + if (delta > 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 + : -1; + if (delta < 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 + : +1; + } + return ((int)a->ID - (int)b->ID); + } +}; + +struct UnsortedAsset : public AssetObject { + UnsortedAsset(ImGuiID id) : AssetObject(id, 0) {} +}; + +struct DungeonAsset : public AssetObject { + DungeonAsset(ImGuiID id) : AssetObject(id, 1) {} +}; + +struct OverworldAsset : public AssetObject { + OverworldAsset(ImGuiID id) : AssetObject(id, 2) {} +}; + +struct SpriteAsset : public AssetObject { + SpriteAsset(ImGuiID id) : AssetObject(id, 3) {} +}; + +struct GfxSheetAssetBrowser { + // Options + bool ShowTypeOverlay = true; + bool AllowSorting = true; + bool AllowDragUnselected = false; + bool AllowBoxSelect = true; + float IconSize = 32.0f; + int IconSpacing = 10; + // Increase hit-spacing if you want to make it possible to clear or + // box-select from gaps. Some spacing is required to able to amend + // with Shift+box-select. Value is small in Explorer. + int IconHitSpacing = 4; + bool StretchSpacing = true; + + // State + ImVector Items; + + // (ImGuiSelectionBasicStorage + helper funcs to handle deletion) + ExampleSelectionWithDeletion Selection; + + ImGuiID NextItemId = 0; // Unique identifier when creating new items + bool RequestDelete = false; // Deferred deletion request + bool RequestSort = false; // Deferred sort request + // Mouse wheel accumulator to handle smooth wheels better + float ZoomWheelAccum = 0.0f; + + // Calculated sizes for layout, output of UpdateLayoutSizes(). Could be locals + // but our code is simpler this way. + ImVec2 LayoutItemSize; + ImVec2 LayoutItemStep; // == LayoutItemSize + LayoutItemSpacing + float LayoutItemSpacing = 0.0f; + float LayoutSelectableSpacing = 0.0f; + float LayoutOuterPadding = 0.0f; + int LayoutColumnCount = 0; + int LayoutLineCount = 0; + bool Initialized = false; + + void Initialize(gfx::BitmapManager* bmp_manager) { + // Load the assets + for (int i = 0; i < bmp_manager->size(); i++) { + Items.push_back(UnsortedAsset(i)); + } + Initialized = true; + } + + void AddItems(int count) { + if (Items.Size == 0) NextItemId = 0; + Items.reserve(Items.Size + count); + for (int n = 0; n < count; n++, NextItemId++) + Items.push_back(AssetObject(NextItemId, (NextItemId % 20) < 15 ? 0 + : (NextItemId % 20) < 18 ? 1 + : 2)); + RequestSort = true; + } + void ClearItems() { + Items.clear(); + Selection.Clear(); + } + + // Logic would be written in the main code BeginChild() and outputing to local + // variables. We extracted it into a function so we can call it easily from + // multiple places. + void UpdateLayoutSizes(float avail_width) { + // Layout: when not stretching: allow extending into right-most spacing. + LayoutItemSpacing = (float)IconSpacing; + if (StretchSpacing == false) + avail_width += floorf(LayoutItemSpacing * 0.5f); + + // Layout: calculate number of icon per line and number of lines + LayoutItemSize = ImVec2(floorf(IconSize * 4), floorf(IconSize)); + LayoutColumnCount = + IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1); + LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; + + // Layout: when stretching: allocate remaining space to more spacing. Round + // before division, so item_spacing may be non-integer. + if (StretchSpacing && LayoutColumnCount > 1) + LayoutItemSpacing = + floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / + LayoutColumnCount; + + LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, + LayoutItemSize.y + LayoutItemSpacing); + LayoutSelectableSpacing = + IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f); + LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f); + } + + void Draw(gfx::BitmapManager* bmp_manager); +}; + +} // namespace gui +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_GUI_ASSET_BROWSER_H \ No newline at end of file diff --git a/src/app/gui/canvas.cc b/src/app/gui/canvas.cc index 66107522..c874815e 100644 --- a/src/app/gui/canvas.cc +++ b/src/app/gui/canvas.cc @@ -1,37 +1,44 @@ #include "canvas.h" -#include +#include "imgui/imgui.h" #include #include -#include "app/editor/graphics_editor.h" +#include "app/editor/graphics/graphics_editor.h" #include "app/gfx/bitmap.h" +#include "app/gui/color.h" +#include "app/gui/input.h" +#include "app/gui/style.h" #include "app/rom.h" namespace yaze { namespace app { namespace gui { +using ImGui::BeginMenu; +using ImGui::BeginPopup; +using ImGui::EndMenu; +using ImGui::GetContentRegionAvail; +using ImGui::GetCursorScreenPos; +using ImGui::GetIO; +using ImGui::GetMouseDragDelta; +using ImGui::GetWindowDrawList; +using ImGui::IsItemActive; +using ImGui::IsItemHovered; +using ImGui::IsMouseClicked; +using ImGui::IsMouseDragging; +using ImGui::MenuItem; +using ImGui::OpenPopupOnItemClick; +using ImGui::Selectable; +using ImGui::Separator; +using ImGui::Text; + constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255); constexpr uint32_t kRectangleBorder = IM_COL32(255, 255, 255, 255); constexpr ImGuiButtonFlags kMouseFlags = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight; -void Canvas::Update(const gfx::Bitmap &bitmap, ImVec2 bg_size, int tile_size, - float scale, float grid_size) { - if (scale != 1.0f) { - bg_size.x *= scale / 2; - bg_size.y *= scale / 2; - } - DrawBackground(bg_size); - DrawContextMenu(); - DrawTileSelector(tile_size); - DrawBitmap(bitmap, 2, scale); - DrawGrid(grid_size); - DrawOverlay(); -} - void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, const std::function &event, int tile_size, float scale) { @@ -46,40 +53,34 @@ void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, DrawOverlay(); } -void Canvas::UpdateEvent(const std::function &event, ImVec2 bg_size, - int tile_size, float scale, float grid_size) { - DrawBackground(bg_size); - DrawContextMenu(); - event(); - DrawGrid(grid_size); - DrawOverlay(); -} - void Canvas::UpdateInfoGrid(ImVec2 bg_size, int tile_size, float scale, - float grid_size) { + float grid_size, int label_id) { enable_custom_labels_ = true; DrawBackground(bg_size); - DrawGrid(grid_size); + DrawInfoGrid(grid_size, 8, label_id); DrawOverlay(); } void Canvas::DrawBackground(ImVec2 canvas_size, bool can_drag) { - canvas_p0_ = ImGui::GetCursorScreenPos(); - if (!custom_canvas_size_) canvas_sz_ = ImGui::GetContentRegionAvail(); + draw_list_ = GetWindowDrawList(); + canvas_p0_ = GetCursorScreenPos(); + if (!custom_canvas_size_) canvas_sz_ = GetContentRegionAvail(); if (canvas_size.x != 0) canvas_sz_ = canvas_size; canvas_p1_ = ImVec2(canvas_p0_.x + (canvas_sz_.x * global_scale_), canvas_p0_.y + (canvas_sz_.y * global_scale_)); - draw_list_ = ImGui::GetWindowDrawList(); // Draw border and background color + + // Draw border and background color draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor); draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder); - const ImGuiIO &io = ImGui::GetIO(); - auto scaled_sz = - ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_); - ImGui::InvisibleButton("canvas", scaled_sz, kMouseFlags); + ImGui::InvisibleButton( + canvas_id_.c_str(), + ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_), + kMouseFlags); - if (draggable_ && ImGui::IsItemHovered()) { - const bool is_active = ImGui::IsItemActive(); // Held + if (draggable_ && IsItemHovered()) { + const ImGuiIO &io = GetIO(); + const bool is_active = IsItemActive(); // Held const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); // Lock scrolled origin const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); @@ -87,8 +88,8 @@ void Canvas::DrawBackground(ImVec2 canvas_size, bool can_drag) { // Pan (we use a zero mouse threshold when there's no context menu) if (const float mouse_threshold_for_pan = enable_context_menu_ ? -1.0f : 0.0f; - is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, - mouse_threshold_for_pan)) { + is_active && + IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) { scrolling_.x += io.MouseDelta.x; scrolling_.y += io.MouseDelta.y; } @@ -96,7 +97,7 @@ void Canvas::DrawBackground(ImVec2 canvas_size, bool can_drag) { } void Canvas::DrawContextMenu(gfx::Bitmap *bitmap) { - const ImGuiIO &io = ImGui::GetIO(); + const ImGuiIO &io = GetIO(); auto scaled_sz = ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_); const ImVec2 origin(canvas_p0_.x + scrolling_.x, @@ -104,61 +105,110 @@ void Canvas::DrawContextMenu(gfx::Bitmap *bitmap) { const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); // Context menu (under default mouse threshold) - if (ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); + if (ImVec2 drag_delta = GetMouseDragDelta(ImGuiMouseButton_Right); enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f) - ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + OpenPopupOnItemClick(context_id_.c_str(), ImGuiPopupFlags_MouseButtonRight); // Contents of the Context Menu - if (ImGui::BeginPopup("context")) { - if (ImGui::MenuItem("Reset Position", nullptr, false)) { + if (ImGui::BeginPopup(context_id_.c_str())) { + if (MenuItem("Reset Position", nullptr, false)) { scrolling_.x = 0; scrolling_.y = 0; } - ImGui::MenuItem("Show Grid", nullptr, &enable_grid_); - ImGui::Selectable("Show Position Labels", &enable_hex_tile_labels_); - if (ImGui::BeginMenu("Canvas Properties")) { - ImGui::Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y); - ImGui::Text("Global Scale: %.1f", global_scale_); - ImGui::Text("Mouse Position: %.0f x %.0f", mouse_pos.x, mouse_pos.y); - ImGui::EndMenu(); + MenuItem("Show Grid", nullptr, &enable_grid_); + Selectable("Show Position Labels", &enable_hex_tile_labels_); + if (BeginMenu("Canvas Properties")) { + Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y); + Text("Global Scale: %.1f", global_scale_); + Text("Mouse Position: %.0f x %.0f", mouse_pos.x, mouse_pos.y); + EndMenu(); } if (bitmap != nullptr) { - if (ImGui::BeginMenu("Bitmap Properties")) { - ImGui::Text("Size: %.0f x %.0f", scaled_sz.x, scaled_sz.y); - ImGui::Text("Pitch: %s", - absl::StrFormat("%d", bitmap->surface()->pitch).c_str()); - ImGui::Text("BitsPerPixel: %d", - bitmap->surface()->format->BitsPerPixel); - ImGui::Text("BytesPerPixel: %d", - bitmap->surface()->format->BytesPerPixel); - ImGui::EndMenu(); + if (BeginMenu("Bitmap Properties")) { + Text("Size: %.0f x %.0f", scaled_sz.x, scaled_sz.y); + Text("Pitch: %s", + absl::StrFormat("%d", bitmap->surface()->pitch).c_str()); + Text("BitsPerPixel: %d", bitmap->surface()->format->BitsPerPixel); + Text("BytesPerPixel: %d", bitmap->surface()->format->BytesPerPixel); + EndMenu(); + } + if (BeginMenu("Bitmap Format")) { + if (MenuItem("Indexed")) { + bitmap->Reformat(0); + bitmap->ApplyPalette(bitmap->palette()); + rom()->UpdateBitmap(bitmap); + } + if (MenuItem("2BPP")) { + bitmap->Reformat(1); + bitmap->ApplyPalette(bitmap->palette()); + rom()->UpdateBitmap(bitmap); + } + if (MenuItem("4BPP")) { + bitmap->Reformat(2); + bitmap->ApplyPalette(bitmap->palette()); + rom()->UpdateBitmap(bitmap); + } + if (MenuItem("8BPP")) { + bitmap->Reformat(3); + bitmap->ApplyPalette(bitmap->palette()); + rom()->UpdateBitmap(bitmap); + } + EndMenu(); + } + if (BeginMenu("Bitmap Palette")) { + if (rom()->is_loaded()) { + gui::TextWithSeparators("ROM Palette"); + ImGui::SetNextItemWidth(100.f); + ImGui::Combo("Palette Group", (int *)&edit_palette_group_name_index_, + gfx::kPaletteGroupAddressesKeys, + IM_ARRAYSIZE(gfx::kPaletteGroupAddressesKeys)); + ImGui::SetNextItemWidth(100.f); + gui::InputHexWord("Palette Group Index", &edit_palette_index_); + + auto palette_group = rom()->mutable_palette_group()->get_group( + gfx::kPaletteGroupAddressesKeys[edit_palette_group_name_index_]); + auto palette = palette_group->mutable_palette(edit_palette_index_); + + if (ImGui::BeginChild("Palette", ImVec2(0, 300), true)) { + gui::SelectablePalettePipeline(edit_palette_sub_index_, + refresh_graphics_, *palette); + + if (refresh_graphics_) { + auto status = bitmap->ApplyPaletteWithTransparent( + *palette, edit_palette_sub_index_); + rom()->UpdateBitmap(bitmap); + refresh_graphics_ = false; + } + ImGui::EndChild(); + } + } + EndMenu(); } } ImGui::Separator(); - if (ImGui::BeginMenu("Grid Tile Size")) { - if (ImGui::MenuItem("8x8", nullptr, custom_step_ == 8.0f)) { + if (BeginMenu("Grid Tile Size")) { + if (MenuItem("8x8", nullptr, custom_step_ == 8.0f)) { custom_step_ = 8.0f; } - if (ImGui::MenuItem("16x16", nullptr, custom_step_ == 16.0f)) { + if (MenuItem("16x16", nullptr, custom_step_ == 16.0f)) { custom_step_ = 16.0f; } - if (ImGui::MenuItem("32x32", nullptr, custom_step_ == 32.0f)) { + if (MenuItem("32x32", nullptr, custom_step_ == 32.0f)) { custom_step_ = 32.0f; } - if (ImGui::MenuItem("64x64", nullptr, custom_step_ == 64.0f)) { + if (MenuItem("64x64", nullptr, custom_step_ == 64.0f)) { custom_step_ = 64.0f; } - ImGui::EndMenu(); + EndMenu(); } - // TODO: Add a menu item for selecting the palette ImGui::EndPopup(); } } bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { - const ImGuiIO &io = ImGui::GetIO(); - const bool is_hovered = ImGui::IsItemHovered(); + const ImGuiIO &io = GetIO(); + const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; // Lock scrolled origin const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); @@ -192,7 +242,7 @@ bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { origin.y + painter_pos.y + size * scale)); } - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + if (IsMouseClicked(ImGuiMouseButton_Left)) { // Draw the currently selected tile on the overworld here // Save the coordinates of the selected tile. drawn_tile_pos_ = painter_pos; @@ -211,8 +261,8 @@ bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { } bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) { - const ImGuiIO &io = ImGui::GetIO(); - const bool is_hovered = ImGui::IsItemHovered(); + const ImGuiIO &io = GetIO(); + const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; // Lock scrolled origin const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); @@ -241,10 +291,9 @@ bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) { painter_pos.y = std::clamp(painter_pos.y, 0.0f, canvas_sz_.y * global_scale_); - auto painter_pos_end = ImVec2(painter_pos.x + scaled_tile_size, - painter_pos.y + scaled_tile_size); points_.push_back(painter_pos); - points_.push_back(painter_pos_end); + points_.push_back(ImVec2(painter_pos.x + scaled_tile_size, + painter_pos.y + scaled_tile_size)); draw_list_->AddRectFilled( ImVec2(origin.x + painter_pos.x + 1, origin.y + painter_pos.y + 1), @@ -252,7 +301,7 @@ bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) { origin.y + painter_pos.y + scaled_tile_size), IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255)); - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + if (IsMouseClicked(ImGuiMouseButton_Left)) { is_dragging = true; start_drag_pos = painter_pos; } @@ -292,12 +341,12 @@ void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, } bool Canvas::DrawTileSelector(int size) { - const ImGuiIO &io = ImGui::GetIO(); - const bool is_hovered = ImGui::IsItemHovered(); + const ImGuiIO &io = GetIO(); + const bool is_hovered = IsItemHovered(); 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); - if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + if (is_hovered && IsMouseClicked(ImGuiMouseButton_Left)) { if (!points_.empty()) { points_.clear(); } @@ -317,101 +366,6 @@ bool Canvas::DrawTileSelector(int size) { return false; } -void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, bool ready) { - if (ready) { - draw_list_->AddImage( - (void *)bitmap.texture(), - ImVec2(canvas_p0_.x + border_offset, canvas_p0_.y + border_offset), - ImVec2(canvas_p0_.x + (bitmap.width() * 2), - canvas_p0_.y + (bitmap.height() * 2))); - } -} - -void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, float scale) { - draw_list_->AddImage((void *)bitmap.texture(), - ImVec2(canvas_p0_.x, canvas_p0_.y), - ImVec2(canvas_p0_.x + (bitmap.width() * scale), - canvas_p0_.y + (bitmap.height() * scale))); - draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder); -} - -void Canvas::DrawBitmap(const Bitmap &bitmap, int x_offset, int y_offset, - float scale, int alpha) { - draw_list_->AddImage( - (void *)bitmap.texture(), - ImVec2(canvas_p0_.x + x_offset + scrolling_.x, - canvas_p0_.y + y_offset + scrolling_.y), - ImVec2( - canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale), - canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)), - ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha)); -} - -// TODO: Add parameters for sizing and positioning -void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) { - for (const auto &[key, value] : gfx_bin) { - int offset = 0x40 * (key + 1); - int top_left_y = canvas_p0_.y + 2; - if (key >= 1) { - top_left_y = canvas_p0_.y + 0x40 * key; - } - draw_list_->AddImage((void *)value.texture(), - ImVec2(canvas_p0_.x + 2, top_left_y), - ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset)); - } -} - -void Canvas::DrawOutline(int x, int y, int w, int h) { - ImVec2 origin(canvas_p0_.x + scrolling_.x + x, - canvas_p0_.y + scrolling_.y + y); - ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, - canvas_p0_.y + scrolling_.y + y + h); - draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 200), 0, 0, 1.5f); -} - -void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) { - ImVec2 origin(canvas_p0_.x + scrolling_.x + x, - canvas_p0_.y + scrolling_.y + y); - ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, - canvas_p0_.y + scrolling_.y + y + h); - draw_list_->AddRect(origin, size, - IM_COL32(color.x, color.y, color.z, color.w)); -} - -void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) { - ImVec2 origin(canvas_p0_.x + scrolling_.x + x, - canvas_p0_.y + scrolling_.y + y); - ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, - canvas_p0_.y + scrolling_.y + y + h); - draw_list_->AddRect(origin, size, color); -} - -void Canvas::DrawSelectRectTile16(int current_map) { - 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); - - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { - // Calculate the coordinates of the mouse - ImVec2 painter_pos; - painter_pos.x = std::floor((double)mouse_pos.x / 16) * 16; - painter_pos.y = std::floor((double)mouse_pos.y / 16) * 16; - int painter_x = painter_pos.x; - int painter_y = painter_pos.y; - constexpr int small_map_size = 0x200; - - auto tile16_x = (painter_x % small_map_size) / (small_map_size / 0x20); - auto tile16_y = (painter_y % small_map_size) / (small_map_size / 0x20); - - int superY = current_map / 8; - int superX = current_map % 8; - - int index_x = superX * 0x20 + tile16_x; - int index_y = superY * 0x20 + tile16_y; - selected_tiles_.push_back(ImVec2(index_x, index_y)); - } -} - namespace { ImVec2 AlignPosToGrid(ImVec2 pos, float scale) { return ImVec2(std::floor((double)pos.x / scale) * scale, @@ -420,7 +374,7 @@ ImVec2 AlignPosToGrid(ImVec2 pos, float scale) { } // namespace void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { - const ImGuiIO &io = ImGui::GetIO(); + const ImGuiIO &io = 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); static ImVec2 drag_start_pos; @@ -431,7 +385,7 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { int superX = current_map % 8; // Handle right click for single tile selection - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { + if (IsMouseClicked(ImGuiMouseButton_Right)) { ImVec2 painter_pos = AlignPosToGrid(mouse_pos, scaled_size); int painter_x = painter_pos.x; int painter_y = painter_pos.y; @@ -446,8 +400,7 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { select_rect_active_ = false; // Start drag position for rectangle selection - drag_start_pos = {std::floor(mouse_pos.x / scaled_size) * scaled_size, - std::floor(mouse_pos.y / scaled_size) * scaled_size}; + drag_start_pos = AlignPosToGrid(mouse_pos, scaled_size); } // Calculate the rectangle's top-left and bottom-right corners @@ -508,6 +461,81 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { } } +void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, bool ready) { + if (ready) { + draw_list_->AddImage( + (void *)bitmap.texture(), + ImVec2(canvas_p0_.x + border_offset, canvas_p0_.y + border_offset), + ImVec2(canvas_p0_.x + (bitmap.width() * 2), + canvas_p0_.y + (bitmap.height() * 2))); + } +} + +void Canvas::DrawBitmap(const Bitmap &bitmap, int border_offset, float scale) { + if (!bitmap.is_active()) { + return; + } + draw_list_->AddImage((void *)bitmap.texture(), + ImVec2(canvas_p0_.x, canvas_p0_.y), + ImVec2(canvas_p0_.x + (bitmap.width() * scale), + canvas_p0_.y + (bitmap.height() * scale))); + draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder); +} + +void Canvas::DrawBitmap(const Bitmap &bitmap, int x_offset, int y_offset, + float scale, int alpha) { + if (!bitmap.is_active()) { + return; + } + draw_list_->AddImage( + (void *)bitmap.texture(), + ImVec2(canvas_p0_.x + x_offset + scrolling_.x, + canvas_p0_.y + y_offset + scrolling_.y), + ImVec2( + canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale), + canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)), + ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha)); +} + +// TODO: Add parameters for sizing and positioning +void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) { + for (const auto &[key, value] : gfx_bin) { + int offset = 0x40 * (key + 1); + int top_left_y = canvas_p0_.y + 2; + if (key >= 1) { + top_left_y = canvas_p0_.y + 0x40 * key; + } + draw_list_->AddImage((void *)value.texture(), + ImVec2(canvas_p0_.x + 2, top_left_y), + ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset)); + } +} + +void Canvas::DrawOutline(int x, int y, int w, int h) { + ImVec2 origin(canvas_p0_.x + scrolling_.x + x, + canvas_p0_.y + scrolling_.y + y); + ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, + canvas_p0_.y + scrolling_.y + y + h); + draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 200), 0, 0, 1.5f); +} + +void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) { + ImVec2 origin(canvas_p0_.x + scrolling_.x + x, + canvas_p0_.y + scrolling_.y + y); + ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, + canvas_p0_.y + scrolling_.y + y + h); + draw_list_->AddRect(origin, size, + IM_COL32(color.x, color.y, color.z, color.w)); +} + +void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) { + ImVec2 origin(canvas_p0_.x + scrolling_.x + x, + canvas_p0_.y + scrolling_.y + y); + ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, + canvas_p0_.y + scrolling_.y + y + h); + draw_list_->AddRect(origin, size, color); +} + void Canvas::DrawBitmapGroup(std::vector &group, std::vector &tile16_individual_, int tile_size, float scale) { @@ -563,15 +591,14 @@ void Canvas::DrawBitmapGroup(std::vector &group, } } - const ImGuiIO &io = ImGui::GetIO(); + const ImGuiIO &io = 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); auto new_start_pos = AlignPosToGrid(mouse_pos, tile_size * scale); - auto new_end_pos = - ImVec2(new_start_pos.x + rect_width, new_start_pos.y + rect_height); selected_points_.clear(); selected_points_.push_back(new_start_pos); - selected_points_.push_back(new_end_pos); + selected_points_.push_back( + ImVec2(new_start_pos.x + rect_width, new_start_pos.y + rect_height)); select_rect_active_ = true; } @@ -610,6 +637,39 @@ void Canvas::DrawGridLines(float grid_step) { IM_COL32(200, 200, 200, 50), 0.5f); } +void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) { + // Draw grid + all lines in the canvas + draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); + if (enable_grid_) { + if (custom_step_ != 0.f) grid_step = custom_step_; + grid_step *= global_scale_; // Apply global scale to grid step + + DrawGridLines(grid_step); + + if (enable_custom_labels_) { + // Draw the contents of labels on the grid + for (float x = fmodf(scrolling_.x, grid_step); + x < canvas_sz_.x * global_scale_; x += grid_step) { + for (float y = fmodf(scrolling_.y, grid_step); + y < canvas_sz_.y * global_scale_; y += grid_step) { + int tile_x = (x - scrolling_.x) / grid_step; + int tile_y = (y - scrolling_.y) / grid_step; + int tile_id = tile_x + (tile_y * tile_id_offset); + + if (tile_id >= labels_[label_id].size()) { + break; + } + std::string label = labels_[label_id][tile_id]; + draw_list_->AddText( + ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset, + canvas_p0_.y + y + (grid_step / 2) - tile_id_offset), + IM_COL32(255, 255, 255, 255), label.data()); + } + } + } + } +} + void Canvas::DrawGrid(float grid_step, int tile_id_offset) { // Draw grid + all lines in the canvas draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); @@ -694,6 +754,102 @@ void Canvas::DrawOverlay() { draw_list_->PopClipRect(); } +void Canvas::DrawLayeredElements() { + // Based on ImGui demo, should be adapted to use for OAM + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + { + Text("Blue shape is drawn first: appears in back"); + Text("Red shape is drawn after: appears in front"); + ImVec2 p0 = ImGui::GetCursorScreenPos(); + draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50), + IM_COL32(0, 0, 255, 255)); // Blue + draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25), + ImVec2(p0.x + 75, p0.y + 75), + IM_COL32(255, 0, 0, 255)); // Red + ImGui::Dummy(ImVec2(75, 75)); + } + ImGui::Separator(); + { + Text("Blue shape is drawn first, into channel 1: appears in front"); + Text("Red shape is drawn after, into channel 0: appears in back"); + ImVec2 p1 = ImGui::GetCursorScreenPos(); + + // Create 2 channels and draw a Blue shape THEN a Red shape. + // You can create any number of channels. Tables API use 1 channel per + // column in order to better batch draw calls. + draw_list->ChannelsSplit(2); + draw_list->ChannelsSetCurrent(1); + draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50), + IM_COL32(0, 0, 255, 255)); // Blue + draw_list->ChannelsSetCurrent(0); + draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25), + ImVec2(p1.x + 75, p1.y + 75), + IM_COL32(255, 0, 0, 255)); // Red + + // Flatten/reorder channels. Red shape is in channel 0 and it appears + // below the Blue shape in channel 1. This works by copying draw indices + // only (vertices are not copied). + draw_list->ChannelsMerge(); + ImGui::Dummy(ImVec2(75, 75)); + Text("After reordering, contents of channel 0 appears below channel 1."); + } +} + +void GraphicsBinCanvasPipeline(int width, int height, int tile_size, + int num_sheets_to_load, int canvas_id, + bool is_loaded, gfx::BitmapTable &graphics_bin) { + gui::Canvas canvas; + if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)canvas_id); + ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1)); + canvas.DrawContextMenu(); + if (is_loaded) { + for (const auto &[key, value] : graphics_bin) { + int offset = height * (key + 1); + int top_left_y = canvas.zero_point().y + 2; + if (key >= 1) { + top_left_y = canvas.zero_point().y + height * key; + } + canvas.draw_list()->AddImage( + (void *)value.texture(), + ImVec2(canvas.zero_point().x + 2, top_left_y), + ImVec2(canvas.zero_point().x + 0x100, + canvas.zero_point().y + offset)); + } + } + canvas.DrawTileSelector(tile_size); + canvas.DrawGrid(tile_size); + canvas.DrawOverlay(); + } + ImGui::EndChild(); +} + +void BitmapCanvasPipeline(gui::Canvas &canvas, const gfx::Bitmap &bitmap, + int width, int height, int tile_size, bool is_loaded, + bool scrollbar, int canvas_id) { + auto draw_canvas = [](gui::Canvas &canvas, const gfx::Bitmap &bitmap, + int width, int height, int tile_size, bool is_loaded) { + canvas.DrawBackground(ImVec2(width + 1, height + 1)); + canvas.DrawContextMenu(); + canvas.DrawBitmap(bitmap, 2, is_loaded); + canvas.DrawTileSelector(tile_size); + canvas.DrawGrid(tile_size); + canvas.DrawOverlay(); + }; + + if (scrollbar) { + if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)canvas_id); + ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded); + } + ImGui::EndChild(); + } else { + draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded); + } +} + } // namespace gui } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/gui/canvas.h b/src/app/gui/canvas.h index 107f8154..07a5c4f4 100644 --- a/src/app/gui/canvas.h +++ b/src/app/gui/canvas.h @@ -1,7 +1,7 @@ #ifndef YAZE_GUI_CANVAS_H #define YAZE_GUI_CANVAS_H -#include +#include "imgui/imgui.h" #include #include @@ -33,13 +33,17 @@ enum class CanvasGridSize { k8x8, k16x16, k32x32, k64x64 }; * on a canvas. It supports features such as bitmap drawing, context menu * handling, tile painting, custom grid, and more. */ -class Canvas { +class Canvas : public SharedRom { public: Canvas() = default; - explicit Canvas(ImVec2 canvas_size) - : custom_canvas_size_(true), canvas_sz_(canvas_size) {} - explicit Canvas(ImVec2 canvas_size, CanvasGridSize grid_size) - : custom_canvas_size_(true), canvas_sz_(canvas_size) { + explicit Canvas(const std::string& id, ImVec2 canvas_size) + : canvas_id_(id), custom_canvas_size_(true), canvas_sz_(canvas_size) { + context_id_ = id + "Context"; + } + explicit Canvas(const std::string& id, ImVec2 canvas_size, + CanvasGridSize grid_size) + : canvas_id_(id), custom_canvas_size_(true), canvas_sz_(canvas_size) { + context_id_ = id + "Context"; switch (grid_size) { case CanvasGridSize::k8x8: custom_step_ = 8.0f; @@ -56,18 +60,12 @@ class Canvas { } } - void Update(const gfx::Bitmap& bitmap, ImVec2 bg_size, int tile_size, - float scale = 1.0f, float grid_size = 64.0f); - void UpdateColorPainter(gfx::Bitmap& bitmap, const ImVec4& color, const std::function& event, int tile_size, float scale = 1.0f); - void UpdateEvent(const std::function& event, ImVec2 bg_size, - int tile_size, float scale = 1.0f, float grid_size = 64.0f); - void UpdateInfoGrid(ImVec2 bg_size, int tile_size, float scale = 1.0f, - float grid_size = 64.0f); + float grid_size = 64.0f, int label_id = 0); // Background for the Canvas represents region without any content drawn to // it, but can be controlled by the user. @@ -90,6 +88,10 @@ class Canvas { // in the canvas window. Represented and split apart into a grid of tiles. bool DrawTileSelector(int size); + // Draws the selection rectangle when the user is selecting multiple tiles + void DrawSelectRect(int current_map, int tile_size = 0x10, + float scale = 1.0f); + // Draws the contents of the Bitmap image to the Canvas void DrawBitmap(const Bitmap& bitmap, int border_offset = 0, bool ready = true); @@ -106,16 +108,29 @@ class Canvas { void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color); void DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color); - void DrawSelectRect(int current_map, int tile_size = 0x10, - float scale = 1.0f); - void DrawSelectRectTile16(int current_map); - void DrawRect(int x, int y, int w, int h, ImVec4 color); void DrawText(std::string text, int x, int y); void DrawGridLines(float grid_step); void DrawGrid(float grid_step = 64.0f, int tile_id_offset = 8); void DrawOverlay(); // last + + void DrawInfoGrid(float grid_step = 64.0f, int tile_id_offset = 8, + int label_id = 0); + + void DrawLayeredElements(); + + int GetTileIdFromMousePos() { + int x = mouse_pos_in_canvas_.x; + int y = mouse_pos_in_canvas_.y; + int num_columns = canvas_sz_.x / custom_step_; + int num_rows = canvas_sz_.y / custom_step_; + int tile_id = (x / custom_step_) + (y / custom_step_) * num_columns; + if (tile_id >= num_columns * num_rows) { + tile_id = -1; // Invalid tile ID + } + return tile_id; + } void SetCanvasSize(ImVec2 canvas_size) { canvas_sz_ = canvas_size; custom_canvas_size_ = true; @@ -148,23 +163,16 @@ class Canvas { } auto mutable_labels(int i) { if (i >= labels_.size()) { + int x = i; + while (x >= labels_.size()) { + labels_.push_back(ImVector()); + x--; + } labels_.push_back(ImVector()); } return &labels_[i]; } - int GetTileIdFromMousePos() { - int x = mouse_pos_in_canvas_.x; - int y = mouse_pos_in_canvas_.y; - int num_columns = width() / custom_step_; - int num_rows = height() / custom_step_; - int tile_id = (x / custom_step_) + (y / custom_step_) * num_columns; - if (tile_id >= num_columns * num_rows) { - tile_id = -1; // Invalid tile ID - } - return tile_id; - } - auto set_current_labels(int i) { current_labels_ = i; } auto set_highlight_tile_id(int i) { highlight_tile_id = i; } @@ -180,12 +188,13 @@ class Canvas { private: bool draggable_ = false; + bool is_hovered_ = false; bool enable_grid_ = true; bool enable_hex_tile_labels_ = false; bool enable_custom_labels_ = false; bool enable_context_menu_ = true; bool custom_canvas_size_ = false; - bool is_hovered_ = false; + bool select_rect_active_ = false; float custom_step_ = 0.0f; float global_scale_ = 1.0f; @@ -193,6 +202,14 @@ class Canvas { int current_labels_ = 0; int highlight_tile_id = -1; + uint16_t edit_palette_index_ = 0; + uint64_t edit_palette_group_name_index_ = 0; + uint64_t edit_palette_sub_index_ = 0; + bool refresh_graphics_ = false; + + std::string canvas_id_ = "Canvas"; + std::string context_id_ = "CanvasContext"; + ImDrawList* draw_list_; ImVector points_; ImVector> labels_; @@ -200,17 +217,23 @@ class Canvas { ImVec2 canvas_sz_; ImVec2 canvas_p0_; ImVec2 canvas_p1_; - ImVec2 mouse_pos_in_canvas_; ImVec2 drawn_tile_pos_; - - bool select_rect_active_ = false; + ImVec2 mouse_pos_in_canvas_; ImVec2 selected_tile_pos_ = ImVec2(-1, -1); ImVector selected_points_; std::vector selected_tiles_; }; +void GraphicsBinCanvasPipeline(int width, int height, int tile_size, + int num_sheets_to_load, int canvas_id, + bool is_loaded, BitmapTable& graphics_bin); + +void BitmapCanvasPipeline(gui::Canvas& canvas, const gfx::Bitmap& bitmap, + int width, int height, int tile_size, bool is_loaded, + bool scrollbar, int canvas_id); + } // namespace gui } // namespace app } // namespace yaze -#endif \ No newline at end of file +#endif diff --git a/src/app/gui/color.cc b/src/app/gui/color.cc index ccf96c94..55541bed 100644 --- a/src/app/gui/color.cc +++ b/src/app/gui/color.cc @@ -1,12 +1,13 @@ #include "color.h" -#include +#include "imgui/imgui.h" #include #include #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" +#include "app/gfx/snes_color.h" namespace yaze { namespace app { @@ -24,30 +25,30 @@ ImVec4 ConvertSNESColorToImVec4(const SnesColor& color) { IMGUI_API bool SnesColorButton(absl::string_view id, SnesColor& color, ImGuiColorEditFlags flags, const ImVec2& size_arg) { - // Convert the SNES color values to ImGui color values (normalized to 0-1 - // range) + // Convert the SNES color values to ImGui color values ImVec4 displayColor = ConvertSNESColorToImVec4(color); // Call the original ImGui::ColorButton with the converted color bool pressed = ImGui::ColorButton(id.data(), displayColor, flags, size_arg); - + // Add the SNES color representation to the tooltip + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("SNES: $%04X", color.snes()); + ImGui::EndTooltip(); + } return pressed; } -IMGUI_API bool SnesColorEdit4(absl::string_view label, SnesColor& color, +IMGUI_API bool SnesColorEdit4(absl::string_view label, SnesColor* color, ImGuiColorEditFlags flags) { - // Convert the SNES color values to ImGui color values (normalized to 0-1 - // range) - ImVec4 displayColor = ConvertSNESColorToImVec4(color); + ImVec4 displayColor = ConvertSNESColorToImVec4(*color); // Call the original ImGui::ColorEdit4 with the converted color - bool pressed = ImGui::ColorEdit4(label.data(), (float*)&displayColor, flags); + bool pressed = + ImGui::ColorEdit4(label.data(), (float*)&displayColor.x, flags); - // Convert the ImGui color values back to SNES color values (normalized to - // 0-255 range) - color = SnesColor(static_cast(displayColor.x * 255.0f), - static_cast(displayColor.y * 255.0f), - static_cast(displayColor.z * 255.0f)); + color->set_rgb(displayColor); + color->set_snes(gfx::ConvertRGBtoSNES(displayColor)); return pressed; } @@ -122,8 +123,52 @@ absl::Status DisplayPalette(app::gfx::SnesPalette& palette, bool loaded) { ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); + return absl::OkStatus(); +} + +void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics, + gfx::SnesPalette& palette) { + const auto palette_row_size = 7; + 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 < palette.size(); n++) { + ImGui::PushID(n); + if ((n % palette_row_size) != 0) + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + + // Check if the current row is selected + bool is_selected = (palette_id == n / palette_row_size); + + // Add outline rectangle to the selected row + if (is_selected) { + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); + } + + if (gui::SnesColorButton("##palette", palette[n], + ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_NoPicker | + ImGuiColorEditFlags_NoTooltip, + ImVec2(20, 20))) { + palette_id = n / palette_row_size; + refresh_graphics = true; + } + + if (is_selected) { + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + } + + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::EndChild(); } } // namespace gui } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/gui/color.h b/src/app/gui/color.h index 1db45db5..6e05a008 100644 --- a/src/app/gui/color.h +++ b/src/app/gui/color.h @@ -1,7 +1,7 @@ #ifndef YAZE_GUI_COLOR_H #define YAZE_GUI_COLOR_H -#include +#include "imgui/imgui.h" #include #include @@ -25,13 +25,16 @@ IMGUI_API bool SnesColorButton(absl::string_view id, SnesColor& color, ImGuiColorEditFlags flags = 0, const ImVec2& size_arg = ImVec2(0, 0)); -IMGUI_API bool SnesColorEdit4(absl::string_view label, SnesColor& color, +IMGUI_API bool SnesColorEdit4(absl::string_view label, SnesColor* color, ImGuiColorEditFlags flags = 0); absl::Status DisplayPalette(app::gfx::SnesPalette& palette, bool loaded); +void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics, + gfx::SnesPalette& palette); + } // namespace gui } // namespace app } // namespace yaze -#endif \ No newline at end of file +#endif diff --git a/src/app/gui/input.cc b/src/app/gui/input.cc index 4da0965a..06e39d64 100644 --- a/src/app/gui/input.cc +++ b/src/app/gui/input.cc @@ -1,10 +1,20 @@ #include "input.h" -#include -#include -#include +#include "ImGuiFileDialog/ImGuiFileDialog.h" +#include "imgui/imgui.h" +#include "imgui/imgui_internal.h" +#include "imgui/misc/cpp/imgui_stdlib.h" + +#include +#include +#include #include "absl/strings/string_view.h" +#include "app/core/common.h" +#include "app/gfx/bitmap.h" +#include "app/gfx/snes_palette.h" +#include "app/gui/canvas.h" +#include "app/gui/color.h" namespace ImGui { @@ -45,9 +55,9 @@ bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data, // value_changed = DataTypeApplyFromText(buf, data_type, p_data, format); // } else { const float button_size = GetFrameHeight(); - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", label); - ImGui::SameLine(); + AlignTextToFramePadding(); + Text("%s", label); + SameLine(); BeginGroup(); // The only purpose of the group here is to allow the caller // to query item data e.g. IsItemActive() PushID(label); @@ -55,12 +65,12 @@ bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data, 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); // Place the label on the left of the input field - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - ImVec2{style.ItemSpacing.x, style.ItemSpacing.y}); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2{style.FramePadding.x, style.FramePadding.y}); + PushStyleVar(ImGuiStyleVar_ItemSpacing, + ImVec2{style.ItemSpacing.x, style.ItemSpacing.y}); + PushStyleVar(ImGuiStyleVar_FramePadding, + ImVec2{style.FramePadding.x, style.FramePadding.y}); - ImGui::SetNextItemWidth(input_width); + SetNextItemWidth(input_width); if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID // from outside point of view @@ -171,6 +181,19 @@ bool InputHexByte(const char* label, uint8_t* data, float input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step); } +bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value, + float input_width, bool no_step) { + if (ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex, + &kStepFastHex, "%02X", input_width, + ImGuiInputTextFlags_CharsHexadecimal, no_step)) { + if (*data > max_value) { + *data = max_value; + } + return true; + } + return false; +} + void ItemLabel(absl::string_view title, ItemLabelFlags flags) { ImGuiWindow* window = ImGui::GetCurrentWindow(); const ImVec2 lineStart = ImGui::GetCursorScreenPos(); @@ -226,6 +249,26 @@ bool ListBox(const char* label, int* current_item, height_in_items); } +ImGuiID GetID(const std::string& id) { return ImGui::GetID(id.c_str()); } + +void FileDialogPipeline(absl::string_view display_key, + absl::string_view file_extensions, + std::optional button_text, + std::function callback) { + if (button_text.has_value() && ImGui::Button(button_text->data())) { + ImGuiFileDialog::Instance()->OpenDialog(display_key.data(), "Choose File", + file_extensions.data(), "."); + } + + if (ImGuiFileDialog::Instance()->Display( + display_key.data(), ImGuiWindowFlags_NoCollapse, ImVec2(600, 400))) { + if (ImGuiFileDialog::Instance()->IsOk()) { + callback(); + } + ImGuiFileDialog::Instance()->Close(); + } +} + } // namespace gui } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/gui/input.h b/src/app/gui/input.h index 823b4a1c..cd7a024b 100644 --- a/src/app/gui/input.h +++ b/src/app/gui/input.h @@ -1,13 +1,25 @@ #ifndef YAZE_APP_CORE_INPUT_H #define YAZE_APP_CORE_INPUT_H -#include +#define IMGUI_DEFINE_MATH_OPERATORS + +#include "imgui/imgui.h" +#include "imgui/misc/cpp/imgui_stdlib.h" +#include "imgui_memory_editor.h" #include #include +#include +#include +#include #include #include "absl/strings/string_view.h" +#include "app/core/common.h" +#include "app/gfx/bitmap.h" +#include "app/gfx/snes_palette.h" +#include "app/gui/canvas.h" +#include "app/gui/color.h" namespace yaze { namespace app { @@ -16,13 +28,10 @@ namespace gui { constexpr ImVec2 kDefaultModalSize = ImVec2(200, 0); constexpr ImVec2 kZeroPos = ImVec2(0, 0); -IMGUI_API bool InputHexWithScrollwheel(const char* label, uint32_t* data, - uint32_t step = 0x01, - float input_width = 50.f); - IMGUI_API bool InputHex(const char* label, uint64_t* data); IMGUI_API bool InputHex(const char* label, int* data, int num_digits = 4, float input_width = 50.f); + IMGUI_API bool InputHexShort(const char* label, uint32_t* data); IMGUI_API bool InputHexWord(const char* label, uint16_t* data, float input_width = 50.f, bool no_step = false); @@ -31,6 +40,9 @@ IMGUI_API bool InputHexWord(const char* label, int16_t* data, IMGUI_API bool InputHexByte(const char* label, uint8_t* data, float input_width = 50.f, bool no_step = false); +IMGUI_API bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value, + float input_width = 50.f, bool no_step = false); + IMGUI_API bool ListBox(const char* label, int* current_item, const std::vector& items, int height_in_items = -1); @@ -43,8 +55,15 @@ using ItemLabelFlags = enum ItemLabelFlag { IMGUI_API void ItemLabel(absl::string_view title, ItemLabelFlags flags); +IMGUI_API ImGuiID GetID(const std::string& id); + +void FileDialogPipeline(absl::string_view display_key, + absl::string_view file_extensions, + std::optional button_text, + std::function callback); + } // namespace gui } // namespace app } // namespace yaze -#endif \ No newline at end of file +#endif diff --git a/src/app/gui/pipeline.cc b/src/app/gui/pipeline.cc deleted file mode 100644 index 8df6536d..00000000 --- a/src/app/gui/pipeline.cc +++ /dev/null @@ -1,180 +0,0 @@ -#include "pipeline.h" - -#include -#include -#include -#include - -#include -#include - -#include "absl/strings/string_view.h" -#include "app/core/common.h" -#include "app/gfx/bitmap.h" -#include "app/gfx/snes_palette.h" -#include "app/gui/canvas.h" -#include "app/gui/color.h" -#include "app/gui/input.h" - -namespace yaze { -namespace app { -namespace gui { - -void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics, - gfx::SnesPalette& palette) { - const auto palette_row_size = 7; - 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 < palette.size(); n++) { - ImGui::PushID(n); - if ((n % palette_row_size) != 0) - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); - - // Check if the current row is selected - bool is_selected = (palette_id == n / palette_row_size); - - // Add outline rectangle to the selected row - if (is_selected) { - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); - } - - if (gui::SnesColorButton("##palette", palette[n], - ImGuiColorEditFlags_NoAlpha | - ImGuiColorEditFlags_NoPicker | - ImGuiColorEditFlags_NoTooltip, - ImVec2(20, 20))) { - palette_id = n / palette_row_size; - refresh_graphics = true; - } - - if (is_selected) { - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - } - - ImGui::PopID(); - } - ImGui::EndGroup(); - } - ImGui::EndChild(); -} - -void GraphicsBinCanvasPipeline(int width, int height, int tile_size, - int num_sheets_to_load, int canvas_id, - bool is_loaded, gfx::BitmapTable& graphics_bin) { - gui::Canvas canvas; - - if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)canvas_id); - ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar)) { - canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1)); - canvas.DrawContextMenu(); - if (is_loaded) { - for (const auto& [key, value] : graphics_bin) { - int offset = height * (key + 1); - int top_left_y = canvas.zero_point().y + 2; - if (key >= 1) { - top_left_y = canvas.zero_point().y + height * key; - } - canvas.draw_list()->AddImage( - (void*)value.texture(), - ImVec2(canvas.zero_point().x + 2, top_left_y), - ImVec2(canvas.zero_point().x + 0x100, - canvas.zero_point().y + offset)); - } - } - canvas.DrawTileSelector(tile_size); - canvas.DrawGrid(tile_size); - canvas.DrawOverlay(); - } - ImGui::EndChild(); -} - -void GraphicsManagerCanvasPipeline(int width, int height, int tile_size, - int num_sheets, int canvas_id, - bool is_loaded, - const gfx::BitmapManager& graphics_manager) { - gui::Canvas canvas; - - if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)canvas_id); - ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar)) { - canvas.DrawBackground(ImVec2(width + 1, num_sheets * height + 1)); - canvas.DrawContextMenu(); - if (is_loaded) { - for (const auto& [key, value] : graphics_manager) { - int offset = height * (key + 1); - int top_left_y = canvas.zero_point().y + 2; - if (key >= 1) { - top_left_y = canvas.zero_point().y + height * key; - } - canvas.draw_list()->AddImage( - (void*)value->texture(), - ImVec2(canvas.zero_point().x + 2, top_left_y), - ImVec2(canvas.zero_point().x + 0x100, - canvas.zero_point().y + offset)); - } - } - canvas.DrawTileSelector(tile_size); - canvas.DrawGrid(tile_size); - canvas.DrawOverlay(); - } - ImGui::EndChild(); -} - -void ButtonPipe(absl::string_view button_text, std::function callback) { - if (ImGui::Button(button_text.data())) { - callback(); - } -} - -void BitmapCanvasPipeline(gui::Canvas& canvas, const gfx::Bitmap& bitmap, - int width, int height, int tile_size, bool is_loaded, - bool scrollbar, int canvas_id) { - auto draw_canvas = [](gui::Canvas& canvas, const gfx::Bitmap& bitmap, - int width, int height, int tile_size, bool is_loaded) { - canvas.DrawBackground(ImVec2(width + 1, height + 1)); - canvas.DrawContextMenu(); - canvas.DrawBitmap(bitmap, 2, is_loaded); - canvas.DrawTileSelector(tile_size); - canvas.DrawGrid(tile_size); - canvas.DrawOverlay(); - }; - - if (scrollbar) { - if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)canvas_id); - ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, - ImGuiWindowFlags_AlwaysVerticalScrollbar)) { - draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded); - } - ImGui::EndChild(); - } else { - draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded); - } -} - -void FileDialogPipeline(absl::string_view display_key, - absl::string_view file_extensions, - std::optional button_text, - std::function callback) { - if (button_text.has_value() && ImGui::Button(button_text->data())) { - ImGuiFileDialog::Instance()->OpenDialog(display_key.data(), "Choose File", - file_extensions.data(), "."); - } - - if (ImGuiFileDialog::Instance()->Display( - display_key.data(), ImGuiWindowFlags_NoCollapse, ImVec2(600, 400))) { - if (ImGuiFileDialog::Instance()->IsOk()) { - callback(); - } - ImGuiFileDialog::Instance()->Close(); - } -} - -} // namespace gui -} // namespace app -} // namespace yaze \ No newline at end of file diff --git a/src/app/gui/pipeline.h b/src/app/gui/pipeline.h deleted file mode 100644 index 7cfbbe35..00000000 --- a/src/app/gui/pipeline.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef YAZE_APP_CORE_PIPELINE_H -#define YAZE_APP_CORE_PIPELINE_H - -#include -#include -#include -#include - -#include -#include - -#include "absl/strings/string_view.h" -#include "app/core/constants.h" -#include "app/gfx/bitmap.h" -#include "app/gfx/snes_palette.h" -#include "app/gui/canvas.h" - -namespace yaze { -namespace app { -namespace gui { - -void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics, - gfx::SnesPalette& palette); - -void GraphicsBinCanvasPipeline(int width, int height, int tile_size, - int num_sheets_to_load, int canvas_id, - bool is_loaded, gfx::BitmapTable& graphics_bin); - -void ButtonPipe(absl::string_view button_text, std::function callback); - -void BitmapCanvasPipeline(gui::Canvas& canvas, const gfx::Bitmap& bitmap, - int width, int height, int tile_size, bool is_loaded, - bool scrollbar, int canvas_id); - -void GraphicsManagerCanvasPipeline(int width, int height, int tile_size, - int num_sheets, int canvas_id, - bool is_loaded, - const gfx::BitmapManager& graphics_manager); - -void FileDialogPipeline(absl::string_view display_key, - absl::string_view file_extensions, - std::optional button_text, - std::function callback); - -} // namespace core -} // namespace app -} // namespace yaze - -#endif \ No newline at end of file diff --git a/src/app/gui/style.cc b/src/app/gui/style.cc index 7ce91b32..c9639717 100644 --- a/src/app/gui/style.cc +++ b/src/app/gui/style.cc @@ -12,7 +12,7 @@ void BeginWindowWithDisplaySettings(const char* id, bool* active, ImGuiWindowFlags flags) { ImGuiStyle* ref = &ImGui::GetStyle(); static float childBgOpacity = 0.75f; - auto color = ImVec4(0.f, 0.f, 0.f, childBgOpacity); + auto color = ref->Colors[ImGuiCol_WindowBg]; ImGui::PushStyleColor(ImGuiCol_WindowBg, color); ImGui::PushStyleColor(ImGuiCol_ChildBg, color); @@ -168,11 +168,6 @@ void DrawDisplaySettings(ImGuiStyle* ref) { ImGui::SeparatorText("Widgets"); ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - int window_menu_button_position = style.WindowMenuButtonPosition + 1; - if (ImGui::Combo("WindowMenuButtonPosition", - (int*)&window_menu_button_position, - "None\0Left\0Right\0")) - style.WindowMenuButtonPosition = window_menu_button_position - 1; ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, @@ -522,6 +517,95 @@ void ColorsYaze() { colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); } +void RenderTabItem(const std::string& title, + const std::function& render_func) { + if (ImGui::BeginTabItem(title.c_str())) { + render_func(); + ImGui::EndTabItem(); + } +} + +// ============================================================================ +// 65816 LanguageDefinition +// ============================================================================ + +static const char* const kKeywords[] = { + "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", + "BPL", "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV", + "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", + "INY", "JMP", "JSR", "JSL", "LDA", "LDX", "LDY", "LSR", "MVN", + "NOP", "ORA", "PEA", "PER", "PHA", "PHB", "PHD", "PHP", "PHX", + "PHY", "PLA", "PLB", "PLD", "PLP", "PLX", "PLY", "REP", "ROL", + "ROR", "RTI", "RTL", "RTS", "SBC", "SEC", "SEI", "SEP", "STA", + "STP", "STX", "STY", "STZ", "TAX", "TAY", "TCD", "TCS", "TDC", + "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA", "TYX", + "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM", "NAMESPACE", "DB"}; + +static const char* const kIdentifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", + "atof", "atoi", "atol", "ceil", "clock", "cosh", + "ctime", "div", "exit", "fabs", "floor", "fmod", + "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", + "log", "memcmp", "modf", "pow", "putchar", "putenv", + "puts", "rand", "remove", "rename", "sinh", "sqrt", + "srand", "strcat", "strcmp", "strerror", "time", "tolower", + "toupper"}; + +TextEditor::LanguageDefinition GetAssemblyLanguageDef() { + TextEditor::LanguageDefinition language_65816; + for (auto& k : kKeywords) language_65816.mKeywords.emplace(k); + + for (auto& k : kIdentifiers) { + TextEditor::Identifier id; + id.mDeclaration = "Built-in function"; + language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "[ \\t]*#[ \\t]*[a-zA-Z_]+", TextEditor::PaletteIndex::Preprocessor)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "L?\\\"(\\\\.|[^\\\"])*\\\"", TextEditor::PaletteIndex::String)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "\\'\\\\?[^\\']\\'", TextEditor::PaletteIndex::CharLiteral)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", + TextEditor::PaletteIndex::Number)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "[+-]?[0-9]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "0[0-7]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", + TextEditor::PaletteIndex::Number)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "[a-zA-Z_][a-zA-Z0-9_]*", TextEditor::PaletteIndex::Identifier)); + language_65816.mTokenRegexStrings.push_back( + std::make_pair( + "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/" + "\\;\\,\\.]", + TextEditor::PaletteIndex::Punctuation)); + + language_65816.mCommentStart = "/*"; + language_65816.mCommentEnd = "*/"; + language_65816.mSingleLineComment = ";"; + + language_65816.mCaseSensitive = false; + language_65816.mAutoIndentation = true; + + language_65816.mName = "65816"; + + return language_65816; +} + } // namespace gui } // namespace app -} // namespace yaze \ No newline at end of file +} // namespace yaze diff --git a/src/app/gui/style.h b/src/app/gui/style.h index ad726983..0fed9c1a 100644 --- a/src/app/gui/style.h +++ b/src/app/gui/style.h @@ -1,9 +1,17 @@ #ifndef YAZE_APP_CORE_STYLE_H #define YAZE_APP_CORE_STYLE_H -#include +#include "ImGuiColorTextEdit/TextEditor.h" +#include "imgui/imgui.h" +#include "imgui/misc/cpp/imgui_stdlib.h" +#include +#include + +#include "absl/status/status.h" #include "absl/strings/string_view.h" +#include "app/core/constants.h" +#include "app/gfx/bitmap.h" namespace yaze { namespace app { @@ -21,7 +29,7 @@ void EndPadding(); void BeginNoPadding(); void EndNoPadding(); -void BeginChildWithScrollbar(const char *str_id); +void BeginChildWithScrollbar(const char* str_id); void BeginChildBothScrollbars(int id); @@ -31,8 +39,113 @@ void TextWithSeparators(const absl::string_view& text); void ColorsYaze(); +TextEditor::LanguageDefinition GetAssemblyLanguageDef(); + +void RenderTabItem(const std::string& title, + const std::function& render_func); + +class BitmapViewer { + public: + BitmapViewer() : current_bitmap_index_(0) {} + + void Display(const std::vector& bitmaps, float scale = 1.0f) { + if (bitmaps.empty()) { + ImGui::Text("No bitmaps available."); + return; + } + + // Display the current bitmap index and total count. + ImGui::Text("Viewing Bitmap %d / %zu", current_bitmap_index_ + 1, + bitmaps.size()); + + // Buttons to navigate through bitmaps. + if (ImGui::Button("<- Prev")) { + if (current_bitmap_index_ > 0) { + --current_bitmap_index_; + } + } + ImGui::SameLine(); + if (ImGui::Button("Next ->")) { + if (current_bitmap_index_ < bitmaps.size() - 1) { + ++current_bitmap_index_; + } + } + + // Display the current bitmap. + const gfx::Bitmap& current_bitmap = bitmaps[current_bitmap_index_]; + // Assuming Bitmap has a function to get its texture ID, and width and + // height. + ImTextureID tex_id = current_bitmap.texture(); + ImVec2 size(current_bitmap.width() * scale, + current_bitmap.height() * scale); + // ImGui::Image(tex_id, size); + + // Scroll if the image is larger than the display area. + if (ImGui::BeginChild("BitmapScrollArea", ImVec2(0, 0), false, + ImGuiWindowFlags_HorizontalScrollbar)) { + ImGui::Image(tex_id, size); + ImGui::EndChild(); + } + } + + private: + int current_bitmap_index_; +}; + +// ============================================================================ + +static const char* ExampleNames[] = { + "Artichoke", "Arugula", "Asparagus", "Avocado", + "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", + "Belgian Endive", "Bell Pepper", "Bitter Gourd", "Bok Choy", + "Broccoli", "Brussels Sprouts", "Burdock Root", "Cabbage", + "Calabash", "Capers", "Carrot", "Cassava", + "Cauliflower", "Celery", "Celery Root", "Celcuce", + "Chayote", "Chinese Broccoli", "Corn", "Cucumber"}; + +struct MultiSelectWithClipper { + const int ITEMS_COUNT = 10000; + void Update() { + // Use default selection.Adapter: Pass index to + // SetNextItemSelectionUserData(), store index in Selection + static ImGuiSelectionBasicStorage selection; + + ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + if (ImGui::BeginChild( + "##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), + ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | + ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = + ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); + + ImGuiListClipper clipper; + clipper.Begin(ITEMS_COUNT); + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex( + (int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + while (clipper.Step()) { + for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++) { + char label[64]; + // sprintf(label, "Object %05d: %s", n, + // ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + } + } + + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + } + ImGui::EndChild(); + ImGui::TreePop(); + } +}; + } // namespace gui } // namespace app } // namespace yaze -#endif \ No newline at end of file +#endif diff --git a/src/app/gui/widgets.cc b/src/app/gui/widgets.cc deleted file mode 100644 index b269708c..00000000 --- a/src/app/gui/widgets.cc +++ /dev/null @@ -1,104 +0,0 @@ -#include "widgets.h" - -#include -#include - -#include "absl/status/status.h" -#include "app/core/constants.h" - -namespace yaze { -namespace app { -namespace gui { - -void RenderTabItem(const std::string &title, - const std::function &render_func) { - if (ImGui::BeginTabItem(title.c_str())) { - render_func(); - ImGui::EndTabItem(); - } -} - -// ============================================================================ -// 65816 LanguageDefinition -// ============================================================================ - -static const char *const kKeywords[] = { - "ADC", "AND", "ASL", "BCC", "BCS", "BEQ", "BIT", "BMI", "BNE", - "BPL", "BRA", "BRL", "BVC", "BVS", "CLC", "CLD", "CLI", "CLV", - "CMP", "CPX", "CPY", "DEC", "DEX", "DEY", "EOR", "INC", "INX", - "INY", "JMP", "JSR", "JSL", "LDA", "LDX", "LDY", "LSR", "MVN", - "NOP", "ORA", "PEA", "PER", "PHA", "PHB", "PHD", "PHP", "PHX", - "PHY", "PLA", "PLB", "PLD", "PLP", "PLX", "PLY", "REP", "ROL", - "ROR", "RTI", "RTL", "RTS", "SBC", "SEC", "SEI", "SEP", "STA", - "STP", "STX", "STY", "STZ", "TAX", "TAY", "TCD", "TCS", "TDC", - "TRB", "TSB", "TSC", "TSX", "TXA", "TXS", "TXY", "TYA", "TYX", - "WAI", "WDM", "XBA", "XCE", "ORG", "LOROM", "HIROM", "NAMESPACE", "DB"}; - -static const char *const kIdentifiers[] = { - "abort", "abs", "acos", "asin", "atan", "atexit", - "atof", "atoi", "atol", "ceil", "clock", "cosh", - "ctime", "div", "exit", "fabs", "floor", "fmod", - "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", - "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", - "log", "memcmp", "modf", "pow", "putchar", "putenv", - "puts", "rand", "remove", "rename", "sinh", "sqrt", - "srand", "strcat", "strcmp", "strerror", "time", "tolower", - "toupper"}; - -TextEditor::LanguageDefinition GetAssemblyLanguageDef() { - TextEditor::LanguageDefinition language_65816; - for (auto &k : kKeywords) language_65816.mKeywords.emplace(k); - - for (auto &k : kIdentifiers) { - TextEditor::Identifier id; - id.mDeclaration = "Built-in function"; - language_65816.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "[ \\t]*#[ \\t]*[a-zA-Z_]+", TextEditor::PaletteIndex::Preprocessor)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "L?\\\"(\\\\.|[^\\\"])*\\\"", TextEditor::PaletteIndex::String)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "\\'\\\\?[^\\']\\'", TextEditor::PaletteIndex::CharLiteral)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", - TextEditor::PaletteIndex::Number)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "[+-]?[0-9]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "0[0-7]+[Uu]?[lL]?[lL]?", TextEditor::PaletteIndex::Number)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", - TextEditor::PaletteIndex::Number)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "[a-zA-Z_][a-zA-Z0-9_]*", TextEditor::PaletteIndex::Identifier)); - language_65816.mTokenRegexStrings.push_back( - std::make_pair( - "[\\[\\]\\{\\}\\!\\%\\^\\&\\*\\(\\)\\-\\+\\=\\~\\|\\<\\>\\?\\/" - "\\;\\,\\.]", - TextEditor::PaletteIndex::Punctuation)); - - language_65816.mCommentStart = "/*"; - language_65816.mCommentEnd = "*/"; - language_65816.mSingleLineComment = ";"; - - language_65816.mCaseSensitive = false; - language_65816.mAutoIndentation = true; - - language_65816.mName = "65816"; - - return language_65816; -} - -} // namespace gui -} // namespace app -} // namespace yaze diff --git a/src/app/gui/widgets.h b/src/app/gui/widgets.h deleted file mode 100644 index c04cd60b..00000000 --- a/src/app/gui/widgets.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef YAZE_GUI_WIDGETS_H -#define YAZE_GUI_WIDGETS_H - -#include -#include -#include - -#include -#include - -#include "absl/status/status.h" -#include "app/core/constants.h" -#include "app/gfx/bitmap.h" - -namespace yaze { -namespace app { -namespace gui { - -class DynamicLayout {}; - -TextEditor::LanguageDefinition GetAssemblyLanguageDef(); - -void RenderTabItem(const std::string& title, - const std::function& render_func); - -class BitmapViewer { - public: - BitmapViewer() : current_bitmap_index_(0) {} - - void Display(const std::vector& bitmaps, float scale = 1.0f) { - if (bitmaps.empty()) { - ImGui::Text("No bitmaps available."); - return; - } - - // Display the current bitmap index and total count. - ImGui::Text("Viewing Bitmap %d / %zu", current_bitmap_index_ + 1, - bitmaps.size()); - - // Buttons to navigate through bitmaps. - if (ImGui::Button("<- Prev")) { - if (current_bitmap_index_ > 0) { - --current_bitmap_index_; - } - } - ImGui::SameLine(); - if (ImGui::Button("Next ->")) { - if (current_bitmap_index_ < bitmaps.size() - 1) { - ++current_bitmap_index_; - } - } - - // Display the current bitmap. - const gfx::Bitmap& current_bitmap = bitmaps[current_bitmap_index_]; - // Assuming Bitmap has a function to get its texture ID, and width and - // height. - ImTextureID tex_id = current_bitmap.texture(); - ImVec2 size(current_bitmap.width() * scale, - current_bitmap.height() * scale); - // ImGui::Image(tex_id, size); - - // Scroll if the image is larger than the display area. - if (ImGui::BeginChild("BitmapScrollArea", ImVec2(0, 0), false, - ImGuiWindowFlags_HorizontalScrollbar)) { - ImGui::Image(tex_id, size); - ImGui::EndChild(); - } - } - - private: - int current_bitmap_index_; -}; - -} // namespace gui -} // namespace app -} // namespace yaze - -#endif \ No newline at end of file diff --git a/src/app/gui/zeml.cc b/src/app/gui/zeml.cc new file mode 100644 index 00000000..f7e3b9ab --- /dev/null +++ b/src/app/gui/zeml.cc @@ -0,0 +1,622 @@ + +#include "app/gui/zeml.h" + +#include "imgui/imgui.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "app/gui/canvas.h" +#include "app/gui/input.h" +#include "app/core/platform/file_path.h" + +namespace yaze { +namespace app { +namespace gui { +namespace zeml { + +std::vector Tokenize(const std::string& input) { + std::vector tokens; + std::istringstream stream(input); + char ch; + + while (stream.get(ch)) { + if (isspace(ch)) continue; + + if (ch == '{') { + tokens.push_back({TokenType::OpenBrace, "{"}); + } else if (ch == '}') { + tokens.push_back({TokenType::CloseBrace, "}"}); + } else if (ch == ',') { + tokens.push_back({TokenType::Comma, ","}); + } else if (std::isalnum(ch) || ch == '_') { + std::string ident(1, ch); + while (stream.get(ch) && (std::isalnum(ch) || ch == '_')) { + ident += ch; + } + stream.unget(); + tokens.push_back({TokenType::Identifier, ident}); + } else if (ch == '"' || ch == '\'') { + std::string str; + char quoteType = ch; + while (stream.get(ch) && ch != quoteType) { + str += ch; + } + tokens.push_back({TokenType::String, str}); + } + } + + tokens.push_back({TokenType::EndOfStream, ""}); + return tokens; +} + +WidgetType MapType(const std::string& type) { + static std::map typeMap = { + {"Window", WidgetType::Window}, + {"Button", WidgetType::Button}, + {"Slider", WidgetType::Slider}, + {"Text", WidgetType::Text}, + {"CollapsingHeader", WidgetType::CollapsingHeader}, + {"Columns", WidgetType::Columns}, + {"Checkbox", WidgetType::Checkbox}, + {"HexInputByte", WidgetType::HexInputByte}, + {"HexInputWord", WidgetType::HexInputWord}, + {"Table", WidgetType::Table}, + {"Selectable", WidgetType::Selectable}, + {"TableSetupColumn", WidgetType::TableSetupColumn}, + {"TableHeadersRow", WidgetType::TableHeadersRow}, + {"TableNextColumn", WidgetType::TableNextColumn}, + {"Function", WidgetType::Function}, + {"BeginChild", WidgetType::BeginChild}, + {"BeginMenu", WidgetType::BeginMenu}, + {"MenuItem", WidgetType::MenuItem}, + {"BeginMenuBar", WidgetType::BeginMenuBar}, + {"Separator", WidgetType::Separator}, + {"BeginTabBar", WidgetType::BeginTabBar}, + {"BeginTabItem", WidgetType::BeginTabItem}, + {"Canvas", WidgetType::Canvas}, + {"ref", WidgetType::Definition}, + }; + return typeMap[type]; +} + +Node ParseNode(const std::vector& tokens, size_t& index, + const std::map& data_bindings, + const std::map& definitions) { + Node node; + if (index >= tokens.size() || tokens[index].type == TokenType::EndOfStream) { + return node; + } + + while (index < tokens.size() && + tokens[index].type != TokenType::EndOfStream) { + Token token = tokens[index]; + if (token.type == TokenType::Identifier) { + node.type = MapType(token.value); + index++; // Move to the next token for attributes + if (node.type == WidgetType::Definition) { + if (definitions.find(token.value) != definitions.end()) { + node = definitions.at(token.value); + } + } else { + node.attributes = + ParseAttributes(tokens, index, node.type, data_bindings); + } + } + + // Handle the opening brace indicating the start of child nodes + if (index < tokens.size() && tokens[index].type == TokenType::OpenBrace) { + index++; // Skip the opening brace + + while (index < tokens.size() && + tokens[index].type != TokenType::CloseBrace) { + if (tokens[index].type == TokenType::Comma) { + index++; // Skip commas + } else { + node.children.push_back(ParseNode(tokens, index, data_bindings)); + } + } + + if (index < tokens.size() && + tokens[index].type == TokenType::CloseBrace) { + index++; // Ensure closing brace is skipped before returning + } + } + + break; // Exit after processing one complete node + } + return node; +} + +void ParseFlags(const WidgetType& type, const std::string& flags, + WidgetAttributes& attributes) { + // Parse the flags for the `|` character + std::vector flag_tokens; + std::string token; + std::istringstream tokenStream(flags); + while (std::getline(tokenStream, token, '|')) { + flag_tokens.push_back(token); + } + + switch (type) { + case WidgetType::BeginChild: { + static std::map flagMap = { + {"None", ImGuiWindowFlags_None}, + {"NoTitleBar", ImGuiWindowFlags_NoTitleBar}, + {"NoResize", ImGuiWindowFlags_NoResize}, + {"NoMove", ImGuiWindowFlags_NoMove}, + {"NoScrollbar", ImGuiWindowFlags_NoScrollbar}, + {"NoScrollWithMouse", ImGuiWindowFlags_NoScrollWithMouse}, + {"NoCollapse", ImGuiWindowFlags_NoCollapse}, + {"AlwaysAutoResize", ImGuiWindowFlags_AlwaysAutoResize}, + {"NoSavedSettings", ImGuiWindowFlags_NoSavedSettings}, + {"NoInputs", ImGuiWindowFlags_NoInputs}, + {"MenuBar", ImGuiWindowFlags_MenuBar}, + {"HorizontalScrollbar", ImGuiWindowFlags_HorizontalScrollbar}, + {"NoFocusOnAppearing", ImGuiWindowFlags_NoFocusOnAppearing}, + {"NoBringToFrontOnFocus", ImGuiWindowFlags_NoBringToFrontOnFocus}, + {"AlwaysVerticalScrollbar", ImGuiWindowFlags_AlwaysVerticalScrollbar}, + {"AlwaysHorizontalScrollbar", + ImGuiWindowFlags_AlwaysHorizontalScrollbar}, + {"AlwaysUseWindowPadding", ImGuiWindowFlags_AlwaysUseWindowPadding}, + {"NoNavInputs", ImGuiWindowFlags_NoNavInputs}, + {"NoNavFocus", ImGuiWindowFlags_NoNavFocus}, + {"UnsavedDocument", ImGuiWindowFlags_UnsavedDocument}, + {"NoNav", ImGuiWindowFlags_NoNav}, + {"NoDecoration", ImGuiWindowFlags_NoDecoration}, + {"NoInputs", ImGuiWindowFlags_NoInputs}, + {"NoFocusOnAppearing", ImGuiWindowFlags_NoFocusOnAppearing}, + {"NoBringToFrontOnFocus", ImGuiWindowFlags_NoBringToFrontOnFocus}, + {"AlwaysAutoResize", ImGuiWindowFlags_AlwaysAutoResize}, + {"NoSavedSettings", ImGuiWindowFlags_NoSavedSettings}, + {"NoMouseInputs", ImGuiWindowFlags_NoMouseInputs}, + {"NoMouseInputs", ImGuiWindowFlags_NoMouseInputs}, + {"NoTitleBar", ImGuiWindowFlags_NoTitleBar}, + {"NoResize", ImGuiWindowFlags_NoResize}, + {"NoMove", ImGuiWindowFlags_NoMove}, + {"NoScrollbar", ImGuiWindowFlags_NoScrollbar}, + {"NoScrollWithMouse", ImGuiWindowFlags_NoScrollWithMouse}, + {"NoCollapse", ImGuiWindowFlags_NoCollapse}, + {"AlwaysVerticalScrollbar", ImGuiWindowFlags_AlwaysVerticalScrollbar}, + {"AlwaysHorizontalScrollbar", + ImGuiWindowFlags_AlwaysHorizontalScrollbar}}; + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_None; + for (const auto& flag : flag_tokens) { + if (flagMap.find(flag) != flagMap.end()) { + windowFlags |= flagMap[flag]; + } + } + attributes.flags = std::make_unique(windowFlags); + break; + } + case WidgetType::CollapsingHeader: { + // Create a flag map using the tree node flags + static std::map flagMap = { + {"None", ImGuiTreeNodeFlags_None}, + {"Selected", ImGuiTreeNodeFlags_Selected}, + {"Framed", ImGuiTreeNodeFlags_Framed}, + {"AllowItemOverlap", ImGuiTreeNodeFlags_AllowItemOverlap}, + {"NoTreePushOnOpen", ImGuiTreeNodeFlags_NoTreePushOnOpen}, + {"NoAutoOpenOnLog", ImGuiTreeNodeFlags_NoAutoOpenOnLog}, + {"DefaultOpen", ImGuiTreeNodeFlags_DefaultOpen}, + {"OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick}, + {"OpenOnArrow", ImGuiTreeNodeFlags_OpenOnArrow}, + {"Leaf", ImGuiTreeNodeFlags_Leaf}, + {"Bullet", ImGuiTreeNodeFlags_Bullet}, + {"FramePadding", ImGuiTreeNodeFlags_FramePadding}, + {"NavLeftJumpsBackHere", ImGuiTreeNodeFlags_NavLeftJumpsBackHere}, + {"CollapsingHeader", ImGuiTreeNodeFlags_CollapsingHeader}}; + ImGuiTreeNodeFlags treeFlags = ImGuiTreeNodeFlags_None; + for (const auto& flag : flag_tokens) { + if (flagMap.find(flag) != flagMap.end()) { + treeFlags |= flagMap[flag]; + } + } + attributes.flags = std::make_unique(treeFlags); + break; + } + case WidgetType::Table: { + // Create a flag map + static std::map flagMap = { + {"None", ImGuiTableFlags_None}, + {"Resizable", ImGuiTableFlags_Resizable}, + {"Reorderable", ImGuiTableFlags_Reorderable}, + {"Hideable", ImGuiTableFlags_Hideable}, + {"Sortable", ImGuiTableFlags_Sortable}, + {"NoSavedSettings", ImGuiTableFlags_NoSavedSettings}, + {"ContextMenuInBody", ImGuiTableFlags_ContextMenuInBody}, + {"RowBg", ImGuiTableFlags_RowBg}, + {"BordersInnerH", ImGuiTableFlags_BordersInnerH}, + {"BordersOuterH", ImGuiTableFlags_BordersOuterH}, + {"BordersInnerV", ImGuiTableFlags_BordersInnerV}, + {"BordersOuterV", ImGuiTableFlags_BordersOuterV}, + {"BordersH", ImGuiTableFlags_BordersH}, + {"BordersV", ImGuiTableFlags_BordersV}, + {"Borders", ImGuiTableFlags_Borders}, + {"NoBordersInBody", ImGuiTableFlags_NoBordersInBody}, + {"NoBordersInBodyUntilResize", + ImGuiTableFlags_NoBordersInBodyUntilResize}, + {"SizingFixedFit", ImGuiTableFlags_SizingFixedFit}, + {"SizingFixedSame", ImGuiTableFlags_SizingFixedSame}, + {"SizingStretchProp", ImGuiTableFlags_SizingStretchProp}, + {"SizingStretchSame", ImGuiTableFlags_SizingStretchSame}, + {"NoHostExtendX", ImGuiTableFlags_NoHostExtendX}, + {"NoHostExtendY", ImGuiTableFlags_NoHostExtendY}, + {"NoKeepColumnsVisible", ImGuiTableFlags_NoKeepColumnsVisible}, + {"PreciseWidths", ImGuiTableFlags_PreciseWidths}, + {"NoClip", ImGuiTableFlags_NoClip}, + {"PadOuterX", ImGuiTableFlags_PadOuterX}, + {"NoPadOuterX", ImGuiTableFlags_NoPadOuterX}, + {"NoPadInnerX", ImGuiTableFlags_NoPadInnerX}, + {"ScrollX", ImGuiTableFlags_ScrollX}, + {"ScrollY", ImGuiTableFlags_ScrollY}, + {"SortMulti", ImGuiTableFlags_SortMulti}, + {"SortTristate", ImGuiTableFlags_SortTristate}}; + ImGuiTableFlags tableFlags = ImGuiTableFlags_None; + for (const auto& flag : flag_tokens) { + if (flagMap.find(flag) != flagMap.end()) { + tableFlags |= flagMap[flag]; + } + } + // Reserve data to the void* pointer and assign flags + attributes.flags = std::make_unique(tableFlags); + } break; + case WidgetType::TableSetupColumn: { + static std::map flagMap = { + {"None", ImGuiTableColumnFlags_None}, + {"DefaultHide", ImGuiTableColumnFlags_DefaultHide}, + {"DefaultSort", ImGuiTableColumnFlags_DefaultSort}, + {"WidthStretch", ImGuiTableColumnFlags_WidthStretch}, + {"WidthFixed", ImGuiTableColumnFlags_WidthFixed}, + {"NoResize", ImGuiTableColumnFlags_NoResize}, + {"NoReorder", ImGuiTableColumnFlags_NoReorder}, + {"NoHide", ImGuiTableColumnFlags_NoHide}, + {"NoClip", ImGuiTableColumnFlags_NoClip}, + {"NoSort", ImGuiTableColumnFlags_NoSort}, + {"NoSortAscending", ImGuiTableColumnFlags_NoSortAscending}, + {"NoSortDescending", ImGuiTableColumnFlags_NoSortDescending}, + {"NoHeaderWidth", ImGuiTableColumnFlags_NoHeaderWidth}, + {"PreferSortAscending", ImGuiTableColumnFlags_PreferSortAscending}, + {"PreferSortDescending", ImGuiTableColumnFlags_PreferSortDescending}, + {"IndentEnable", ImGuiTableColumnFlags_IndentEnable}, + {"IndentDisable", ImGuiTableColumnFlags_IndentDisable}, + {"IsEnabled", ImGuiTableColumnFlags_IsEnabled}, + {"IsVisible", ImGuiTableColumnFlags_IsVisible}, + {"IsSorted", ImGuiTableColumnFlags_IsSorted}, + {"IsHovered", ImGuiTableColumnFlags_IsHovered}}; + ImGuiTableColumnFlags columnFlags = ImGuiTableColumnFlags_None; + for (const auto& flag : flag_tokens) { + if (flagMap.find(flag) != flagMap.end()) { + columnFlags |= flagMap[flag]; + } + } + // Reserve data to the void* pointer and assign flags + attributes.flags = std::make_unique(columnFlags); + } + default: + break; + } +} + +WidgetAttributes ParseAttributes( + const std::vector& tokens, size_t& index, const WidgetType& type, + const std::map& data_bindings) { + WidgetAttributes attributes; + + while (index < tokens.size() && tokens[index].type != TokenType::CloseBrace) { + if (tokens[index].type == TokenType::Identifier) { + Token keyToken = tokens[index]; + index++; // Move to the value token. + if (index < tokens.size() && tokens[index].type == TokenType::String) { + std::string value = tokens[index].value; + index++; // Move past the value. + + if (keyToken.value == "id") + attributes.id = value; + else if (keyToken.value == "title") + attributes.title = value; + else if (keyToken.value == "min") + attributes.min = std::stod(value); + else if (keyToken.value == "max") + attributes.max = std::stod(value); + else if (keyToken.value == "value") + attributes.value = std::stod(value); + else if (keyToken.value == "width") + if (value == "autox") + attributes.width = ImGui::GetContentRegionAvail().x; + else if (value == "autoy") + attributes.width = ImGui::GetContentRegionAvail().y; + else + attributes.width = std::stod(value); + else if (keyToken.value == "text") + attributes.text = value; + else if (keyToken.value == "data" && + data_bindings.find(value) != data_bindings.end()) { + attributes.data = data_bindings.at(value); + } else if (keyToken.value == "count") { + attributes.count = std::stoi(value); + } else if (keyToken.value == "flags") { + ParseFlags(type, value, attributes); + } else if (keyToken.value == "size") { + std::string sizeX, sizeY; + std::istringstream sizeStream(value); + std::getline(sizeStream, sizeX, ','); + std::getline(sizeStream, sizeY, ','); + attributes.size = ImVec2(std::stod(sizeX), std::stod(sizeY)); + } + } + } else { + // If it's not an identifier or we encounter an open brace, break out. + break; + } + } + return attributes; +} + +Node Parse(const std::string& yazon_input, + const std::map& data_bindings) { + size_t index = 0; + auto tokens = Tokenize(yazon_input); + + std::map definitions; + if (tokens[index].value == "Definitions") { + index++; // Skip the "Definitions" token + while (index < tokens.size() && + tokens[index].value != "Layout") { // Skip the definitions + // Get the definition name and parse the node + std::string definition_name = tokens[index].value; + index++; // Move to the definition node + definitions[definition_name] = ParseNode(tokens, index, data_bindings); + index++; + } + } + + return ParseNode(tokens, index, data_bindings); +} + +void Render(Node& node) { + switch (node.type) { + case WidgetType::Window: { + ImGuiWindowFlags flags = ImGuiWindowFlags_None; + if (node.attributes.flags) { + flags = *(ImGuiWindowFlags*)node.attributes.flags.get(); + } + if (ImGui::Begin(node.attributes.title.c_str(), nullptr, flags)) { + for (auto& child : node.children) { + Render(child); + } + ImGui::End(); + } + } break; + case WidgetType::Button: + if (node.attributes.data) { + // Format the text with the data value + char formattedText[256]; + snprintf(formattedText, sizeof(formattedText), + node.attributes.text.c_str(), *(int*)node.attributes.data); + if (ImGui::Button(formattedText)) { + ExecuteActions(node.actions, ActionType::Click); + } + } else { + if (ImGui::Button(node.attributes.text.c_str())) { + ExecuteActions(node.actions, ActionType::Click); + } + } + break; + case WidgetType::CollapsingHeader: { + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None; + if (node.attributes.flags) { + flags = *(ImGuiTreeNodeFlags*)node.attributes.flags.get(); + } + if (ImGui::CollapsingHeader(node.attributes.title.c_str(), flags)) { + for (auto& child : node.children) { + Render(child); + } + } + } break; + case WidgetType::Columns: + ImGui::Columns(node.attributes.count, node.attributes.title.c_str()); + ImGui::Separator(); + for (auto& child : node.children) { + Render(child); + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + break; + case WidgetType::Checkbox: + if (ImGui::Checkbox(node.attributes.title.c_str(), + (bool*)node.attributes.data)) { + ExecuteActions(node.actions, ActionType::Change); + } + break; + case WidgetType::Table: { + ImGuiTableFlags flags = ImGuiTableFlags_None; + if (node.attributes.flags) { + flags = *(ImGuiTableFlags*)node.attributes.flags.get(); + } + if (ImGui::BeginTable(node.attributes.id.c_str(), node.attributes.count, + flags)) { + for (auto& child : node.children) { + Render(child); + } + } + ImGui::EndTable(); + } break; + case WidgetType::TableSetupColumn: { + ImGuiTableColumnFlags flags = ImGuiTableColumnFlags_None; + if (node.attributes.flags) { + flags = *(ImGuiTableColumnFlags*)node.attributes.flags.get(); + } + ImGui::TableSetupColumn(node.attributes.title.c_str(), flags); + } break; + case WidgetType::TableHeadersRow: + ImGui::TableHeadersRow(); + break; + case WidgetType::TableNextColumn: + ImGui::TableNextColumn(); + break; + case WidgetType::Text: + if (node.attributes.data) { + // Assuming all data-bound Text widgets use string formatting + char formattedText[128]; + snprintf(formattedText, sizeof(formattedText), + node.attributes.text.c_str(), + *(int*)node.attributes.data & 0xFFFF); + ImGui::Text("%s", formattedText); + } else { + ImGui::Text("%s", node.attributes.text.c_str()); + } + break; + case WidgetType::Function: { + node.actions[0].callback(); + break; + } + case WidgetType::BeginChild: + if (ImGui::BeginChild(node.attributes.id.c_str(), node.attributes.size)) { + for (auto& child : node.children) { + Render(child); + } + ImGui::EndChild(); + } + break; + case WidgetType::BeginMenuBar: + if (ImGui::BeginMenuBar()) { + for (auto& child : node.children) { + Render(child); + } + ImGui::EndMenuBar(); + } + break; + case WidgetType::BeginMenu: { + if (ImGui::BeginMenu(node.attributes.title.c_str())) { + for (auto& child : node.children) { + Render(child); + } + ImGui::EndMenu(); + } + break; + } + case WidgetType::MenuItem: { + if (ImGui::MenuItem(node.attributes.title.c_str())) { + ExecuteActions(node.actions, ActionType::Click); + } + break; + } + case WidgetType::Separator: + ImGui::Separator(); + break; + case WidgetType::Selectable: + if (ImGui::Selectable(node.attributes.title.c_str(), + (bool*)node.attributes.selected)) { + ExecuteActions(node.actions, ActionType::Click); + } + break; + case WidgetType::BeginTabBar: + if (ImGui::BeginTabBar(node.attributes.title.c_str())) { + for (auto& child : node.children) { + Render(child); + } + ImGui::EndTabBar(); + } + break; + case WidgetType::BeginTabItem: + if (ImGui::BeginTabItem(node.attributes.title.c_str())) { + for (auto& child : node.children) { + Render(child); + } + ImGui::EndTabItem(); + } + break; + case WidgetType::HexInputByte: + gui::InputHexByte(node.attributes.id.c_str(), + (uint8_t*)node.attributes.data); + break; + case WidgetType::HexInputWord: + gui::InputHexWord(node.attributes.id.c_str(), + (uint16_t*)node.attributes.data); + break; + case WidgetType::Canvas: { + gui::Canvas* canvas = (gui::Canvas*)node.attributes.data; + if (canvas) { + canvas->DrawBackground(); + canvas->DrawContextMenu(); + + canvas->DrawGrid(); + canvas->DrawOverlay(); + } + break; + } + default: + break; + } +} + +void ExecuteActions(const std::vector& actions, ActionType type) { + for (const auto& action : actions) { + if (action.type == type) { + action.callback(); // Execute the callback associated with the action + } + } +} + +void Bind(Node* node, std::function callback) { + if (node) { + Action action = {ActionType::Click, callback}; + node->actions.push_back(action); + } +} + +void BindAction(Node* node, ActionType type, std::function callback) { + if (node) { + Action action = {type, callback}; + node->actions.push_back(action); + } +} + +void BindSelectable(Node* node, bool* selected, + std::function callback) { + if (node) { + Action action = {ActionType::Click, callback}; + node->actions.push_back(action); + node->attributes.selected = selected; + } +} + +std::string LoadFile(const std::string& filename) { + std::string fileContents; + const std::string kPath = "assets/layouts/"; + + #ifdef __APPLE__ + #if TARGET_OS_IOS == 1 + const std::string kBundlePath = GetBundleResourcePath(); + std::ifstream file(kBundlePath + filename); + #else + std::ifstream file(kPath + filename); + #endif + #else + std::ifstream file(kPath + filename); + #endif + + if (file.is_open()) { + std::string line; + while (std::getline(file, line)) { + fileContents += line; + } + file.close(); + } else { + fileContents = "File not found: " + filename; + std::cout << fileContents << std::endl; + } + return fileContents; +} + +} // namespace zeml +} // namespace gui +} // namespace app +} // namespace yaze diff --git a/src/app/gui/zeml.h b/src/app/gui/zeml.h new file mode 100644 index 00000000..dbb9c396 --- /dev/null +++ b/src/app/gui/zeml.h @@ -0,0 +1,215 @@ +#ifndef YAZE_APP_GUI_ZEML_H +#define YAZE_APP_GUI_ZEML_H + +#include "imgui/imgui.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace yaze { +namespace app { +namespace gui { + +/** + * @namespace yaze::app::gui::zeml + * @brief Zelda Editor Markup Language Functions + */ +namespace zeml { + +/** + * @enum TokenType + */ +enum class TokenType { + Identifier, + String, + OpenBrace, + CloseBrace, + Comma, + EndOfStream +}; + +/** + * @struct Token + */ +struct Token { + TokenType type; + std::string value; +} typedef Token; + +/** + * @enum WidgetType + */ +enum class WidgetType { + Window, + Button, + Slider, + Text, + Table, + TableSetupColumn, + TableHeadersRow, + TableNextColumn, + CollapsingHeader, + Columns, + Checkbox, + Selectable, + Function, + BeginChild, + BeginMenuBar, + BeginMenu, + BeginTabBar, + BeginTabItem, + MenuItem, + Separator, + HexInputByte, + HexInputWord, + Canvas, + Definition, +}; + +/** + * @struct WidgetAttributes + * @brief Attributes for a widget + * @details id, title, min, max, value, text, count, size, flags, data + * @details id: unique identifier for the widget + * @details title: title for the widget + * @details text: text for the widget + * @details min: minimum value for the widget + * @details max: maximum value for the widget + * @details value: value for the widget + * @details count: number of columns + * @details size: size of the widget + * @details flags: flags for the widget + * @details data: data to be binded using the data_binding map + */ +struct WidgetAttributes { + std::string id; + std::string title; // For Window, Button + std::string text; // For Text, Button + double min; // For Slider + double max; // For Slider + double value; // For Slidecar + float width; // For Columns + int count = 0; // For Columns + ImVec2 size = ImVec2(0, 0); // For BeginChild + bool* selected = nullptr; // For Selectable + std::shared_ptr flags = nullptr; // For Various + void* data = nullptr; // For Various +}; + +/** + * @enum ActionType + */ +enum class ActionType { Click, Change, Run }; + +/** + * @struct Action + */ +struct Action { + ActionType type; + std::function callback; +}; + +/** + * @brief Tokenize a zeml string + */ +std::vector Tokenize(const std::string& input); + +/** + * @struct Node + * @brief Node for a zeml tree + */ +struct Node { + WidgetType type; + WidgetAttributes attributes; + std::vector actions; + std::vector children; + + Node* parent = nullptr; + + Node* GetNode(const std::string& searchId) { + if (attributes.id == searchId) { + return this; + } + for (Node& child : children) { + Node* found = child.GetNode(searchId); + if (found != nullptr) { + return found; + } + } + return nullptr; + } +}; + +/** + * @brief Bind a callback to a node + */ +void Bind(Node* node, std::function callback); + +/** + * @brief Bind an action to a node + */ +void BindAction(Node* node, ActionType type, std::function callback); + +/** + * @brief Bind a selectable node + */ +void BindSelectable(Node* node, bool* selected, std::function callback); + +/** + * @brief Map a string to a widget type + */ +WidgetType MapType(const std::string& type); + +/** + * @brief Parse a zeml definition + */ +void ParseDefinitions(const std::vector& tokens, size_t& index, + std::map& definitions); + +void ParseFlags(const WidgetType& type, const std::string& flags, + WidgetAttributes& flags_ptr); + +/** + * @brief ParseNode attributes for a widget + */ +WidgetAttributes ParseAttributes( + const std::vector& tokens, size_t& index, const WidgetType& type, + const std::map& data_bindings = {}); + +/** + * @brief Parse a zeml node + */ +Node ParseNode(const std::vector& tokens, size_t& index, + const std::map& data_bindings = {}, + const std::map& definitions = {}); + +/** + * @brief Parse a zeml string + */ +Node Parse(const std::string& yazon_input, + const std::map& data_bindings = {}); + +/** + * @brief Render a zeml tree + */ +void Render(Node& node); + +/** + * @brief Execute actions for a node + */ +void ExecuteActions(const std::vector& actions, ActionType type); + +std::string LoadFile(const std::string& filename); + +} // namespace zeml +} // namespace gui +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_GUI_YAZON_H_ diff --git a/src/app/rom.cc b/src/app/rom.cc index 92f7db10..b0007665 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -99,13 +99,13 @@ absl::Status Rom::LoadAllGraphicsData() { core::kTilesheetDepth); if (i > 115) { // Apply sprites palette - RETURN_IF_ERROR(graphics_manager_[i]->ApplyPaletteWithTransparent( + RETURN_IF_ERROR(graphics_manager_[i].ApplyPaletteWithTransparent( palette_groups_.global_sprites[0], 0)); } else { - RETURN_IF_ERROR(graphics_manager_[i]->ApplyPaletteWithTransparent( + RETURN_IF_ERROR(graphics_manager_[i].ApplyPaletteWithTransparent( palette_groups_.dungeon_main[0], 0)); } - graphics_manager_[i]->CreateTexture(renderer_); + graphics_manager_[i].CreateTexture(renderer_); } graphics_bin_[i] = gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight, @@ -113,8 +113,8 @@ absl::Status Rom::LoadAllGraphicsData() { graphics_bin_.at(i).CreateTexture(renderer_); if (flags()->kUseBitmapManager) { - for (int j = 0; j < graphics_manager_[i].get()->size(); ++j) { - graphics_buffer_.push_back(graphics_manager_[i]->at(j)); + for (int j = 0; j < graphics_manager_[i].size(); ++j) { + graphics_buffer_.push_back(graphics_manager_[i].at(j)); } } } else { @@ -126,20 +126,29 @@ absl::Status Rom::LoadAllGraphicsData() { return absl::OkStatus(); } -absl::Status Rom::LoadFromFile(const absl::string_view& filename, - bool z3_load) { +absl::Status Rom::LoadFromFile(const std::string& filename, bool z3_load) { + std::string full_filename = std::filesystem::absolute(filename).string(); + if (full_filename.empty()) { + return absl::InvalidArgumentError( + "Could not load ROM: parameter `filename` is empty."); + } // Set filename - filename_ = filename; + filename_ = full_filename; // Open file - std::ifstream file(filename.data(), std::ios::binary); + std::ifstream file(filename_, std::ios::binary); if (!file.is_open()) { return absl::InternalError( - absl::StrCat("Could not open ROM file: ", filename)); + absl::StrCat("Could not open ROM file: ", filename_)); } // Get file size and resize rom_data_ - size_ = std::filesystem::file_size(filename); + try { + size_ = std::filesystem::file_size(filename_); + } catch (const std::filesystem::filesystem_error& e) { + return absl::InternalError( + absl::StrCat("Could not get file size: ", filename_, " - ", e.what())); + } rom_data_.resize(size_); // Read file into rom_data_ @@ -149,6 +158,7 @@ absl::Status Rom::LoadFromFile(const absl::string_view& filename, constexpr size_t baseROMSize = 1048576; // 1MB constexpr size_t headerSize = 0x200; // 512 bytes if (size_ % baseROMSize == headerSize) { + std::cout << "ROM has a header" << std::endl; has_header_ = true; } @@ -241,7 +251,7 @@ absl::Status Rom::SaveToFile(bool backup, bool save_new, std::string filename) { // Now, copy the original file to the backup file try { - std::filesystem::copy(filename, backup_filename, + std::filesystem::copy(filename_, backup_filename, std::filesystem::copy_options::overwrite_existing); } catch (const std::filesystem::filesystem_error& e) { non_firing_status = absl::InternalError(absl::StrCat( @@ -251,7 +261,11 @@ absl::Status Rom::SaveToFile(bool backup, bool save_new, std::string filename) { // Run the other save functions if (flags()->kSaveAllPalettes) { - SaveAllPalettes(); + RETURN_IF_ERROR(SaveAllPalettes()); + } + + if (flags()->kSaveGfxGroups) { + SaveGroupsToRom(); } if (save_new) { @@ -323,15 +337,89 @@ absl::Status Rom::SavePalette(int index, const std::string& group_name, } absl::Status Rom::SaveAllPalettes() { - palette_groups_.for_each([&](gfx::PaletteGroup& group) { - for (size_t i = 0; i < group.size(); ++i) { - SavePalette(i, group.name(), *group.mutable_palette(i)); - } - }); + RETURN_IF_ERROR( + palette_groups_.for_each([&](gfx::PaletteGroup& group) -> absl::Status { + for (size_t i = 0; i < group.size(); ++i) { + RETURN_IF_ERROR( + SavePalette(i, group.name(), *group.mutable_palette(i))); + } + return absl::OkStatus(); + })); return absl::OkStatus(); } +void Rom::LoadGfxGroups() { + main_blockset_ids.resize(37, std::vector(8)); + room_blockset_ids.resize(82, std::vector(4)); + spriteset_ids.resize(144, std::vector(4)); + paletteset_ids.resize(72, std::vector(4)); + + int gfxPointer = + (rom_data_[kGfxGroupsPointer + 1] << 8) + rom_data_[kGfxGroupsPointer]; + gfxPointer = core::SnesToPc(gfxPointer); + + for (int i = 0; i < 37; i++) { + for (int j = 0; j < 8; j++) { + main_blockset_ids[i][j] = rom_data_[gfxPointer + (i * 8) + j]; + } + } + + for (int i = 0; i < 82; i++) { + for (int j = 0; j < 4; j++) { + room_blockset_ids[i][j] = + rom_data_[core::entrance_gfx_group + (i * 4) + j]; + } + } + + for (int i = 0; i < 144; i++) { + for (int j = 0; j < 4; j++) { + spriteset_ids[i][j] = + rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j]; + } + } + + for (int i = 0; i < 72; i++) { + for (int j = 0; j < 4; j++) { + paletteset_ids[i][j] = + rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j]; + } + } +} + +void Rom::SaveGroupsToRom() { + int gfxPointer = + (rom_data_[kGfxGroupsPointer + 1] << 8) + rom_data_[kGfxGroupsPointer]; + gfxPointer = core::SnesToPc(gfxPointer); + + for (int i = 0; i < 37; i++) { + for (int j = 0; j < 8; j++) { + rom_data_[gfxPointer + (i * 8) + j] = main_blockset_ids[i][j]; + } + } + + for (int i = 0; i < 82; i++) { + for (int j = 0; j < 4; j++) { + rom_data_[core::entrance_gfx_group + (i * 4) + j] = + room_blockset_ids[i][j]; + } + } + + for (int i = 0; i < 144; i++) { + for (int j = 0; j < 4; j++) { + rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j] = + spriteset_ids[i][j]; + } + } + + for (int i = 0; i < 72; i++) { + for (int j = 0; j < 4; j++) { + rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j] = + paletteset_ids[i][j]; + } + } +} + std::shared_ptr SharedRom::shared_rom_ = nullptr; } // namespace app diff --git a/src/app/rom.h b/src/app/rom.h index 597ed24a..20341481 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -2,7 +2,6 @@ #define YAZE_APP_ROM_H #include -#include #include #include @@ -22,7 +21,6 @@ #include #include // for vector -#include "SDL_render.h" // for SDL_Renderer #include "absl/container/flat_hash_map.h" // for flat_hash_map #include "absl/status/status.h" // for Status #include "absl/status/statusor.h" // for StatusOr @@ -171,8 +169,7 @@ class Rom : public core::ExperimentFlags { * @param z3_load Whether to load data specific to Zelda 3. * */ - absl::Status LoadFromFile(const absl::string_view& filename, - bool z3_load = true); + absl::Status LoadFromFile(const std::string& filename, bool z3_load = true); absl::Status LoadFromPointer(uchar* data, size_t length); absl::Status LoadFromBytes(const Bytes& data); @@ -462,6 +459,7 @@ class Rom : public core::ExperimentFlags { auto bitmap_manager() { return graphics_manager_; } auto mutable_bitmap_manager() { return &graphics_manager_; } auto link_graphics() { return link_graphics_; } + auto mutable_link_graphics() { return &link_graphics_; } auto title() const { return title_; } auto size() const { return size_; } @@ -473,6 +471,7 @@ class Rom : public core::ExperimentFlags { auto filename() const { return filename_; } auto is_loaded() const { return is_loaded_; } auto version() const { return version_; } + auto renderer() const { return renderer_; } uint8_t& operator[](int i) { if (i > size_) { @@ -532,80 +531,11 @@ class Rom : public core::ExperimentFlags { std::vector> spriteset_ids; std::vector> paletteset_ids; - void LoadGfxGroups() { - main_blockset_ids.resize(37, std::vector(8)); - room_blockset_ids.resize(82, std::vector(4)); - spriteset_ids.resize(144, std::vector(4)); - paletteset_ids.resize(72, std::vector(4)); - - int gfxPointer = - (rom_data_[kGfxGroupsPointer + 1] << 8) + rom_data_[kGfxGroupsPointer]; - gfxPointer = core::SnesToPc(gfxPointer); - - for (int i = 0; i < 37; i++) { - for (int j = 0; j < 8; j++) { - main_blockset_ids[i][j] = rom_data_[gfxPointer + (i * 8) + j]; - } - } - - for (int i = 0; i < 82; i++) { - for (int j = 0; j < 4; j++) { - room_blockset_ids[i][j] = - rom_data_[core::entrance_gfx_group + (i * 4) + j]; - } - } - - for (int i = 0; i < 144; i++) { - for (int j = 0; j < 4; j++) { - spriteset_ids[i][j] = - rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j]; - } - } - - for (int i = 0; i < 72; i++) { - for (int j = 0; j < 4; j++) { - paletteset_ids[i][j] = - rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j]; - } - } - } - - bool SaveGroupsToRom() { - int gfxPointer = - (rom_data_[kGfxGroupsPointer + 1] << 8) + rom_data_[kGfxGroupsPointer]; - gfxPointer = core::SnesToPc(gfxPointer); - - for (int i = 0; i < 37; i++) { - for (int j = 0; j < 8; j++) { - rom_data_[gfxPointer + (i * 8) + j] = main_blockset_ids[i][j]; - } - } - - for (int i = 0; i < 82; i++) { - for (int j = 0; j < 4; j++) { - rom_data_[core::entrance_gfx_group + (i * 4) + j] = - room_blockset_ids[i][j]; - } - } - - for (int i = 0; i < 144; i++) { - for (int j = 0; j < 4; j++) { - rom_data_[version_constants().kSpriteBlocksetPointer + (i * 4) + j] = - spriteset_ids[i][j]; - } - } - - for (int i = 0; i < 72; i++) { - for (int j = 0; j < 4; j++) { - rom_data_[version_constants().kDungeonPalettesGroups + (i * 4) + j] = - paletteset_ids[i][j]; - } - } - - return false; - } + void LoadGfxGroups(); + void SaveGroupsToRom(); auto resource_label() { return &resource_label_manager_; } + auto font_gfx_data() { return font_gfx_data_; } private: struct WriteAction { @@ -660,6 +590,7 @@ class Rom : public core::ExperimentFlags { Bytes rom_data_; Bytes graphics_buffer_; + Bytes font_gfx_data_; Z3_Version version_ = Z3_Version::US; gfx::BitmapTable graphics_bin_; diff --git a/src/app/rom_test.cc b/src/app/rom_test.cc new file mode 100644 index 00000000..e2d34360 --- /dev/null +++ b/src/app/rom_test.cc @@ -0,0 +1,20 @@ +#include "app/rom.h" + +#include +#include + +namespace yaze { +namespace app { + +class RomTest : public ::testing::Test { + protected: + Rom rom_; +}; + +TEST_F(RomTest, RomTest) { + EXPECT_EQ(rom_.size(), 0); + EXPECT_EQ(rom_.data(), nullptr); +} + +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/yaze.cc b/src/app/yaze.cc index b7b784aa..6017227e 100644 --- a/src/app/yaze.cc +++ b/src/app/yaze.cc @@ -22,21 +22,41 @@ int main(int argc, char** argv) { absl::FailureSignalHandlerOptions options; options.symbolize_stacktrace = true; + options.use_alternate_stack = true; options.alarm_on_failure_secs = true; + options.call_previous_handler = true; absl::InstallFailureSignalHandler(options); + std::string rom_filename; + if (argc > 1) { + rom_filename = argv[1]; + } + core::Controller controller; - EXIT_IF_ERROR(controller.OnEntry()) + EXIT_IF_ERROR(controller.OnEntry(rom_filename)) #ifdef __APPLE__ InitializeCocoa(); #endif - - while (controller.IsActive()) { - controller.OnInput(); - controller.OnLoad(); - controller.DoRender(); + try { + while (controller.IsActive()) { + controller.OnInput(); + if (auto status = controller.OnLoad(); !status.ok()) { + std::cerr << status.message() << std::endl; + break; + } + controller.DoRender(); + } + controller.OnExit(); + } catch (const std::bad_alloc& e) { + std::cerr << "Memory allocation failed: " << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } catch (...) { + std::cerr << "Unknown exception" << std::endl; + return EXIT_FAILURE; } - controller.OnExit(); return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/app/zelda3/CMakeLists.txt b/src/app/zelda3/CMakeLists.txt new file mode 100644 index 00000000..3b021326 --- /dev/null +++ b/src/app/zelda3/CMakeLists.txt @@ -0,0 +1,12 @@ +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 +) \ No newline at end of file diff --git a/src/app/zelda3/dungeon/object_names.h b/src/app/zelda3/dungeon/object_names.h index 15a731b3..cc24aeea 100644 --- a/src/app/zelda3/dungeon/object_names.h +++ b/src/app/zelda3/dungeon/object_names.h @@ -8,7 +8,7 @@ namespace app { namespace zelda3 { namespace dungeon { -static const absl::string_view Type1RoomObjectNames[] = { +constexpr static inline absl::string_view Type1RoomObjectNames[] = { "Ceiling ↔", "Wall (top, north) ↔", "Wall (top, south) ↔", @@ -259,7 +259,7 @@ static const absl::string_view Type1RoomObjectNames[] = { "Nothing", }; -static const absl::string_view Type2RoomObjectNames[] = { +constexpr static inline absl::string_view Type2RoomObjectNames[] = { "Corner (top, concave) ▛", "Corner (top, concave) ▙", "Corner (top, concave) ▜", @@ -326,7 +326,7 @@ static const absl::string_view Type2RoomObjectNames[] = { "Magic bat altar", }; -static const absl::string_view Type3RoomObjectNames[] = { +constexpr static inline absl::string_view Type3RoomObjectNames[] = { "Waterfall face (empty)", "Waterfall face (short)", "Waterfall face (long)", diff --git a/src/app/zelda3/dungeon/object_renderer.cc b/src/app/zelda3/dungeon/object_renderer.cc index 35c5c6a3..5e030162 100644 --- a/src/app/zelda3/dungeon/object_renderer.cc +++ b/src/app/zelda3/dungeon/object_renderer.cc @@ -96,7 +96,6 @@ void DungeonObjectRenderer::ConfigureObject(const SubtypeInfo& info) { */ void DungeonObjectRenderer::RenderObject(const SubtypeInfo& info) { cpu.PB = 0x01; - cpu.PC = cpu.ReadWord(0x01 << 16 | info.routine_ptr); // Push an initial value to the stack we can read later to confirm we are // done @@ -106,15 +105,8 @@ void DungeonObjectRenderer::RenderObject(const SubtypeInfo& info) { while (true) { uint8_t opcode = cpu.ReadByte(cpu.PB << 16 | cpu.PC); cpu.ExecuteInstruction(opcode); - cpu.HandleInterrupts(); - if ((i != 0 && - (cpu.ReadWord((0x00 << 16 | cpu.SP() + 2)) == info.routine_ptr) || - 0x8b93 == cpu.PC)) { - std::cout << std::hex << cpu.ReadWord((0x00 << 16 | cpu.SP() + 3)) - << std::endl; - break; - } + i++; } diff --git a/src/app/zelda3/dungeon/object_renderer.h b/src/app/zelda3/dungeon/object_renderer.h index 7d1aac3e..aa25fa75 100644 --- a/src/app/zelda3/dungeon/object_renderer.h +++ b/src/app/zelda3/dungeon/object_renderer.h @@ -48,7 +48,8 @@ class DungeonObjectRenderer : public SharedRom { std::vector rom_data_; emu::memory::MemoryImpl memory_; emu::ClockImpl clock_; - emu::Cpu cpu{memory_, clock_}; + emu::memory::CpuCallbacks cpu_callbacks_; + emu::Cpu cpu{memory_, clock_, cpu_callbacks_}; emu::video::Ppu ppu{memory_, clock_}; gfx::Bitmap bitmap_; PseudoVram vram_; diff --git a/src/app/zelda3/dungeon/room_entrance.h b/src/app/zelda3/dungeon/room_entrance.h index 0819dd88..4e67bed6 100644 --- a/src/app/zelda3/dungeon/room_entrance.h +++ b/src/app/zelda3/dungeon/room_entrance.h @@ -209,89 +209,114 @@ class RoomEntrance { } } - void Save(Rom& rom, int entrance_id, bool is_spawn_point = false) { + absl::Status Save(Rom& rom, int entrance_id, bool is_spawn_point = false) { if (!is_spawn_point) { - rom.WriteShort(entrance_room + (entrance_id * 2), room_); - rom.WriteShort(entrance_yposition + (entrance_id * 2), y_position_); - rom.WriteShort(entrance_xposition + (entrance_id * 2), x_position_); - rom.WriteShort(entrance_yscroll + (entrance_id * 2), camera_y_); - rom.WriteShort(entrance_xscroll + (entrance_id * 2), camera_x_); - rom.WriteShort(entrance_cameraxtrigger + (entrance_id * 2), - camera_trigger_x_); - rom.WriteShort(entrance_cameraytrigger + (entrance_id * 2), - camera_trigger_y_); - - rom.WriteShort(entrance_exit + (entrance_id * 2), exit_); - rom.Write(entrance_blockset + entrance_id, (uint8_t)(blockset_ & 0xFF)); - rom.Write(entrance_music + entrance_id, (uint8_t)(music_ & 0xFF)); - rom.Write(entrance_dungeon + entrance_id, (uint8_t)(dungeon_id_ & 0xFF)); - rom.Write(entrance_door + entrance_id, (uint8_t)(door_ & 0xFF)); - rom.Write(entrance_floor + entrance_id, (uint8_t)(floor_ & 0xFF)); - rom.Write(entrance_ladderbg + entrance_id, (uint8_t)(ladder_bg_ & 0xFF)); - rom.Write(entrance_scrolling + entrance_id, (uint8_t)(scrolling_ & 0xFF)); - rom.Write(entrance_scrollquadrant + entrance_id, - (uint8_t)(scroll_quadrant_ & 0xFF)); - - rom.Write(entrance_scrolledge + 0 + (entrance_id * 8), - camera_boundary_qn_); - rom.Write(entrance_scrolledge + 1 + (entrance_id * 8), - camera_boundary_fn_); - rom.Write(entrance_scrolledge + 2 + (entrance_id * 8), - camera_boundary_qs_); - rom.Write(entrance_scrolledge + 3 + (entrance_id * 8), - camera_boundary_fs_); - rom.Write(entrance_scrolledge + 4 + (entrance_id * 8), - camera_boundary_qw_); - rom.Write(entrance_scrolledge + 5 + (entrance_id * 8), - camera_boundary_fw_); - rom.Write(entrance_scrolledge + 6 + (entrance_id * 8), - camera_boundary_qe_); - rom.Write(entrance_scrolledge + 7 + (entrance_id * 8), - camera_boundary_fe_); + RETURN_IF_ERROR( + rom.WriteShort(entrance_yposition + (entrance_id * 2), y_position_)); + RETURN_IF_ERROR( + rom.WriteShort(entrance_xposition + (entrance_id * 2), x_position_)); + RETURN_IF_ERROR( + rom.WriteShort(entrance_yscroll + (entrance_id * 2), camera_y_)); + RETURN_IF_ERROR( + rom.WriteShort(entrance_xscroll + (entrance_id * 2), camera_x_)); + RETURN_IF_ERROR(rom.WriteShort( + entrance_cameraxtrigger + (entrance_id * 2), camera_trigger_x_)); + RETURN_IF_ERROR(rom.WriteShort( + entrance_cameraytrigger + (entrance_id * 2), camera_trigger_y_)); + RETURN_IF_ERROR(rom.WriteShort(entrance_exit + (entrance_id * 2), exit_)); + RETURN_IF_ERROR(rom.Write(entrance_blockset + entrance_id, + (uint8_t)(blockset_ & 0xFF))); + RETURN_IF_ERROR( + rom.Write(entrance_music + entrance_id, (uint8_t)(music_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(entrance_dungeon + entrance_id, + (uint8_t)(dungeon_id_ & 0xFF))); + RETURN_IF_ERROR( + rom.Write(entrance_door + entrance_id, (uint8_t)(door_ & 0xFF))); + RETURN_IF_ERROR( + rom.Write(entrance_floor + entrance_id, (uint8_t)(floor_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(entrance_ladderbg + entrance_id, + (uint8_t)(ladder_bg_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(entrance_scrolling + entrance_id, + (uint8_t)(scrolling_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(entrance_scrollquadrant + entrance_id, + (uint8_t)(scroll_quadrant_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 0 + (entrance_id * 8), + camera_boundary_qn_)); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 1 + (entrance_id * 8), + camera_boundary_fn_)); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 2 + (entrance_id * 8), + camera_boundary_qs_)); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 3 + (entrance_id * 8), + camera_boundary_fs_)); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 4 + (entrance_id * 8), + camera_boundary_qw_)); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 5 + (entrance_id * 8), + camera_boundary_fw_)); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 6 + (entrance_id * 8), + camera_boundary_qe_)); + RETURN_IF_ERROR(rom.Write(entrance_scrolledge + 7 + (entrance_id * 8), + camera_boundary_fe_)); } else { - rom.WriteShort(startingentrance_room + (entrance_id * 2), room_); - rom.WriteShort(startingentrance_yposition + (entrance_id * 2), - y_position_); - rom.WriteShort(startingentrance_xposition + (entrance_id * 2), - x_position_); - rom.WriteShort(startingentrance_yscroll + (entrance_id * 2), camera_y_); - rom.WriteShort(startingentrance_xscroll + (entrance_id * 2), camera_x_); - rom.WriteShort(startingentrance_cameraxtrigger + (entrance_id * 2), - camera_trigger_x_); - rom.WriteShort(startingentrance_cameraytrigger + (entrance_id * 2), - camera_trigger_y_); - rom.WriteShort(startingentrance_exit + (entrance_id * 2), exit_); - - rom.Write(startingentrance_blockset + entrance_id, - (uint8_t)(blockset_ & 0xFF)); - rom.Write(startingentrance_music + entrance_id, (uint8_t)(music_ & 0xFF)); - rom.Write(startingentrance_dungeon + entrance_id, - (uint8_t)(dungeon_id_ & 0xFF)); - rom.Write(startingentrance_door + entrance_id, (uint8_t)(door_ & 0xFF)); - rom.Write(startingentrance_floor + entrance_id, (uint8_t)(floor_ & 0xFF)); - rom.Write(startingentrance_ladderbg + entrance_id, - (uint8_t)(ladder_bg_ & 0xFF)); - rom.Write(startingentrance_scrolling + entrance_id, - (uint8_t)(scrolling_ & 0xFF)); - rom.Write(startingentrance_scrollquadrant + entrance_id, - (uint8_t)(scroll_quadrant_ & 0xFF)); - rom.Write(startingentrance_scrolledge + 0 + (entrance_id * 8), - camera_boundary_qn_); - rom.Write(startingentrance_scrolledge + 1 + (entrance_id * 8), - camera_boundary_fn_); - rom.Write(startingentrance_scrolledge + 2 + (entrance_id * 8), - camera_boundary_qs_); - rom.Write(startingentrance_scrolledge + 3 + (entrance_id * 8), - camera_boundary_fs_); - rom.Write(startingentrance_scrolledge + 4 + (entrance_id * 8), - camera_boundary_qw_); - rom.Write(startingentrance_scrolledge + 5 + (entrance_id * 8), - camera_boundary_fw_); - rom.Write(startingentrance_scrolledge + 6 + (entrance_id * 8), - camera_boundary_qe_); - rom.Write(startingentrance_scrolledge + 7 + (entrance_id * 8), - camera_boundary_fe_); + RETURN_IF_ERROR( + rom.WriteShort(startingentrance_room + (entrance_id * 2), room_)); + RETURN_IF_ERROR(rom.WriteShort( + startingentrance_yposition + (entrance_id * 2), y_position_)); + RETURN_IF_ERROR(rom.WriteShort( + startingentrance_xposition + (entrance_id * 2), x_position_)); + RETURN_IF_ERROR(rom.WriteShort( + startingentrance_yscroll + (entrance_id * 2), camera_y_)); + RETURN_IF_ERROR(rom.WriteShort( + startingentrance_xscroll + (entrance_id * 2), camera_x_)); + RETURN_IF_ERROR( + rom.WriteShort(startingentrance_cameraxtrigger + (entrance_id * 2), + camera_trigger_x_)); + RETURN_IF_ERROR( + rom.WriteShort(startingentrance_cameraytrigger + (entrance_id * 2), + camera_trigger_y_)); + RETURN_IF_ERROR( + rom.WriteShort(startingentrance_exit + (entrance_id * 2), exit_)); + RETURN_IF_ERROR(rom.Write(startingentrance_blockset + entrance_id, + (uint8_t)(blockset_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(startingentrance_music + entrance_id, + (uint8_t)(music_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(startingentrance_dungeon + entrance_id, + (uint8_t)(dungeon_id_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(startingentrance_door + entrance_id, + (uint8_t)(door_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(startingentrance_floor + entrance_id, + (uint8_t)(floor_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(startingentrance_ladderbg + entrance_id, + (uint8_t)(ladder_bg_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(startingentrance_scrolling + entrance_id, + (uint8_t)(scrolling_ & 0xFF))); + RETURN_IF_ERROR(rom.Write(startingentrance_scrollquadrant + entrance_id, + (uint8_t)(scroll_quadrant_ & 0xFF))); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 0 + (entrance_id * 8), + camera_boundary_qn_)); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 1 + (entrance_id * 8), + camera_boundary_fn_)); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 2 + (entrance_id * 8), + camera_boundary_qs_)); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 3 + (entrance_id * 8), + camera_boundary_fs_)); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 4 + (entrance_id * 8), + camera_boundary_qw_)); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 5 + (entrance_id * 8), + camera_boundary_fw_)); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 6 + (entrance_id * 8), + camera_boundary_qe_)); + RETURN_IF_ERROR( + rom.Write(startingentrance_scrolledge + 7 + (entrance_id * 8), + camera_boundary_fe_)); } + return absl::OkStatus(); } uint16_t entrance_id_; diff --git a/src/app/zelda3/dungeon/room_object.h b/src/app/zelda3/dungeon/room_object.h index 25a07782..b32a2cce 100644 --- a/src/app/zelda3/dungeon/room_object.h +++ b/src/app/zelda3/dungeon/room_object.h @@ -76,18 +76,12 @@ class RoomObject : public SharedRom { height_(16), unique_id_(0) {} - virtual void Draw() { - // ... Draw function implementation here - } - void GetObjectSize() { previous_size_ = size_; size_ = 1; - // Draw(); GetBaseSize(); UpdateSize(); size_ = 2; - // Draw(); GetSizeSized(); UpdateSize(); size_ = previous_size_; @@ -172,7 +166,6 @@ class Subtype1 : public RoomObject { int tileCount) : RoomObject(id, x, y, size, layer), tile_count_(tileCount) { auto rom_data = rom()->data(); - name = Type1RoomObjectNames[id & 0xFF]; int pos = core::tile_address + static_cast( @@ -182,7 +175,7 @@ class Subtype1 : public RoomObject { sort = (Sorting)(Sorting::Horizontal | Sorting::Wall); } - void Draw() override { + void Draw() { for (int s = 0; s < size_ + (tile_count_ == 8 ? 1 : 0); s++) { for (int i = 0; i < tile_count_; i++) { // DrawTile(tiles[i], ((s * 2)) * 8, (i / 2) * 8); @@ -201,7 +194,6 @@ class Subtype2 : public RoomObject { Subtype2(int16_t id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer) : RoomObject(id, x, y, size, layer) { auto rom_data = rom()->data(); - name = Type2RoomObjectNames[id & 0x7F]; int pos = core::tile_address + static_cast( @@ -211,7 +203,7 @@ class Subtype2 : public RoomObject { sort = (Sorting)(Sorting::Horizontal | Sorting::Wall); } - void Draw() override { + void Draw() { for (int i = 0; i < 8; i++) { // DrawTile(tiles[i], x_ * 8, (y_ + i) * 8); } @@ -228,7 +220,6 @@ class Subtype3 : public RoomObject { Subtype3(int16_t id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer) : RoomObject(id, x, y, size, layer) { auto rom_data = rom()->data(); - name = Type3RoomObjectNames[id & 0xFF]; int pos = core::tile_address + static_cast( @@ -238,7 +229,7 @@ class Subtype3 : public RoomObject { sort = (Sorting)(Sorting::Horizontal | Sorting::Wall); } - void Draw() override { + void Draw() { for (int i = 0; i < 8; i++) { // DrawTile(tiles[i], x_ * 8, (y_ + i) * 8); } diff --git a/src/app/zelda3/music/tracker.cc b/src/app/zelda3/music/tracker.cc index ff824339..9de7bedb 100644 --- a/src/app/zelda3/music/tracker.cc +++ b/src/app/zelda3/music/tracker.cc @@ -20,7 +20,6 @@ #include "app/gfx/bitmap.h" #include "app/gfx/snes_tile.h" #include "app/rom.h" -#include "snes_spc/snes_spc/spc.h" namespace yaze { namespace app { diff --git a/src/app/zelda3/music/tracker.h b/src/app/zelda3/music/tracker.h index dd66d49c..f7ebb212 100644 --- a/src/app/zelda3/music/tracker.h +++ b/src/app/zelda3/music/tracker.h @@ -12,7 +12,6 @@ #include "app/gfx/bitmap.h" #include "app/gfx/snes_tile.h" #include "app/rom.h" -#include "snes_spc/snes_spc/spc.h" namespace yaze { namespace app { diff --git a/src/app/zelda3/overworld/overworld.h b/src/app/zelda3/overworld/overworld.h index 6f78acfd..affb48d8 100644 --- a/src/app/zelda3/overworld/overworld.h +++ b/src/app/zelda3/overworld/overworld.h @@ -372,7 +372,6 @@ class OverworldEntrance : public OverworldEntity { constexpr int kCompressedAllMap32PointersHigh = 0x1794D; constexpr int kCompressedAllMap32PointersLow = 0x17B2D; -constexpr int overworldgfxGroups = 0x05D97; constexpr int overworldPalGroup1 = 0xDE6C8; constexpr int overworldPalGroup2 = 0xDE86C; constexpr int overworldPalGroup3 = 0xDE604; @@ -433,33 +432,37 @@ constexpr int transition_target_west = 0x13F62; constexpr int overworldCustomMosaicASM = 0x1301D0; constexpr int overworldCustomMosaicArray = 0x1301F0; -constexpr int OverworldCustomASMHasBeenApplied = - 0x140145; // 1 byte, not 0 if enabled +// 1 byte, not 0 if enabled +constexpr int OverworldCustomASMHasBeenApplied = 0x140145; -constexpr int OverworldCustomAreaSpecificBGPalette = - 0x140000; // 2 bytes for each overworld area (0x140) -constexpr int OverworldCustomAreaSpecificBGEnabled = - 0x140140; // 1 byte, not 0 if enabled +// 2 bytes for each overworld area (0x140) +constexpr int OverworldCustomAreaSpecificBGPalette = 0x140000; -constexpr int OverworldCustomMainPaletteArray = - 0x140160; // 1 byte for each overworld area (0xA0) -constexpr int OverworldCustomMainPaletteEnabled = - 0x140141; // 1 byte, not 0 if enabled +// 1 byte, not 0 if enabled +constexpr int OverworldCustomAreaSpecificBGEnabled = 0x140140; -constexpr int OverworldCustomMosaicArray = - 0x140200; // 1 byte for each overworld area (0xA0) -constexpr int OverworldCustomMosaicEnabled = - 0x140142; // 1 byte, not 0 if enabled +// 1 byte for each overworld area (0xA0) +constexpr int OverworldCustomMainPaletteArray = 0x140160; +// 1 byte, not 0 if enabled +constexpr int OverworldCustomMainPaletteEnabled = 0x140141; -constexpr int OverworldCustomAnimatedGFXArray = - 0x1402A0; // 1 byte for each overworld area (0xA0) -constexpr int OverworldCustomAnimatedGFXEnabled = - 0x140143; // 1 byte, not 0 if enabled +// 1 byte for each overworld area (0xA0) +constexpr int OverworldCustomMosaicArray = 0x140200; -constexpr int OverworldCustomSubscreenOverlayArray = - 0x140340; // 2 bytes for each overworld area (0x140) -constexpr int OverworldCustomSubscreenOverlayEnabled = - 0x140144; // 1 byte, not 0 if enabled +// 1 byte, not 0 if enabled +constexpr int OverworldCustomMosaicEnabled = 0x140142; + +// 1 byte for each overworld area (0xA0) +constexpr int OverworldCustomAnimatedGFXArray = 0x1402A0; + +// 1 byte, not 0 if enabled +constexpr int OverworldCustomAnimatedGFXEnabled = 0x140143; + +// 2 bytes for each overworld area (0x140) +constexpr int OverworldCustomSubscreenOverlayArray = 0x140340; + +// 1 byte, not 0 if enabled +constexpr int OverworldCustomSubscreenOverlayEnabled = 0x140144; constexpr int kMap16Tiles = 0x78000; constexpr int kNumOverworldMaps = 160; diff --git a/src/app/zelda3/overworld/overworld_map.cc b/src/app/zelda3/overworld/overworld_map.cc index 23870c62..47b63070 100644 --- a/src/app/zelda3/overworld/overworld_map.cc +++ b/src/app/zelda3/overworld/overworld_map.cc @@ -1,6 +1,6 @@ #include "overworld_map.h" -#include +#include "imgui/imgui.h" #include #include @@ -9,7 +9,7 @@ #include #include "app/core/common.h" -#include "app/editor/context/gfx_context.h" +#include "app/editor/utils/gfx_context.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_tile.h" #include "app/rom.h" @@ -146,13 +146,13 @@ void OverworldMap::LoadAreaInfo() { // ============================================================================ -void OverworldMap::LoadWorldIndex() { +void OverworldMap::LoadMainBlocksetId() { if (parent_ < 0x40) { - world_index_ = 0x20; + main_gfx_id_ = 0x20; } else if (parent_ >= 0x40 && parent_ < 0x80) { - world_index_ = 0x21; + main_gfx_id_ = 0x21; } else if (parent_ == 0x88) { - world_index_ = 0x24; + main_gfx_id_ = 0x24; } } @@ -174,7 +174,7 @@ void OverworldMap::LoadSpritesBlocksets() { void OverworldMap::LoadMainBlocksets() { for (int i = 0; i < 8; i++) { static_graphics_[i] = rom_[rom_.version_constants().kOverworldGfxGroups2 + - (world_index_ * 8) + i]; + (main_gfx_id_ * 8) + i]; } } @@ -221,7 +221,7 @@ void OverworldMap::LoadDeathMountainGFX() { } void OverworldMap::LoadAreaGraphics() { - LoadWorldIndex(); + LoadMainBlocksetId(); LoadSpritesBlocksets(); LoadMainBlocksets(); LoadAreaGraphicsBlocksets(); diff --git a/src/app/zelda3/overworld/overworld_map.h b/src/app/zelda3/overworld/overworld_map.h index f3c47a28..881292c5 100644 --- a/src/app/zelda3/overworld/overworld_map.h +++ b/src/app/zelda3/overworld/overworld_map.h @@ -1,7 +1,7 @@ #ifndef YAZE_APP_ZELDA3_OVERWORLD_MAP_H #define YAZE_APP_ZELDA3_OVERWORLD_MAP_H -#include +#include "imgui/imgui.h" #include #include @@ -11,7 +11,7 @@ #include "absl/status/status.h" #include "app/core/common.h" -#include "app/editor/context/gfx_context.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" @@ -51,6 +51,8 @@ class OverworldMap : public editor::context::GfxContext { auto is_initialized() const { return initialized_; } auto parent() const { return parent_; } + auto mutable_mosaic() { return &mosaic_; } + auto mutable_current_palette() { return ¤t_palette_; } auto area_graphics() const { return area_graphics_; } @@ -103,7 +105,7 @@ class OverworldMap : public editor::context::GfxContext { private: void LoadAreaInfo(); - void LoadWorldIndex(); + void LoadMainBlocksetId(); void LoadSpritesBlocksets(); void LoadMainBlocksets(); void LoadAreaGraphicsBlocksets(); @@ -118,12 +120,14 @@ class OverworldMap : public editor::context::GfxContext { bool large_map_ = false; bool initialized_ = false; + bool mosaic_ = false; + int index_ = 0; // Map index int parent_ = 0; // Parent map index int large_index_ = 0; // Quadrant ID [0-3] int world_ = 0; // World ID [0-2] int game_state_ = 0; // Game state [0-2] - int world_index_ = 0; // Spr Pal Modifier + int main_gfx_id_ = 0; // Main Gfx ID uint16_t message_id_ = 0; uint8_t area_graphics_ = 0; diff --git a/src/app/zelda3/screen/dungeon_map.h b/src/app/zelda3/screen/dungeon_map.h index 5977a947..326c4dbb 100644 --- a/src/app/zelda3/screen/dungeon_map.h +++ b/src/app/zelda3/screen/dungeon_map.h @@ -18,9 +18,9 @@ constexpr int kDungeonMapGfxPtr = 0x57BE4; // 14 pointers of gfx data constexpr int kDungeonMapDataStart = 0x57039; // IF Byte = 0xB9 dungeon maps are not expanded -constexpr int kDungeonMapExpCheck = 0x56652; -constexpr int kDungeonMapTile16 = 0x57009; -constexpr int kDungeonMapTile16Expanded = 0x109010; +constexpr int kDungeonMapExpCheck = 0x56652; // $0A:E652 +constexpr int kDungeonMapTile16 = 0x57009; // $0A:F009 +constexpr int kDungeonMapTile16Expanded = 0x109010; // $21:9010 // 14 words values 0x000F = no boss constexpr int kDungeonMapBossRooms = 0x56807; diff --git a/src/app/zelda3/screen/inventory.cc b/src/app/zelda3/screen/inventory.cc index 4cafb895..b33d4c77 100644 --- a/src/app/zelda3/screen/inventory.cc +++ b/src/app/zelda3/screen/inventory.cc @@ -10,12 +10,12 @@ namespace app { namespace zelda3 { namespace screen { -void Inventory::Create() { +absl::Status Inventory::Create() { data_.reserve(256 * 256); for (int i = 0; i < 256 * 256; i++) { data_.push_back(0xFF); } - PRINT_IF_ERROR(BuildTileset()) + RETURN_IF_ERROR(BuildTileset()) for (int i = 0; i < 0x500; i += 0x08) { tiles_.push_back(gfx::GetTilesInfo(rom()->toint16(i + kBowItemPos))); tiles_.push_back(gfx::GetTilesInfo(rom()->toint16(i + kBowItemPos + 0x02))); @@ -64,8 +64,9 @@ void Inventory::Create() { } bitmap_.Create(256, 256, 8, data_); - bitmap_.ApplyPalette(palette_); + RETURN_IF_ERROR(bitmap_.ApplyPalette(palette_)); rom()->RenderBitmap(&bitmap_); + return absl::OkStatus(); } absl::Status Inventory::BuildTileset() { diff --git a/src/app/zelda3/screen/inventory.h b/src/app/zelda3/screen/inventory.h index 6c1496c2..9f251aac 100644 --- a/src/app/zelda3/screen/inventory.h +++ b/src/app/zelda3/screen/inventory.h @@ -21,7 +21,7 @@ class Inventory : public SharedRom { auto Tilesheet() const { return tilesheets_bmp_; } auto Palette() const { return palette_; } - void Create(); + absl::Status Create(); private: absl::Status BuildTileset(); diff --git a/src/app/zelda3/screen/title_screen.cc b/src/app/zelda3/screen/title_screen.cc index 3ef63361..075e72f6 100644 --- a/src/app/zelda3/screen/title_screen.cc +++ b/src/app/zelda3/screen/title_screen.cc @@ -13,13 +13,11 @@ namespace zelda3 { namespace screen { void TitleScreen::Create() { - tiles8Bitmap.Create(128, 512, 8, 0x20000); - tilesBG1Bitmap.Create(256, 256, 8, 0x80000); - tilesBG2Bitmap.Create(256, 256, 8, 0x80000); - oamBGBitmap.Create(256, 256, 8, 0x80000); - + tiles8Bitmap.Create(128, 512, 8, Bytes(0, 0x20000)); + tilesBG1Bitmap.Create(256, 256, 8, Bytes(0, 0x80000)); + tilesBG2Bitmap.Create(256, 256, 8, Bytes(0, 0x80000)); + oamBGBitmap.Create(256, 256, 8, Bytes(0, 0x80000)); BuildTileset(); - LoadTitleScreen(); } diff --git a/src/app/zelda3/sprite/sprite.h b/src/app/zelda3/sprite/sprite.h index 67698f98..f3f824a1 100644 --- a/src/app/zelda3/sprite/sprite.h +++ b/src/app/zelda3/sprite/sprite.h @@ -59,7 +59,7 @@ class Sprite : public OverworldEntity { auto Width() const { return bounding_box_.w; } auto Height() const { return bounding_box_.h; } - std::string& Name() { return name_; } + auto name() { return name_; } auto deleted() const { return deleted_; } auto set_deleted(bool deleted) { deleted_ = deleted; } diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index e47a43b5..5bafd437 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable( app/core/labeling.cc app/gui/pipeline.cc app/editor/context/gfx_context.cc + app/core/platform/file_dialog.mm ${YAZE_APP_EMU_SRC} ${YAZE_APP_GFX_SRC} ${YAZE_APP_ZELDA3_SRC} @@ -19,7 +20,6 @@ target_include_directories( z3ed PUBLIC lib/ app/ - lib/SDL_mixer/include/ ${CMAKE_SOURCE_DIR}/src/ ${PNG_INCLUDE_DIRS} ${SDL2_INCLUDE_DIR} @@ -34,6 +34,5 @@ target_link_libraries( ${GLEW_LIBRARIES} ${OPENGL_LIBRARIES} ${CMAKE_DL_LIBS} - SDL2_mixer ImGui ) \ No newline at end of file diff --git a/src/cli/command_handler.cc b/src/cli/command_handler.cc index 6b604c38..3878c8a9 100644 --- a/src/cli/command_handler.cc +++ b/src/cli/command_handler.cc @@ -65,7 +65,8 @@ absl::Status Tile16Transfer::handle(const std::vector& arg_vec) { // Transfer if user confirms if (userChoice == 'y' || userChoice == 'Y') { - dest_rom.WriteTile16(tile16_id_int, source_tile16_data); + RETURN_IF_ERROR( + dest_rom.WriteTile16(tile16_id_int, source_tile16_data)); std::cout << "Transferred tile16 ID " << tile16_id_int << " to dest rom." << std::endl; } else { diff --git a/src/cli/command_handler.h b/src/cli/command_handler.h index 6bd725be..13a837ef 100644 --- a/src/cli/command_handler.h +++ b/src/cli/command_handler.h @@ -20,7 +20,6 @@ #include "app/gfx/snes_palette.h" #include "app/gfx/snes_tile.h" #include "app/gui/canvas.h" -#include "app/gui/pipeline.h" #include "app/rom.h" // for Rom #include "app/zelda3/overworld/overworld.h" #include "cli/patch.h" // for ApplyBpsPatch, CreateBpsPatch @@ -316,39 +315,6 @@ class Emulator : public CommandHandler { std::string filename = arg_vec[0]; RETURN_IF_ERROR(rom_.LoadFromFile(filename)) - bool step = false; - if (arg_vec[1].empty()) { - snes.SetCpuMode(0); - } else { - snes.SetCpuMode(1); - step = true; - } - - snes.SetupMemory(rom_); - snes.Init(rom_); - - if (!step) { - int i = 0; - while (i < 80000) { - snes.Run(); - i++; - } - } else { - // This loop should take in input from the keyboard, such as pressing - // space to step through the loop and pressing x to end the execution. - bool stepping = true; - std::cout << "Press space to step, x to exit" << std::endl; - while (stepping) { - char input; - std::cin.get(input); - if (input == 'x') { - break; - } else { - snes.StepRun(); - } - } - } - return absl::OkStatus(); } diff --git a/src/lib/SDL_mixer b/src/lib/SDL_mixer deleted file mode 160000 index b25e80c2..00000000 --- a/src/lib/SDL_mixer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b25e80c271f426760eeeab9bfb12394045e1687a diff --git a/src/lib/imgui b/src/lib/imgui index f790d516..1f634f1d 160000 --- a/src/lib/imgui +++ b/src/lib/imgui @@ -1 +1 @@ -Subproject commit f790d516652d269989d90ef3c4ce3880f50c2a6c +Subproject commit 1f634f1d94e16feb3d0b15ad79e8bc44cccc184d diff --git a/src/lib/imgui_test_engine b/src/lib/imgui_test_engine new file mode 160000 index 00000000..82714610 --- /dev/null +++ b/src/lib/imgui_test_engine @@ -0,0 +1 @@ +Subproject commit 8271461028664d67ae633712b1e0805ace73fe47 diff --git a/src/lib/snes_spc b/src/lib/snes_spc deleted file mode 160000 index ec8ee2bb..00000000 --- a/src/lib/snes_spc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ec8ee2bbe30451614c1d02a83f7af1c97d497d45 diff --git a/src/lib/sneshacking b/src/lib/sneshacking deleted file mode 160000 index 8a798c15..00000000 --- a/src/lib/sneshacking +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8a798c159bef275d9d3ef80f30c6b0298314da93 diff --git a/test/CMakeLists.txt b/src/test/CMakeLists.txt similarity index 57% rename from test/CMakeLists.txt rename to src/test/CMakeLists.txt index 4c9e2914..cfbb66fe 100644 --- a/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -10,57 +10,54 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) enable_testing() +# Append the ../ prefix to the include directories +list(TRANSFORM YAZE_APP_GFX_SRC PREPEND "../") +list(TRANSFORM YAZE_APP_EMU_SRC PREPEND "../") +list(TRANSFORM YAZE_APP_CORE_SRC PREPEND "../") +list(TRANSFORM YAZE_GUI_SRC PREPEND "../") + add_executable( yaze_test yaze_test.cc emu/cpu_test.cc - emu/spc700_test.cc - emu/ppu_test.cc + # emu/spc700_test.cc + # emu/ppu_test.cc gfx/compression_test.cc gfx/snes_palette_test.cc zelda3/room_object_test.cc - ../src/cli/patch.cc - ../src/cli/command_handler.cc - ../src/app/rom.cc - ../src/app/emu/cpu/cpu.cc - ../src/app/emu/cpu/internal/instructions.cc - ../src/app/emu/cpu/internal/addressing.cc - ../src/app/emu/audio/internal/addressing.cc - ../src/app/emu/audio/internal/instructions.cc - ../src/app/emu/audio/apu.cc - ../src/app/emu/video/ppu.cc - ../src/app/emu/audio/dsp.cc - ../src/app/emu/audio/spc700.cc - ../src/app/editor/context/gfx_context.cc - ../src/app/gfx/bitmap.cc - ../src/app/gfx/snes_tile.cc - ../src/app/gfx/snes_color.cc - ../src/app/gfx/snes_palette.cc - ../src/app/gfx/compression.cc - ../src/app/core/common.cc - ../src/app/core/labeling.cc + ../cli/patch.cc + ../cli/command_handler.cc + ../app/rom.cc + ../app/rom_test.cc + ../app/core/common.cc + ../app/core/labeling.cc + ../app/editor/utils/gfx_context.cc + ${YAZE_APP_EMU_SRC} + ${YAZE_APP_GFX_SRC} + ${YAZE_GUI_SRC} + ${IMGUI_FILE_DLG_PATH}/ImGuiFileDialog.cpp + ../lib/imgui/misc/cpp/imgui_stdlib.cpp # ${ASAR_STATIC_SRC} ) target_include_directories( yaze_test PUBLIC - ../src/ - ../src/lib/ - # ../src/lib/asar/src/asar/ + ../ + ../app/ + ../lib/ ${SDL2_INCLUDE_DIR} ${PNG_INCLUDE_DIRS} ) target_link_libraries( yaze_test - ${ABSL_TARGETS} SDL2::SDL2 + ${ABSL_TARGETS} ${PNG_LIBRARIES} ${GLEW_LIBRARIES} ${OPENGL_LIBRARIES} ${CMAKE_DL_LIBS} - # asar-static - # snes_spc + ImGuiTestEngine ImGui gmock_main gmock diff --git a/test/emu/cpu_test.cc b/src/test/emu/cpu_test.cc similarity index 98% rename from test/emu/cpu_test.cc rename to src/test/emu/cpu_test.cc index 7c294a43..13b85ab5 100644 --- a/test/emu/cpu_test.cc +++ b/src/test/emu/cpu_test.cc @@ -14,6 +14,7 @@ namespace emu_test { using yaze::app::emu::AsmParser; using yaze::app::emu::Cpu; +using yaze::app::emu::memory::CpuCallbacks; using yaze::app::emu::memory::MockClock; using yaze::app::emu::memory::MockMemory; @@ -26,13 +27,13 @@ class CpuTest : public ::testing::Test { mock_memory.Init(); EXPECT_CALL(mock_memory, ClearMemory()).Times(::testing::AtLeast(1)); mock_memory.ClearMemory(); - asm_parser.CreateInternalOpcodeMap(); } AsmParser asm_parser; MockMemory mock_memory; MockClock mock_clock; - Cpu cpu{mock_memory, mock_clock}; + CpuCallbacks cpu_callbacks; + Cpu cpu{mock_memory, mock_clock, cpu_callbacks}; }; using ::testing::_; @@ -42,6 +43,34 @@ using ::testing::Return; // Infrastructure // ============================================================================ +TEST_F(CpuTest, AsmParserTokenizerOk) { + AsmParser asm_parser; + std::string instruction = R"( + ADC.b #$01 + LDA.b #$FF + STA.w $2000 + )"; + + std::vector tokens = asm_parser.Tokenize(instruction); + + std::vector expected_tokens = {"ADC", ".b", "#", "$", "01", + "LDA", ".b", "#", "$", "FF", + "STA", ".w", "$", "2000"}; + EXPECT_THAT(tokens, ::testing::ContainerEq(expected_tokens)); +} + +TEST_F(CpuTest, AsmParserSingleInstructionOk) { + AsmParser asm_parser; + std::string instruction = "ADC.b #$01"; + std::vector tokens = asm_parser.Tokenize(instruction); + + std::vector expected_tokens = {"ADC", ".b", "#", "$", "01"}; + EXPECT_THAT(tokens, ::testing::ContainerEq(expected_tokens)); + + auto opcode = asm_parser.Parse(instruction); + EXPECT_EQ(opcode[0], 0x69); +} + TEST_F(CpuTest, CheckMemoryContents) { MockMemory memory; std::vector data = {0x00, 0x01, 0x02, 0x03, 0x04}; @@ -68,12 +97,10 @@ TEST_F(CpuTest, CheckMemoryContents) { TEST_F(CpuTest, ADC_CheckCarryFlag) { cpu.A = 0xFF; cpu.SetAccumulatorSize(true); - std::vector data = {0x15, 0x01}; // Operand at address 0x15 + std::vector data = {0x69, 0x15, 0x01}; // Operand at address 0x15 mock_memory.SetMemoryContents(data); - EXPECT_CALL(mock_memory, ReadByte(_)).WillOnce(Return(1)); - - cpu.ExecuteInstruction(0x69); // ADC Immediate + cpu.ExecuteInstruction(cpu.ReadOpcode()); // ADC Immediate EXPECT_EQ(cpu.A, 0x00); EXPECT_TRUE(cpu.GetCarryFlag()); @@ -485,10 +512,10 @@ TEST_F(CpuTest, AND_DirectPageIndirect) { } TEST_F(CpuTest, AND_StackRelativeIndirectIndexedY) { - cpu.A = 0b11110000; // A register - cpu.Y = 0x02; // Y register - cpu.DB = 0x10; // Setting Data Bank register to 0x20 - cpu.SetSP(0x01FF); // Setting Stack Pointer to 0x01FF + cpu.A = 0b11110000; // A register + cpu.Y = 0x02; // Y register + cpu.DB = 0x10; // Setting Data Bank register to 0x20 + mock_memory.SetSP(0x01FF); // Setting Stack Pointer to 0x01FF std::vector data = {0x33, 0x02}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0201, {0x00, 0x30}); // [0x0201] = 0x3000 @@ -1081,7 +1108,7 @@ TEST_F(CpuTest, CMP_DirectPageIndexedIndirectX) { TEST_F(CpuTest, CMP_StackRelative) { cpu.A = 0x80; - cpu.SetSP(0x01FF); + mock_memory.SetSP(0x01FF); std::vector data = {0xC3, 0x02}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0201, {0x40, 0x9F}); @@ -1223,10 +1250,10 @@ TEST_F(CpuTest, CMP_DirectPageIndirect) { } TEST_F(CpuTest, CMP_StackRelativeIndirectIndexedY) { - cpu.A = 0x03; // A register - cpu.Y = 0x02; // Y register - cpu.DB = 0x10; // Setting Data Bank register to 0x20 - cpu.SetSP(0x01FF); // Setting Stack Pointer to 0x01FF + cpu.A = 0x03; // A register + cpu.Y = 0x02; // Y register + cpu.DB = 0x10; // Setting Data Bank register to 0x20 + mock_memory.SetSP(0x01FF); // Setting Stack Pointer to 0x01FF std::vector data = {0xD3, 0x02}; // ADC sr, Y mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0201, {0x00, 0x30}); // [0x0201] = 0x3000 @@ -1561,9 +1588,9 @@ TEST_F(CpuTest, EOR_DirectPageIndexedIndirectX) { } TEST_F(CpuTest, EOR_StackRelative) { - cpu.A = 0b10101010; // A register - cpu.status = 0xFF; // 8-bit mode - cpu.SetSP(0x01FF); // Set Stack Pointer to 0x01FF + cpu.A = 0b10101010; // A register + cpu.status = 0xFF; // 8-bit mode + mock_memory.SetSP(0x01FF); // Set Stack Pointer to 0x01FF std::vector data = {0x43, 0x02}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0201, {0b01010101}); // [0x0201] = 0b01010101 @@ -1845,17 +1872,16 @@ TEST_F(CpuTest, INC_AbsoluteIndexedX_16bit) { EXPECT_FALSE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); } - TEST_F(CpuTest, INX) { cpu.SetIndexSize(true); // Set X register to 8-bit mode cpu.X = 0x7F; - cpu.INX(); + // cpu.INX(); EXPECT_EQ(cpu.X, 0x80); EXPECT_TRUE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); cpu.X = 0xFF; - cpu.INX(); + // cpu.INX(); EXPECT_EQ(cpu.X, 0x00); EXPECT_FALSE(cpu.GetNegativeFlag()); EXPECT_TRUE(cpu.GetZeroFlag()); @@ -1864,13 +1890,13 @@ TEST_F(CpuTest, INX) { TEST_F(CpuTest, INY) { cpu.SetIndexSize(true); // Set Y register to 8-bit mode cpu.Y = 0x7F; - cpu.INY(); + // cpu.INY(); EXPECT_EQ(cpu.Y, 0x80); EXPECT_TRUE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); cpu.Y = 0xFF; - cpu.INY(); + // cpu.INY(); EXPECT_EQ(cpu.Y, 0x00); EXPECT_FALSE(cpu.GetNegativeFlag()); EXPECT_TRUE(cpu.GetZeroFlag()); @@ -2015,7 +2041,7 @@ TEST_F(CpuTest, LDA_DirectPageIndexedIndirectX) { TEST_F(CpuTest, LDA_StackRelative) { cpu.SetAccumulatorSize(true); // Set A register to 8-bit mode cpu.status = 0xFF; // 8-bit mode - cpu.SetSP(0x01FF); // Set Stack Pointer to 0x01FF + mock_memory.SetSP(0x01FF); // Set Stack Pointer to 0x01FF std::vector data = {0xA3, 0x02}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0201, {0x7F}); @@ -2473,7 +2499,7 @@ TEST_F(CpuTest, ORA_DirectPageIndexedIndirectX) { TEST_F(CpuTest, ORA_StackRelative) { cpu.SetAccumulatorSize(true); // Set A register to 8-bit mode cpu.status = 0xFF; // 8-bit mode - cpu.SetSP(0x01FF); // Set Stack Pointer to 0x01FF + mock_memory.SetSP(0x01FF); // Set Stack Pointer to 0x01FF std::vector data = {0x03, 0x02}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0201, {0x7F}); @@ -2974,10 +3000,10 @@ TEST_F(CpuTest, REP_16Bit) { TEST_F(CpuTest, PHA_PLA_Ok) { cpu.A = 0x42; EXPECT_CALL(mock_memory, PushByte(0x42)).WillOnce(Return()); - cpu.PHA(); + // cpu.Pha(); cpu.A = 0x00; EXPECT_CALL(mock_memory, PopByte()).WillOnce(Return(0x42)); - cpu.PLA(); + // cpu.Pla(); EXPECT_EQ(cpu.A, 0x42); } @@ -2990,7 +3016,7 @@ TEST_F(CpuTest, PHP_PLP_Ok) { EXPECT_FALSE(cpu.GetZeroFlag()); EXPECT_CALL(mock_memory, PushByte(0x80)).WillOnce(Return()); - cpu.PHP(); + // cpu.Php(); // Clear status flags cpu.SetNegativeFlag(false); @@ -2999,7 +3025,7 @@ TEST_F(CpuTest, PHP_PLP_Ok) { EXPECT_TRUE(cpu.GetZeroFlag()); EXPECT_CALL(mock_memory, PopByte()).WillOnce(Return(0x80)); - cpu.PLP(); + // cpu.Plp(); EXPECT_TRUE(cpu.GetNegativeFlag()); EXPECT_FALSE(cpu.GetZeroFlag()); @@ -3307,7 +3333,7 @@ TEST_F(CpuTest, SBC_StackRelative) { mock_memory.SetMemoryContents(data); cpu.SetAccumulatorSize(true); // Set A register to 8-bit mode cpu.status = 0xFF; // 8-bit mode - cpu.SetSP(0x01FF); // Set Stack Pointer to 0x01FF + mock_memory.SetSP(0x01FF); // Set Stack Pointer to 0x01FF mock_memory.InsertMemory(0x00003E, {0x02}); mock_memory.InsertMemory(0x2002, {0x80}); @@ -3449,7 +3475,7 @@ TEST_F(CpuTest, SBC_StackRelativeIndirectIndexedY) { cpu.Y = 0x02; // Set Y register to 0x02 cpu.A = 0xFF; // Set A register to 0x80 cpu.status = 0xFF; // 8-bit mode - cpu.SetSP(0x01FF); // Set Stack Pointer to 0x01FF + mock_memory.SetSP(0x01FF); // Set Stack Pointer to 0x01FF std::vector data = {0xF3, 0x02}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x0201, {0x00, 0x30}); @@ -3618,7 +3644,7 @@ TEST_F(CpuTest, STA_DirectPageIndexedIndirectX) { TEST_F(CpuTest, STA_StackRelative) { cpu.SetAccumulatorSize(true); // Set A register to 8-bit mode cpu.A = 0x42; - cpu.SetSP(0x01FF); // Set Stack Pointer to 0x01FF + mock_memory.SetSP(0x01FF); // Set Stack Pointer to 0x01FF std::vector data = {0x83, 0x3C}; mock_memory.SetMemoryContents(data); @@ -3712,8 +3738,8 @@ TEST_F(CpuTest, STA_DirectPageIndirect) { TEST_F(CpuTest, STA_StackRelativeIndirectIndexedY) { cpu.SetAccumulatorSize(true); // Set A register to 8-bit mode cpu.A = 0x42; - cpu.Y = 0x02; // Set Y register to 0x02 - cpu.SetSP(0x01FF); // Set Stack Pointer to 0x01FF + cpu.Y = 0x02; // Set Y register to 0x02 + mock_memory.SetSP(0x01FF); // Set Stack Pointer to 0x01FF std::vector data = {0x93, 0x3C}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x00023B, {0x00, 0x10}); @@ -4034,7 +4060,7 @@ TEST_F(CpuTest, TSB_Absolute) { TEST_F(CpuTest, TSC) { cpu.SetAccumulatorSize(true); // Set A register to 8-bit mode - cpu.SetSP(0x42); + mock_memory.SetSP(0x42); std::vector data = {0x3B}; mock_memory.SetMemoryContents(data); @@ -4046,7 +4072,7 @@ TEST_F(CpuTest, TSC) { TEST_F(CpuTest, TSX) { cpu.SetAccumulatorSize(true); // Set A register to 8-bit mode - cpu.SetSP(0x42); + mock_memory.SetSP(0x42); std::vector data = {0xBA}; mock_memory.SetMemoryContents(data); @@ -4075,7 +4101,7 @@ TEST_F(CpuTest, TXS) { mock_memory.SetMemoryContents(data); cpu.ExecuteInstruction(0x9A); // TXS - EXPECT_EQ(cpu.SP(), 0x42); + EXPECT_EQ(mock_memory.SP(), 0x42); } // ============================================================================ diff --git a/test/emu/ppu_test.cc b/src/test/emu/ppu_test.cc similarity index 87% rename from test/emu/ppu_test.cc rename to src/test/emu/ppu_test.cc index 8e0a671f..637aebec 100644 --- a/test/emu/ppu_test.cc +++ b/src/test/emu/ppu_test.cc @@ -110,27 +110,27 @@ class PpuTest : public ::testing::Test { // Test Initialization TEST_F(PpuTest, InitializationSetsCorrectFrameBufferSize) { - // EXPECT_CALL(mock_ppu, Init()).Times(1); - // mock_ppu.Init(); - // EXPECT_EQ(mock_ppu.GetFrameBuffer().size(), 256 * 240); + EXPECT_CALL(mock_ppu, Init()).Times(1); + mock_ppu.Init(); + EXPECT_EQ(mock_ppu.GetFrameBuffer().size(), 256 * 240); } // Test State Reset TEST_F(PpuTest, ResetClearsFrameBuffer) { - // EXPECT_CALL(mock_ppu, Reset()).Times(1); - // mock_ppu.Reset(); - // auto frameBuffer = mock_ppu.GetFrameBuffer(); - // EXPECT_TRUE(std::all_of(frameBuffer.begin(), frameBuffer.end(), - // [](uint8_t val) { return val == 0; })); + EXPECT_CALL(mock_ppu, Reset()).Times(1); + mock_ppu.Reset(); + auto frameBuffer = mock_ppu.GetFrameBuffer(); + EXPECT_TRUE(std::all_of(frameBuffer.begin(), frameBuffer.end(), + [](uint8_t val) { return val == 0; })); } // Test Memory Interaction TEST_F(PpuTest, ReadWriteVRAM) { - // uint16_t address = testVRAMAddress; - // uint8_t value = testVRAMValue; - // EXPECT_CALL(mock_ppu, Write(address, value)).Times(1); - // mock_ppu.Write(address, value); - // EXPECT_EQ(mock_ppu.Read(address), value); + uint16_t address = testVRAMAddress; + uint8_t value = testVRAMValue; + EXPECT_CALL(mock_ppu, Write(address, value)).Times(1); + mock_ppu.Write(address, value); + EXPECT_EQ(mock_ppu.Read(address), value); } // Test Rendering Mechanics diff --git a/test/emu/spc700_test.cc b/src/test/emu/spc700_test.cc similarity index 99% rename from test/emu/spc700_test.cc rename to src/test/emu/spc700_test.cc index 03175d1b..7ecb9628 100644 --- a/test/emu/spc700_test.cc +++ b/src/test/emu/spc700_test.cc @@ -9,6 +9,7 @@ namespace emu_test { using testing::_; using testing::Return; +using yaze::app::emu::audio::ApuCallbacks; using yaze::app::emu::audio::AudioRam; using yaze::app::emu::audio::Spc700; @@ -70,7 +71,8 @@ class Spc700Test : public ::testing::Test { } testing::StrictMock audioRAM; - Spc700 spc700{audioRAM}; + ApuCallbacks callbacks_; + Spc700 spc700{audioRAM, callbacks_}; }; // ======================================================== diff --git a/test/gfx/compression_test.cc b/src/test/gfx/compression_test.cc similarity index 100% rename from test/gfx/compression_test.cc rename to src/test/gfx/compression_test.cc diff --git a/test/gfx/snes_palette_test.cc b/src/test/gfx/snes_palette_test.cc similarity index 100% rename from test/gfx/snes_palette_test.cc rename to src/test/gfx/snes_palette_test.cc diff --git a/test/yaze_test.cc b/src/test/yaze_test.cc similarity index 100% rename from test/yaze_test.cc rename to src/test/yaze_test.cc diff --git a/test/zelda3/room_object_test.cc b/src/test/zelda3/room_object_test.cc similarity index 100% rename from test/zelda3/room_object_test.cc rename to src/test/zelda3/room_object_test.cc