Compare commits
2 Commits
pre-0.2.2-
...
pre-0.2.2-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546093360f | ||
|
|
d94b7a3e81 |
11
.github/workflows/cmake.yml
vendored
11
.github/workflows/cmake.yml
vendored
@@ -2,8 +2,14 @@ name: CMake
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
branches: [ "master" ]
|
||||
|
||||
env:
|
||||
@@ -18,13 +24,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Video Libs
|
||||
run: sudo apt install libglew-dev
|
||||
|
||||
- name: Install Audio Libs
|
||||
run: sudo apt install libwavpack-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
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# CMake Specifications --------------------------------------------------------
|
||||
# CMake Specifications
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Yet Another Zelda3 Editor
|
||||
# by scawful
|
||||
project(yaze VERSION 0.01)
|
||||
|
||||
# C++ Standard Specifications -------------------------------------------------
|
||||
# C++ Standard Specifications
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
|
||||
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)
|
||||
@@ -18,46 +18,22 @@ 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)
|
||||
|
||||
# Abseil Standard Specifications ----------------------------------------------
|
||||
# Abseil Standard Specifications
|
||||
include(cmake/absl.cmake)
|
||||
add_subdirectory(src/lib/abseil-cpp)
|
||||
|
||||
include(cmake/openssl.cmake)
|
||||
set(Protobuf_PROTOC_EXECUTABLE "/Users/scawful/code/protobuf/bin/protoc")
|
||||
set(Protobuf_INCLUDE_DIRS "/Users/scawful/code/protobuf/include/")
|
||||
set(PROTOBUF_INCLUDE_PATH ${CMAKE_CURRENT_BINARY_DIR}
|
||||
CACHE INTERNAL "Path to generated protobuf files.")
|
||||
include_directories(${PROTOBUF_INCLUDE_PATH})
|
||||
|
||||
# Video Libraries -------------------------------------------------------------
|
||||
# Video Libraries
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLEW REQUIRED)
|
||||
include(cmake/sdl2.cmake)
|
||||
|
||||
# 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)
|
||||
# Asar
|
||||
# add_subdirectory(src/lib/asar/src)
|
||||
# include(cmake/asar.cmake)
|
||||
|
||||
# Asar ------------------------------------------------------------------------
|
||||
add_subdirectory(src/lib/asar/src)
|
||||
include(cmake/asar.cmake)
|
||||
|
||||
# snes-spc --------------------------------------------------------------------
|
||||
# snes-spc
|
||||
ADD_DEFINITIONS(-DSNES_SPC_EXPORTS)
|
||||
set(SNES_SPC_SOURCES
|
||||
"../src/lib/snes_spc/snes_spc/spc.cpp"
|
||||
@@ -71,9 +47,9 @@ set(SNES_SPC_SOURCES
|
||||
"../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/spc700/spc700.def)
|
||||
ADD_LIBRARY(snes_spc STATIC ${SNES_SPC_SOURCES} src/app/zelda3/music/spc700.def)
|
||||
|
||||
# ImGui -----------------------------------------------------------------------
|
||||
# ImGui
|
||||
include(cmake/imgui.cmake)
|
||||
|
||||
# Project Files
|
||||
|
||||
16
README.md
16
README.md
@@ -15,24 +15,38 @@ Building and installation
|
||||
|
||||
1. Clone the repository
|
||||
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/scawful/yaze.git
|
||||
```
|
||||
|
||||
2. Create the build directory and configuration
|
||||
|
||||
```
|
||||
cmake -S . -B build
|
||||
```
|
||||
|
||||
3. Build and run.
|
||||
|
||||
```
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
License
|
||||
--------
|
||||
YAZE is distributed under the [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.txt) license.
|
||||
|
||||
SDL2, ImGUI and Abseil are subject to respective licenses.
|
||||
SDL2, ImGui and Abseil are subject to respective licenses.
|
||||
|
||||
Screenshots
|
||||
--------
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
Submodule assets/asm/alttp-hacker-workspace updated: ea81eb3425...2520fb70c3
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/font/NotoSansJP.ttf
Normal file
BIN
assets/font/NotoSansJP.ttf
Normal file
Binary file not shown.
@@ -27,8 +27,8 @@ set(
|
||||
${IMGUI_PATH}/imgui_demo.cpp
|
||||
${IMGUI_PATH}/imgui_draw.cpp
|
||||
${IMGUI_PATH}/imgui_widgets.cpp
|
||||
${IMGUI_PATH}/backends/imgui_impl_sdl.cpp
|
||||
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer.cpp
|
||||
${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp
|
||||
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp
|
||||
${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
|
||||
${IMGUI_FILE_DLG_PATH}/ImGuiFileDialog.cpp
|
||||
${IMGUI_COLOR_TEXT_EDIT_PATH}/TextEditor.cpp
|
||||
|
||||
19
cmake/sdl2.cmake
Normal file
19
cmake/sdl2.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
# SDL2, SDL2_image and SDL2_mixer
|
||||
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)
|
||||
@@ -1,26 +0,0 @@
|
||||
## September 2022
|
||||
|
||||
- Drawing Overworld maps to the screen
|
||||
- Drawing entrance data on the overworld
|
||||
- Drawing 2bpp inventory graphics data
|
||||
- Started the YazeDelta project for version control.
|
||||
|
||||
## August 2022
|
||||
|
||||
- Added ValidateCompressionResults to ROM::Compress
|
||||
- Improved Overworld systems in preparation for drawing maps.
|
||||
|
||||
## July 2022
|
||||
|
||||
- Display current overworld map graphics tile sheets.
|
||||
- Added CreateAllGraphicsData to the ROM class
|
||||
- Added Google Abseil C++ library for error handling, string manipulation
|
||||
- Refactor ROM class to use smart pointers and STL containers
|
||||
|
||||
## June 2022
|
||||
|
||||
- Implemented LC_LZ2 Decompression
|
||||
- Created Bitmap class for displaying SNES Graphics
|
||||
- Added Overworld and OverworldMap class definitions
|
||||
- Built user interface using ImGui and SDL2
|
||||
- Started YAZE
|
||||
67
docs/compression.md
Normal file
67
docs/compression.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# LC_LZ2 Compression
|
||||
|
||||
The compression algorithm has multiple implementations with varying levels of quality, based primarily on the implementations made in skarsnik/sneshacking, Zarby89/ZScreamDungeon and ZCompress with optimizations made for C++.
|
||||
|
||||
## Key Definitions
|
||||
|
||||
### Constants and Macros:
|
||||
- `BUILD_HEADER(command, length)`: Macro to build a header from a command and a length.
|
||||
- Command Constants: Constants to represent different commands like `kCommandDirectCopy`, `kCommandByteFill`, etc.
|
||||
- Length and Mode Constants: Such as `kMaxLengthNormalHeader`, `kNintendoMode1`, etc.
|
||||
|
||||
### Data Structures:
|
||||
|
||||
#### 1. CompressionCommand:
|
||||
- **arguments**: 2D array representing the command arguments for each possible command.
|
||||
- **cmd_size**: Array storing the size of each possible command.
|
||||
- **data_size**: Array storing the size of the data processed by each possible command.
|
||||
|
||||
#### 2. CompressionPiece:
|
||||
- **command**: Represents the compression command.
|
||||
- **length**: Length of the compressed data piece.
|
||||
- **argument_length**: Length of the argument.
|
||||
- **argument**: Argument as a string.
|
||||
- **next**: Pointer to the next compression piece.
|
||||
|
||||
#### 3. CompressionContext (for Compression V3):
|
||||
- Contains vectors to store raw and compressed data, compression pieces, and compression string.
|
||||
- Various counters and flags for compression control.
|
||||
- Current compression command details.
|
||||
|
||||
## Compression Functions
|
||||
|
||||
### Version 1:
|
||||
- **Byte Repeat**: `CheckByteRepeat`
|
||||
- **Word Repeat**: `CheckWordRepeat`
|
||||
- **Increasing Byte**: `CheckIncByte`
|
||||
- **Intra Copy**: `CheckIntraCopy`
|
||||
- **Validation and Alternatives**: `ValidateForByteGain` & `CompressionCommandAlternative`
|
||||
|
||||
### Version 2:
|
||||
- **Byte Repeat**: `CheckByteRepeatV2`
|
||||
- **Word Repeat**: `CheckWordRepeatV2`
|
||||
- **Increasing Byte**: `CheckIncByteV2`
|
||||
- **Intra Copy**: `CheckIntraCopyV2`
|
||||
- **Validation and Alternatives**: `ValidateForByteGainV2` & `CompressionCommandAlternativeV2`
|
||||
|
||||
### Version 3:
|
||||
Using `CompressionContext` to handle compression.
|
||||
- **Initialization**: `InitializeCompression`
|
||||
- **Command Checks**: Such as `CheckByteRepeatV3`
|
||||
- **Determining Best Compression**: `DetermineBestCompression`
|
||||
- **Handling Direct Copy**: `HandleDirectCopy`
|
||||
- **Adding Compression to Chain**: `AddCompressionToChain`
|
||||
|
||||
## Decompression Functions:
|
||||
- `SetBuffer`: Prepares a buffer from data.
|
||||
- `memfill`: Fills memory.
|
||||
- **Decompression**: Such as `DecompressV2`, `DecompressGraphics`, and `DecompressOverworld`.
|
||||
|
||||
## Utility Functions:
|
||||
- **Printing**: Such as `PrintCompressionPiece` and `PrintCompressionChain`.
|
||||
- **Compression String Creation**: `CreateCompressionString`
|
||||
- **Compression Result Validation**: Such as `ValidateCompressionResult` and its V3 variant.
|
||||
- **Compression Piece Manipulation**: Like `SplitCompressionPiece` and its V3 variant.
|
||||
|
||||
## Final Notes
|
||||
The YAZE's LC_LZ2 compression scheme provides three versions of compression methodologies with comprehensive support for various commands and modes. It ensures versatility and adaptability for different compression needs.
|
||||
32
docs/getting-started.md
Normal file
32
docs/getting-started.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Getting Started with YAZE
|
||||
|
||||
This software allows you to modify the classic SNES game "The Legend of Zelda: A Link to the Past" by editing its ROM file. With this editor, you can change various aspects of the game, such as the maps, sprites, items, and more.
|
||||
|
||||
Please note that this project is currently a work in progress, and some features may not be fully implemented or may be subject to change.
|
||||
|
||||
## Prerequisites
|
||||
Before you start using YAZE, make sure you have the following:
|
||||
|
||||
- A copy of "The Legend of Zelda: A Link to the Past" ROM file (US or JP)
|
||||
- Basic knowledge of hexadecimal and binary data
|
||||
|
||||
## Installation
|
||||
To install YAZE, follow these steps based on your platform:
|
||||
|
||||
### Windows
|
||||
|
||||
### MacOS
|
||||
|
||||
### GNU/Linux
|
||||
|
||||
## Usage
|
||||
To use the Link to the Past ROM Editor, follow these steps:
|
||||
|
||||
Open the "ALTTP.sfc" ROM file using the "File" menu.
|
||||
|
||||
...
|
||||
|
||||
Save your changes using the "File" menu.
|
||||
Backup files are enabled by default. Each save will produce a timestamped copy of your ROM before you last saved.
|
||||
|
||||
That's it! With these instructions, you should be able to get started with using YAZE. Happy editing!
|
||||
209
docs/infrastructure.md
Normal file
209
docs/infrastructure.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# YAZE Infrastructure Overview
|
||||
|
||||
For developers to reference.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
- **.github/workflows**: Contains `yaze_test` workflow config.
|
||||
- **assets**: Hosts assets like fonts.
|
||||
- **cmake**: Contains CMake configurations.
|
||||
- **docs**: Contains documentation for users and developers.
|
||||
- [Getting Started](./getting-started.md)
|
||||
- [LC_LZ2 Compression](./compression.md)
|
||||
- **src**: Contains source files.
|
||||
- **app**: Contains the GUI editor `yaze`
|
||||
- **cli**: Contains the command line interface `z3ed`
|
||||
- **lib**: Contains git submodule dependencies.
|
||||
- Abseil-cpp
|
||||
- Asar
|
||||
- ImGui
|
||||
- ImGuiFileDialog
|
||||
- ImGuiColorTextEdit
|
||||
- imgui_memory_editor
|
||||
- SDL2
|
||||
- **test**: Contains testing interface `yaze_test`
|
||||
|
||||
## App Organization
|
||||
|
||||
- **Core Namespace**:
|
||||
- Contains fundamental functionalities.
|
||||
- [Common](../src/app/core/common.h)
|
||||
- [Constants](../src/app/core/constants.h)
|
||||
- [Controller](../src/app/core/controller.h)
|
||||
- [Editor](../src/app/core/editor.h)
|
||||
- [Pipeline](../src/app/gui/pipeline.h)
|
||||
- **Editor Namespace**:
|
||||
- Editors are responsible for representing the GUI view and handling user input.
|
||||
- These classes are all controlled by [MasterEditor](../src/app/editor/master_editor.h)
|
||||
- [DungeonEditor](../src/app/editor/dungeon_editor.h)
|
||||
- [GraphicsEditor](../src/app/editor/graphics_editor.h)
|
||||
- [OverworldEditor](../src/app/editor/overworld_editor.h)
|
||||
- [ScreenEditor](../src/app/editor/screen_editor.h)
|
||||
- [SpriteEditor](../src/app/editor/sprite_editor.h)
|
||||
- **Modules**
|
||||
- [AssemblyEditor](../src/app/editor/modules/assembly_editor.h)
|
||||
- [MusicEditor](../src/app/editor/modules/music_editor.h)
|
||||
- [GfxGroupEditor](../src/app/editor/modules/gfx_group_editor.h)
|
||||
- [Tile16Editor](../src/app/editor/modules/tile16_editor.h)
|
||||
- **Emu Namespace**:
|
||||
- Contains business logic for `core::emulator`
|
||||
- [Audio](../src/app/emu/audio/)
|
||||
- [Debug](../src/app/emu/debug/)
|
||||
- [Memory](../src/app/emu/memory/)
|
||||
- [Video](../src/app/emu/video/)
|
||||
- [Emulator](../src/app/emu/emulator.h)
|
||||
- **Gfx Namespace**:
|
||||
- Handles graphics related tasks.
|
||||
- [Bitmap](../src/app/gfx/bitmap.h)
|
||||
- [Compression](../src/app/gfx/compression.h)
|
||||
- [SCAD Format](../src/app/gfx/scad_format.h)
|
||||
- [SNES Palette](../src/app/gfx/snes_palette.h)
|
||||
- [SNES Tile](../src/app/gfx/snes_tile.h)
|
||||
- **Gui Namespace**:
|
||||
- Manages GUI elements.
|
||||
- [Canvas](../src/app/gui/canvas.h)
|
||||
- [Color](../src/app/gui/color.h)
|
||||
- [Icons](../src/app/gui/icons.h)
|
||||
- [Input](../src/app/gui/input.h)
|
||||
- [Style](../src/app/gui/style.h)
|
||||
- [Widgets](../src/app/gui/widgets.h)
|
||||
- **Zelda3 Namespace**:
|
||||
- Holds business logic specific to Zelda3.
|
||||
- [Dungeon](../src/app/zelda3/dungeon/)
|
||||
- [Music](../src/app/zelda3/music/)
|
||||
- [Screen](../src/app/zelda3/screen/)
|
||||
- [Sprite](../src/app/zelda3/sprite/)
|
||||
- [OverworldMap](../src/app/zelda3/overworld_map.h)
|
||||
- [Overworld](../src/app/zelda3/overworld.h)
|
||||
|
||||
### Flow of Control
|
||||
|
||||
- [app/yaze.cc](../src/app/yaze.cc)
|
||||
- Initializes `absl::FailureSignalHandler` for stack tracing.
|
||||
- Runs the `core::Controller` loop.
|
||||
- [app/core/controller.cc](../src/app/core/controller.cc)
|
||||
- Initializes SDLRenderer and SDLWindow
|
||||
- Initializes ImGui, fonts, themes, and clipboard.
|
||||
- Handles user input from keyboard and mouse.
|
||||
- Updates `editor::MasterEditor`
|
||||
- Renders the output to the screen.
|
||||
- Handles the teardown of SDL and ImGui resources.
|
||||
- [app/editor/master_editor.cc](../src/app/editor/master_editor.cc)
|
||||
- Handles the main menu bar.
|
||||
- File
|
||||
- Open - [app::ROM::LoadFromFile](../src/app/rom.cc#l=90)
|
||||
- Save - [app::ROM::SaveToFile](../src/app/rom.cc#l=301)
|
||||
- Edit
|
||||
- View
|
||||
- Emulator
|
||||
- HEX Editor
|
||||
- ASM Editor
|
||||
- Palette Editor
|
||||
- Memory Viewer
|
||||
- ImGui Demo
|
||||
- GUI Tools
|
||||
- Runtime Metrics
|
||||
- Style Editor
|
||||
- Help
|
||||
- Handles `absl::Status` errors as popups delivered to the user.
|
||||
- Update all the editors in a tab view.
|
||||
- [app/editor/assembly_editor.cc](../src/app/editor/assembly_editor.cc)
|
||||
- [app/editor/dungeon_editor.cc](../src/app/editor/dungeon_editor.cc)
|
||||
- [app/editor/graphics_editor.cc](../src/app/editor/graphics_editor.cc)
|
||||
- [app/editor/music_editor.cc](../src/app/editor/music_editor.cc)
|
||||
- [app/editor/overworld_editor.cc](../src/app/editor/overworld_editor.cc)
|
||||
- [app/editor/screen_editor.cc](../src/app/editor/screen_editor.cc)
|
||||
- [app/editor/sprite_editor.cc](../src/app/editor/sprite_editor.cc)
|
||||
|
||||
## ROM
|
||||
- [app/rom.cc](../src/app/rom.cc)
|
||||
- [app/rom.h](../src/app/rom.h)
|
||||
---
|
||||
|
||||
This `ROM` class provides methods to manipulate and access data from a ROM.
|
||||
|
||||
- **Key Methods**:
|
||||
- `Load2BppGraphics()`: Loads 2BPP graphics data from specified sheets.
|
||||
- `LoadAllGraphicsData()`: Loads all graphics data, both compressed and uncompressed, converting where necessary.
|
||||
- `LoadFromFile(const absl::string_view& filename, bool z3_load)`: Loads ROM data from a file. It also handles headers and Zelda 3 specific data if requested.
|
||||
- `LoadFromPointer(uchar* data, size_t length)`: Loads ROM data from a provided pointer.
|
||||
- `LoadFromBytes(const Bytes& data)`: Loads ROM data from bytes.
|
||||
- `LoadAllPalettes()`: Loads all color palettes used in the ROM. This includes palettes for various elements like sprites, shields, swords, etc.
|
||||
- `UpdatePaletteColor(...)`: Updates a specific color within a named palette group.
|
||||
|
||||
- **Internal Data Structures**:
|
||||
- `rom_data_`: A container that holds the ROM data.
|
||||
- `graphics_bin_`: Holds the graphics data.
|
||||
- `palette_groups_`: A map containing various palette groups, each having its own set of color palettes.
|
||||
|
||||
- **Special Notes**:
|
||||
- The class interacts with various external functionalities, such as decompression algorithms (`gfx::DecompressV2`) and color conversion (`gfx::SnesTo8bppSheet`).
|
||||
- Headers in the ROM data, if present, are identified and removed.
|
||||
- Specific Zelda 3 data can be loaded if specified.
|
||||
- Palettes are categorized into multiple groups (e.g., `ow_main`, `ow_aux`, `hud`, etc.) and loaded accordingly.
|
||||
|
||||
|
||||
## Overworld
|
||||
|
||||
- [app/zelda3/overworld.cc](../src/app/zelda3/overworld.cc)
|
||||
- [app/zelda3/overworld.h](../src/app/zelda3/overworld.h)
|
||||
- [app/zelda3/overworld_map.cc](../src/app/zelda3/overworld_map.cc)
|
||||
- [app/zelda3/overworld_map.h](../src/app/zelda3/overworld_map.h)
|
||||
|
||||
---
|
||||
|
||||
- **Construction of Tile16 and Tile32**
|
||||
- **Save and Load Resources**
|
||||
- Sprites
|
||||
- Entrances
|
||||
- Tilemaps
|
||||
|
||||
## Bitmap
|
||||
|
||||
- [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc)
|
||||
- [app/gfx/bitmap.h](../src/app/gfx/bitmap.cc)
|
||||
---
|
||||
|
||||
This class is responsible for creating, managing, and manipulating bitmap data, which can be displayed on the screen using the ImGui library.
|
||||
|
||||
### Key Attributes:
|
||||
|
||||
1. **Width, Height, Depth, and Data Size**: These represent the dimensions and data size of the bitmap.
|
||||
2. **Pixel Data**: Points to the raw data of the bitmap.
|
||||
3. **Texture and Surface**: Use SDL to manage the graphical representation of the bitmap data. Both these attributes have custom deleters, ensuring proper resource management.
|
||||
|
||||
### Main Functions:
|
||||
|
||||
1. **Constructors**: Multiple constructors allow for different ways to create a Bitmap instance, like specifying width, height, depth, and data.
|
||||
2. **Create**: This set of overloaded functions provides ways to create a bitmap from different data sources.
|
||||
3. **CreateFromSurface**: Allows for the creation of a bitmap from an SDL_Surface.
|
||||
4. **Apply**: Changes the bitmap's data to a new set of Bytes.
|
||||
5. **Texture Operations**:
|
||||
- **CreateTexture**: Creates an SDL_Texture from the bitmap's data for rendering.
|
||||
- **UpdateTexture**: Updates the SDL_Texture with the latest bitmap data.
|
||||
6. **SaveSurfaceToFile**: Saves the SDL_Surface to a file.
|
||||
7. **SetSurface**: Assigns a new SDL_Surface to the bitmap.
|
||||
8. **Palette Functions**:
|
||||
- **ApplyPalette (Overloaded)**: This allows for the application of a SNESPalette or a standard SDL_Color palette to the bitmap.
|
||||
9. **WriteToPixel**: Directly writes a value to a specified position in the pixel data.
|
||||
|
||||
## Z3ED cli
|
||||
|
||||
| Command | Arg | Params | Status |
|
||||
|---------|-----|--------|--------|
|
||||
| Apply BPS Patch | -a | rom_file bps_file | In progress |
|
||||
| Create BPS Patch | -c | bps_file src_file modified_file | Not started |
|
||||
| Open ROM | -o | rom_file | Complete |
|
||||
| Backup ROM | -b | rom_file [new_file] | In progress |
|
||||
| Expand ROM | -x | rom_file file_size | Not started |
|
||||
| Transfer Tile16 | -t | src_rom dest_rom tile32_id_list(csv) | Complete |
|
||||
| Export Graphics | -e | rom_file bin_file | In progress |
|
||||
| Import Graphics | -i | bin_file rom_file | Not started |
|
||||
| SNES to PC Address | -s | address | Complete |
|
||||
| PC to SNES Address | -p | address | Complete |
|
||||
|
||||
|
||||
## Further Development Ideas
|
||||
- Extend `zelda3` namespace with additional functionalities.
|
||||
- Optimize program performance.
|
||||
- Introduce new features in the GUI editor.
|
||||
6
docs/macos-build.md
Normal file
6
docs/macos-build.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# macOS Build Settings
|
||||
|
||||
- Clang 15.0.1 x86_64-apple-darrwin22.5.0
|
||||
- SDL2 Source v2.26.5
|
||||
- Removed snes_spc
|
||||
- Removed asar_static
|
||||
Binary file not shown.
@@ -1,40 +0,0 @@
|
||||
\documentclass[12pt, oneside]{report}
|
||||
\title{Yet Another Zelda3 Editor}
|
||||
\author{Justin Scofield\thanks{Special thanks to JaredBrian, Zarby89}}
|
||||
\date{June 2022 - October 2022}
|
||||
\pagestyle{headings}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\tableofcontents
|
||||
|
||||
\chapter{Introduction}
|
||||
|
||||
{\bf Yet Another Zelda3 Editor} is a multi-purpose editor for the retro video game title {\it {"The Legend of Zelda: A Link to the Past"}} for the Super Nintendo Entertainment System. The editor only supports the US version.
|
||||
|
||||
\section{Getting Started}
|
||||
\section{Loading from ROM}
|
||||
\section{Saving to ROM}
|
||||
|
||||
\chapter{Overworld}
|
||||
|
||||
The editor provides an interface for the user to make various changes to the overworld maps. These changes include the manpulation of the maps tiles, palettes, entrances, exits, sprites, area music, and other properties. Here we will explain the basics of the tile system.
|
||||
|
||||
\section{Tile System}
|
||||
\section{Map Toolset}
|
||||
\section{Map Canvas}
|
||||
|
||||
\chapter{Dungeons}
|
||||
|
||||
\chapter{Palettes}
|
||||
|
||||
\chapter{Sprites}
|
||||
|
||||
\chapter{Screens}
|
||||
\section{Inventory}
|
||||
\section{Heads-up Display}
|
||||
|
||||
\chapter{Modules}
|
||||
|
||||
\end{document}
|
||||
@@ -1,100 +1,107 @@
|
||||
# yaze source files -----------------------------------------------------------
|
||||
set(
|
||||
YAZE_APP_CORE_SRC
|
||||
app/core/common.cc
|
||||
app/core/controller.cc
|
||||
app/core/labeling.cc
|
||||
)
|
||||
|
||||
set(
|
||||
YAZE_APP_EDITOR_SRC
|
||||
app/editor/assembly_editor.cc
|
||||
app/editor/dungeon_editor.cc
|
||||
app/editor/graphics_editor.cc
|
||||
app/editor/master_editor.cc
|
||||
app/editor/music_editor.cc
|
||||
app/editor/overworld_editor.cc
|
||||
app/editor/palette_editor.cc
|
||||
app/editor/screen_editor.cc
|
||||
app/editor/sprite_editor.cc
|
||||
app/editor/modules/music_editor.cc
|
||||
app/editor/modules/palette_editor.cc
|
||||
app/editor/modules/assembly_editor.cc
|
||||
app/editor/modules/tile16_editor.cc
|
||||
app/editor/modules/gfx_group_editor.cc
|
||||
app/editor/context/gfx_context.cc
|
||||
)
|
||||
|
||||
set(
|
||||
YAZE_APP_GFX_SRC
|
||||
app/gfx/bitmap.cc
|
||||
app/gfx/compression.cc
|
||||
app/gfx/scad_format.cc
|
||||
app/gfx/snes_palette.cc
|
||||
app/gfx/snes_tile.cc
|
||||
app/gfx/snes_color.cc
|
||||
)
|
||||
|
||||
set(
|
||||
YAZE_APP_ZELDA3_SRC
|
||||
app/zelda3/inventory.cc
|
||||
app/zelda3/overworld_map.cc
|
||||
app/zelda3/overworld.cc
|
||||
app/zelda3/title_screen.cc
|
||||
app/zelda3/sprite.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
|
||||
gui/canvas.cc
|
||||
gui/input.cc
|
||||
gui/style.cc
|
||||
gui/widgets.cc
|
||||
gui/color.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
|
||||
)
|
||||
|
||||
# executable creation ---------------------------------------------------------
|
||||
|
||||
add_executable(
|
||||
yaze
|
||||
app/yaze.cc
|
||||
app/rom.cc
|
||||
${YAZE_APP_CORE_SRC}
|
||||
${YAZE_APP_EDITOR_SRC}
|
||||
${YAZE_APP_GFX_SRC}
|
||||
${YAZE_APP_ZELDA3_SRC}
|
||||
${YAZE_GUI_SRC}
|
||||
${ASAR_STATIC_SRC}
|
||||
${SNES_SPC_SOURCES}
|
||||
${IMGUI_SRC}
|
||||
)
|
||||
|
||||
# including libraries ---------------------------------------------------------
|
||||
|
||||
target_include_directories(
|
||||
yaze PUBLIC
|
||||
lib/
|
||||
app/
|
||||
${CMAKE_SOURCE_DIR}/src/
|
||||
${PNG_INCLUDE_DIRS}
|
||||
${SDL2_INCLUDE_DIR}
|
||||
lib/SDL_mixer/include/
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
lib/asar/src/asar/
|
||||
lib/snes_spc/snes_spc/
|
||||
set(
|
||||
YAZE_APP_EMU_SRC
|
||||
app/emu/emulator.cc
|
||||
app/emu/audio/apu.cc
|
||||
app/emu/audio/spc700.cc
|
||||
app/emu/audio/dsp.cc
|
||||
app/emu/audio/internal/addressing.cc
|
||||
app/emu/audio/internal/instructions.cc
|
||||
app/emu/cpu/internal/addressing.cc
|
||||
app/emu/cpu/internal/instructions.cc
|
||||
app/emu/cpu/cpu.cc
|
||||
app/emu/video/ppu.cc
|
||||
app/emu/memory/dma.cc
|
||||
app/emu/memory/memory.cc
|
||||
app/emu/snes.cc
|
||||
)
|
||||
|
||||
set(SDL_TARGETS SDL2::SDL2)
|
||||
|
||||
if(WIN32 OR MINGW)
|
||||
list(PREPEND SDL_TARGETS SDL2::SDL2main)
|
||||
list(PREPEND SDL_TARGETS SDL2::SDL2main ws2_32)
|
||||
add_definitions(-DSDL_MAIN_HANDLED)
|
||||
endif()
|
||||
|
||||
# linking libraries -----------------------------------------------------------
|
||||
|
||||
target_link_libraries(
|
||||
yaze PUBLIC
|
||||
${ABSL_TARGETS}
|
||||
${SDL_TARGETS}
|
||||
${SDLMIXER_LIBRARY}
|
||||
SDL2_mixer
|
||||
${PNG_LIBRARIES}
|
||||
${GLEW_LIBRARIES}
|
||||
${OPENGL_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
asar-static
|
||||
snes_spc
|
||||
ImGui
|
||||
if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
|
||||
list(APPEND YAZE_APP_CORE_SRC
|
||||
app/core/platform/font_loader.cc
|
||||
app/core/platform/clipboard.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
list(APPEND YAZE_APP_CORE_SRC
|
||||
app/core/platform/file_dialog.mm
|
||||
app/core/platform/app_delegate.mm
|
||||
app/core/platform/font_loader.mm
|
||||
app/core/platform/clipboard.mm
|
||||
)
|
||||
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
if(NOT COCOA_LIBRARY)
|
||||
message(FATAL_ERROR "Cocoa not found")
|
||||
endif()
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-framework ServiceManagement -framework Foundation -framework Cocoa")
|
||||
endif()
|
||||
|
||||
include(app/CMakeLists.txt)
|
||||
include(cli/CMakeLists.txt)
|
||||
|
||||
if (UNIX)
|
||||
target_compile_definitions(yaze PRIVATE "linux")
|
||||
@@ -109,7 +116,7 @@ if(MACOS)
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
# MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(yaze
|
||||
@@ -128,56 +135,3 @@ set_target_properties(yaze
|
||||
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
|
||||
)
|
||||
endif()
|
||||
|
||||
# add_subdirectory(app/delta)
|
||||
|
||||
# add_executable(
|
||||
# yaze_delta
|
||||
# app/delta/delta.cc
|
||||
# app/delta/viewer.cc
|
||||
# app/delta/service.cc
|
||||
# app/delta/client.cc
|
||||
# app/rom.cc
|
||||
# ${YAZE_APP_ASM_SRC}
|
||||
# ${YAZE_APP_CORE_SRC}
|
||||
# ${YAZE_APP_EDITOR_SRC}
|
||||
# ${YAZE_APP_GFX_SRC}
|
||||
# ${YAZE_APP_ZELDA3_SRC}
|
||||
# ${YAZE_GUI_SRC}
|
||||
# ${IMGUI_SRC}
|
||||
# ${ASAR_STATIC_SRC}
|
||||
# )
|
||||
|
||||
# target_include_directories(
|
||||
# yaze_delta PUBLIC
|
||||
# lib/
|
||||
# app/
|
||||
# lib/asar/src/
|
||||
# ${ASAR_INCLUDE_DIR}
|
||||
# ${CMAKE_SOURCE_DIR}/src/
|
||||
# ${PNG_INCLUDE_DIRS}
|
||||
# ${SDL2_INCLUDE_DIR}
|
||||
# ${GLEW_INCLUDE_DIRS}
|
||||
# )
|
||||
|
||||
# target_link_libraries(
|
||||
# yaze_delta PUBLIC
|
||||
# ${ABSL_TARGETS}
|
||||
# ${SDL_TARGETS}
|
||||
# ${PNG_LIBRARIES}
|
||||
# ${GLEW_LIBRARIES}
|
||||
# ${OPENGL_LIBRARIES}
|
||||
# ${CMAKE_DL_LIBS}
|
||||
# delta-service
|
||||
# asar-static
|
||||
# ImGui
|
||||
# )
|
||||
# target_compile_definitions(yaze_delta PRIVATE "linux")
|
||||
# target_compile_definitions(yaze_delta PRIVATE "stricmp=strcasecmp")
|
||||
|
||||
# set (source "${CMAKE_SOURCE_DIR}/assets")
|
||||
# set (destination "${CMAKE_CURRENT_BINARY_DIR}/assets")
|
||||
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
# COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination}
|
||||
# DEPENDS ${destination}
|
||||
# COMMENT "symbolic link resources folder from ${source} => ${destination}")
|
||||
36
src/app/CMakeLists.txt
Normal file
36
src/app/CMakeLists.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
add_executable(
|
||||
yaze
|
||||
app/yaze.cc
|
||||
app/rom.cc
|
||||
${YAZE_APP_EMU_SRC}
|
||||
${YAZE_APP_CORE_SRC}
|
||||
${YAZE_APP_EDITOR_SRC}
|
||||
${YAZE_APP_GFX_SRC}
|
||||
${YAZE_APP_ZELDA3_SRC}
|
||||
${YAZE_GUI_SRC}
|
||||
${IMGUI_SRC}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
yaze PUBLIC
|
||||
lib/
|
||||
app/
|
||||
lib/SDL_mixer/include/
|
||||
${CMAKE_SOURCE_DIR}/src/
|
||||
${PNG_INCLUDE_DIRS}
|
||||
${SDL2_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
yaze PUBLIC
|
||||
${ABSL_TARGETS}
|
||||
${SDL_TARGETS}
|
||||
${PNG_LIBRARIES}
|
||||
${CMAKE_DL_LIBS}
|
||||
SDL2_mixer
|
||||
ImGui
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
|
||||
endif()
|
||||
@@ -1,20 +1,66 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
unsigned int SnesToPc(unsigned int addr) {
|
||||
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_;
|
||||
|
||||
std::string UppercaseHexByte(uint8_t byte, bool leading) {
|
||||
if (leading) {
|
||||
std::string result = absl::StrFormat("0x%02X", byte);
|
||||
return result;
|
||||
}
|
||||
std::string result = absl::StrFormat("%02X", byte);
|
||||
return result;
|
||||
}
|
||||
std::string UppercaseHexWord(uint16_t word) {
|
||||
std::string result = absl::StrFormat("0x%04x", word);
|
||||
return result;
|
||||
}
|
||||
std::string UppercaseHexLong(uint32_t dword) {
|
||||
std::string result = absl::StrFormat("0x%08x", dword);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t SnesToPc(uint32_t addr) {
|
||||
if (addr >= 0x808000) {
|
||||
addr -= 0x808000;
|
||||
}
|
||||
unsigned int temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
|
||||
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
|
||||
return (temp + 0x0);
|
||||
}
|
||||
|
||||
uint32_t PcToSnes(uint32_t addr) {
|
||||
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
|
||||
b[2] = static_cast<uint8_t>(b[2] * 2);
|
||||
|
||||
if (b[1] >= 0x80) {
|
||||
b[2] += 1;
|
||||
} else {
|
||||
b[1] += 0x80;
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) {
|
||||
uint32_t result = 0;
|
||||
result = (bank << 16) | addr;
|
||||
return result;
|
||||
}
|
||||
|
||||
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3) {
|
||||
return (addr1 << 16) | (addr2 << 8) | addr3;
|
||||
}
|
||||
@@ -58,6 +104,76 @@ bool StringReplace(std::string &str, const std::string &from,
|
||||
return true;
|
||||
}
|
||||
|
||||
void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) {
|
||||
uint8_t v = (p_val >> (8 * p_index)) & 0xff;
|
||||
|
||||
p_arr[p_index] = v;
|
||||
}
|
||||
|
||||
void stle0(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 0, p_val);
|
||||
}
|
||||
|
||||
void stle1(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 1, p_val);
|
||||
}
|
||||
|
||||
void stle2(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 2, p_val);
|
||||
}
|
||||
|
||||
void stle3(uint8_t *const p_arr, unsigned const p_val) {
|
||||
stle(p_arr, 3, p_val);
|
||||
}
|
||||
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
|
||||
stle0(p_arr, p_val);
|
||||
stle1(p_arr, p_val);
|
||||
}
|
||||
// "Store little endian 16-bit value using a byte pointer, offset by an
|
||||
// index before dereferencing"
|
||||
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
|
||||
uint16_t const p_val) {
|
||||
stle16b(p_arr + (p_index * 2), p_val);
|
||||
}
|
||||
// "load little endian value at the given byte offset and shift to get its
|
||||
// value relative to the base offset (powers of 256, essentially)"
|
||||
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
|
||||
uint32_t v = p_arr[p_index];
|
||||
|
||||
v <<= (8 * p_index);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// Helper function to get the first byte in a little endian number
|
||||
uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); }
|
||||
|
||||
// Helper function to get the second byte in a little endian number
|
||||
uint32_t ldle1(uint8_t const *const p_arr) { return ldle(p_arr, 1); }
|
||||
|
||||
// Helper function to get the third byte in a little endian number
|
||||
uint32_t ldle2(uint8_t const *const p_arr) { return ldle(p_arr, 2); }
|
||||
|
||||
// Helper function to get the third byte in a little endian number
|
||||
uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); }
|
||||
// Load little endian halfword (16-bit) dereferenced from
|
||||
uint16_t ldle16b(uint8_t const *const p_arr) {
|
||||
uint16_t v = 0;
|
||||
|
||||
v |= (ldle0(p_arr) | ldle1(p_arr));
|
||||
|
||||
return v;
|
||||
}
|
||||
// Load little endian halfword (16-bit) dereferenced from an arrays of bytes.
|
||||
// This version provides an index that will be multiplied by 2 and added to the
|
||||
// base address.
|
||||
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
|
||||
return ldle16b(p_arr + (2 * p_index));
|
||||
}
|
||||
|
||||
// Initialize the static member
|
||||
std::stack<ImGuiID> ImGuiIdIssuer::idStack;
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
@@ -1,19 +1,276 @@
|
||||
#ifndef YAZE_CORE_COMMON_H
|
||||
#define YAZE_CORE_COMMON_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
unsigned int SnesToPc(unsigned int addr);
|
||||
class ExperimentFlags {
|
||||
public:
|
||||
struct Flags {
|
||||
// Bitmap manager abstraction to manage graphics bin of ROM.
|
||||
bool kUseBitmapManager = true;
|
||||
|
||||
// Log instructions to the GUI debugger.
|
||||
bool kLogInstructions = false;
|
||||
|
||||
// Flag to enable ImGui input config flags. Currently is
|
||||
// handled manually by controller class but should be
|
||||
// ported away from that eventually.
|
||||
bool kUseNewImGuiInput = false;
|
||||
|
||||
// Flag to enable the saving of all palettes to the ROM.
|
||||
bool kSaveAllPalettes = 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.
|
||||
bool kSaveWithChangeQueue = false;
|
||||
|
||||
// Attempt to run the dungeon room draw routine when opening a room.
|
||||
bool kDrawDungeonRoomGraphics = true;
|
||||
|
||||
// Use the new platform specific file dialog wrappers.
|
||||
bool kNewFileDialogWrapper = true;
|
||||
|
||||
// Platform specific loading of fonts from the system. Currently
|
||||
// only supports macOS.
|
||||
bool kLoadSystemFonts = true;
|
||||
|
||||
// Uses texture streaming from SDL for my dynamic updates.
|
||||
bool kLoadTexturesAsStreaming = true;
|
||||
|
||||
// Save dungeon map edits to the ROM.
|
||||
bool kSaveDungeonMaps = false;
|
||||
|
||||
// Log to the console.
|
||||
bool kLogToConsole = false;
|
||||
|
||||
// Overworld flags
|
||||
struct Overworld {
|
||||
// Load and render overworld sprites to the screen. Unstable.
|
||||
bool kDrawOverworldSprites = false;
|
||||
|
||||
// Save overworld map edits to the ROM.
|
||||
bool kSaveOverworldMaps = true;
|
||||
|
||||
// Save overworld entrances to the ROM.
|
||||
bool kSaveOverworldEntrances = true;
|
||||
|
||||
// Save overworld exits to the ROM.
|
||||
bool kSaveOverworldExits = true;
|
||||
|
||||
// Save overworld items to the ROM.
|
||||
bool kSaveOverworldItems = true;
|
||||
|
||||
// Save overworld properties to the ROM.
|
||||
bool kSaveOverworldProperties = true;
|
||||
} overworld;
|
||||
};
|
||||
|
||||
ExperimentFlags() = default;
|
||||
virtual ~ExperimentFlags() = default;
|
||||
auto flags() const {
|
||||
if (!flags_) {
|
||||
flags_ = std::make_shared<Flags>();
|
||||
}
|
||||
Flags *flags = flags_.get();
|
||||
return flags;
|
||||
}
|
||||
Flags *mutable_flags() {
|
||||
if (!flags_) {
|
||||
flags_ = std::make_shared<Flags>();
|
||||
}
|
||||
return flags_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Flags> flags_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class NotifyValue {
|
||||
public:
|
||||
NotifyValue() : value_(), modified_(false), temp_value_() {}
|
||||
NotifyValue(const T &value)
|
||||
: value_(value), modified_(false), temp_value_() {}
|
||||
|
||||
void set(const T &value) {
|
||||
value_ = value;
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
const T &get() {
|
||||
modified_ = false;
|
||||
return value_;
|
||||
}
|
||||
|
||||
T &mutable_get() {
|
||||
modified_ = false;
|
||||
temp_value_ = value_;
|
||||
return temp_value_;
|
||||
}
|
||||
|
||||
void apply_changes() {
|
||||
if (temp_value_ != value_) {
|
||||
value_ = temp_value_;
|
||||
modified_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void operator=(const T &value) { set(value); }
|
||||
operator T() { return get(); }
|
||||
|
||||
bool modified() const { return modified_; }
|
||||
|
||||
private:
|
||||
T value_;
|
||||
bool modified_;
|
||||
T temp_value_;
|
||||
};
|
||||
|
||||
struct TaskCheckpoint {
|
||||
int task_index = 0;
|
||||
bool complete = false;
|
||||
// You can add more internal data or state-related variables here as needed
|
||||
};
|
||||
|
||||
class TaskTimer {
|
||||
public:
|
||||
// Starts the timer
|
||||
void StartTimer() { start_time_ = std::chrono::steady_clock::now(); }
|
||||
|
||||
// Checks if the task should finish based on the given timeout in seconds
|
||||
bool ShouldFinishTask(int timeout_seconds) {
|
||||
auto current_time = std::chrono::steady_clock::now();
|
||||
auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
current_time - start_time_);
|
||||
return elapsed_time.count() >= timeout_seconds;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::steady_clock::time_point start_time_;
|
||||
};
|
||||
|
||||
template <typename TFunc>
|
||||
class TaskManager {
|
||||
public:
|
||||
TaskManager() = default;
|
||||
~TaskManager() = default;
|
||||
|
||||
TaskManager(int totalTasks, int timeoutSeconds)
|
||||
: total_tasks_(totalTasks),
|
||||
timeout_seconds_(timeoutSeconds),
|
||||
task_index_(0),
|
||||
task_complete_(false) {}
|
||||
|
||||
void ExecuteTasks(const TFunc &taskFunc) {
|
||||
if (task_complete_) {
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimer();
|
||||
|
||||
for (; task_index_ < total_tasks_; ++task_index_) {
|
||||
taskFunc(task_index_);
|
||||
|
||||
if (ShouldFinishTask()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (task_index_ == total_tasks_) {
|
||||
task_complete_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTaskComplete() const { return task_complete_; }
|
||||
void SetTimeout(int timeout) { timeout_seconds_ = timeout; }
|
||||
|
||||
private:
|
||||
int total_tasks_;
|
||||
int timeout_seconds_;
|
||||
int task_index_;
|
||||
bool task_complete_;
|
||||
std::chrono::steady_clock::time_point start_time_;
|
||||
|
||||
void StartTimer() { start_time_ = std::chrono::steady_clock::now(); }
|
||||
|
||||
bool ShouldFinishTask() {
|
||||
auto current_time = std::chrono::steady_clock::now();
|
||||
auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
current_time - start_time_);
|
||||
return elapsed_time.count() >= timeout_seconds_;
|
||||
}
|
||||
};
|
||||
|
||||
class ImGuiIdIssuer {
|
||||
private:
|
||||
static std::stack<ImGuiID> idStack;
|
||||
|
||||
public:
|
||||
// Generate and push a new ID onto the stack
|
||||
static ImGuiID GetNewID() {
|
||||
static int counter = 1; // Start from 1 to ensure uniqueness
|
||||
ImGuiID child_id = ImGui::GetID((void *)(intptr_t)counter++);
|
||||
idStack.push(child_id);
|
||||
return child_id;
|
||||
}
|
||||
|
||||
// Pop all IDs from the stack (can be called explicitly or upon program exit)
|
||||
static void Cleanup() {
|
||||
while (!idStack.empty()) {
|
||||
idStack.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
static void log(std::string message) {
|
||||
static std::ofstream fout("log.txt", std::ios::out | std::ios::app);
|
||||
fout << message << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
std::string UppercaseHexByte(uint8_t byte, bool leading = false);
|
||||
std::string UppercaseHexWord(uint16_t word);
|
||||
std::string UppercaseHexLong(uint32_t dword);
|
||||
|
||||
uint32_t SnesToPc(uint32_t addr);
|
||||
uint32_t PcToSnes(uint32_t addr);
|
||||
|
||||
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr);
|
||||
|
||||
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3);
|
||||
int HexToDec(char *input, int length);
|
||||
|
||||
bool StringReplace(std::string &str, const std::string &from,
|
||||
const std::string &to);
|
||||
|
||||
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
|
||||
uint16_t const p_val);
|
||||
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
|
||||
|
||||
uint16_t ldle16b(uint8_t const *const p_arr);
|
||||
|
||||
void stle16b(uint8_t *const p_arr, uint16_t const p_val);
|
||||
void 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);
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#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)) {
|
||||
@@ -33,6 +35,22 @@
|
||||
ImGui::TableNextColumn(); \
|
||||
ImGui::Text(w);
|
||||
|
||||
#define BEGIN_TABLE(l, n, f) if (ImGui::BeginTable(l, n, f, ImVec2(0, 0))) {
|
||||
#define SETUP_COLUMN(l) ImGui::TableSetupColumn(l);
|
||||
|
||||
#define TABLE_HEADERS() \
|
||||
ImGui::TableHeadersRow(); \
|
||||
ImGui::TableNextRow();
|
||||
|
||||
#define NEXT_COLUMN() ImGui::TableNextColumn();
|
||||
|
||||
#define END_TABLE() \
|
||||
ImGui::EndTable(); \
|
||||
}
|
||||
|
||||
#define HOVER_HINT(string) \
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip(string);
|
||||
|
||||
#define PRINT_IF_ERROR(expression) \
|
||||
{ \
|
||||
auto error = expression; \
|
||||
@@ -41,6 +59,24 @@
|
||||
} \
|
||||
}
|
||||
|
||||
#define EXIT_IF_ERROR(expression) \
|
||||
{ \
|
||||
auto error = expression; \
|
||||
if (!error.ok()) { \
|
||||
std::cout << error.ToString() << std::endl; \
|
||||
return EXIT_FAILURE; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define RETURN_VOID_IF_ERROR(expression) \
|
||||
{ \
|
||||
auto error = expression; \
|
||||
if (!error.ok()) { \
|
||||
std::cout << error.ToString() << std::endl; \
|
||||
return; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define RETURN_IF_ERROR(expression) \
|
||||
{ \
|
||||
auto error = expression; \
|
||||
@@ -60,17 +96,43 @@
|
||||
} \
|
||||
type_variable_name = std::move(*error_or_value);
|
||||
|
||||
#define ASSIGN_OR_LOG_ERROR(type_variable_name, expression) \
|
||||
ASSIGN_OR_LOG_ERROR_IMPL(APPEND_NUMBER(error_or_value, __LINE__), \
|
||||
type_variable_name, expression)
|
||||
|
||||
#define ASSIGN_OR_LOG_ERROR_IMPL(error_or_value, type_variable_name, \
|
||||
expression) \
|
||||
auto error_or_value = expression; \
|
||||
if (!error_or_value.ok()) { \
|
||||
std::cout << error_or_value.status().ToString() << std::endl; \
|
||||
} \
|
||||
type_variable_name = std::move(*error_or_value);
|
||||
|
||||
#define APPEND_NUMBER(expression, number) \
|
||||
APPEND_NUMBER_INNER(expression, number)
|
||||
|
||||
#define APPEND_NUMBER_INNER(expression, number) expression##number
|
||||
|
||||
#define TEXT_WITH_SEPARATOR(text) \
|
||||
ImGui::Text(text); \
|
||||
ImGui::Separator();
|
||||
|
||||
#define TABLE_BORDERS_RESIZABLE \
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable
|
||||
|
||||
#define CLEAR_AND_RETURN_STATUS(status) \
|
||||
if (!status.ok()) { \
|
||||
auto temp = status; \
|
||||
status = absl::OkStatus(); \
|
||||
return temp; \
|
||||
}
|
||||
|
||||
using ushort = unsigned short;
|
||||
using uint = unsigned int;
|
||||
using uchar = unsigned char;
|
||||
using Bytes = std::vector<uchar>;
|
||||
using Bytes = std::vector<uint8_t>;
|
||||
|
||||
using OWBlockset = std::vector<std::vector<ushort>>;
|
||||
using OWBlockset = std::vector<std::vector<uint16_t>>;
|
||||
struct OWMapTiles {
|
||||
OWBlockset light_world; // 64 maps
|
||||
OWBlockset dark_world; // 64 maps
|
||||
@@ -82,39 +144,8 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
// ============================================================================
|
||||
// Window Variables
|
||||
// ============================================================================
|
||||
|
||||
constexpr int kScreenWidth = 1200;
|
||||
constexpr int kScreenHeight = 800;
|
||||
|
||||
// ============================================================================
|
||||
// 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"};
|
||||
constexpr uint32_t kRedPen = 0xFF0000FF;
|
||||
constexpr float kYazeVersion = 0.07;
|
||||
|
||||
// ============================================================================
|
||||
// Magic numbers
|
||||
@@ -135,15 +166,8 @@ constexpr ushort TileNameMask = 0x03FF;
|
||||
constexpr int Uncompressed3BPPSize = 0x0600;
|
||||
constexpr int UncompressedSheetSize = 0x0800;
|
||||
|
||||
constexpr int NumberOfSheets = 223;
|
||||
constexpr int LimitOfMap32 = 8864;
|
||||
constexpr int NumberOfRooms = 296;
|
||||
|
||||
constexpr int kNumOverworldMaps = 160;
|
||||
constexpr int Map32PerScreen = 256;
|
||||
constexpr int NumberOfMap16 = 3752; // 4096
|
||||
constexpr int NumberOfMap32 = Map32PerScreen * kNumOverworldMaps;
|
||||
constexpr int NumberOfOWSprites = 352;
|
||||
constexpr int NumberOfColors = 3143;
|
||||
|
||||
// ============================================================================
|
||||
@@ -156,12 +180,7 @@ constexpr int subtype1_tiles = 0x8000; // JP = Same
|
||||
constexpr int subtype2_tiles = 0x83F0; // JP = Same
|
||||
constexpr int subtype3_tiles = 0x84F0; // JP = Same
|
||||
constexpr int gfx_animated_pointer = 0x10275; // JP 0x10624 //long pointer
|
||||
constexpr int overworldgfxGroups2 = 0x6073; // 0x60B3
|
||||
|
||||
// 2 byte pointer bank 00 pc -> 0x4320
|
||||
constexpr int gfx_1_pointer = 0x6790; // CF80 ; 004F80
|
||||
constexpr int gfx_2_pointer = 0x6795; // D05F ; 00505F
|
||||
constexpr int gfx_3_pointer = 0x679A; // D13E ; 00513E
|
||||
constexpr int hud_palettes = 0xDD660;
|
||||
constexpr int maxGfx = 0xC3FB5;
|
||||
|
||||
@@ -169,174 +188,6 @@ constexpr int kTilesheetWidth = 128;
|
||||
constexpr int kTilesheetHeight = 32;
|
||||
constexpr int kTilesheetDepth = 8;
|
||||
|
||||
// ============================================================================
|
||||
// Overworld Related Variables
|
||||
// ============================================================================
|
||||
|
||||
constexpr int compressedAllMap32PointersHigh = 0x1794D;
|
||||
constexpr int compressedAllMap32PointersLow = 0x17B2D;
|
||||
constexpr int overworldgfxGroups = 0x05D97;
|
||||
constexpr int map16Tiles = 0x78000;
|
||||
constexpr int map32TilesTL = 0x18000;
|
||||
constexpr int map32TilesTR = 0x1B400;
|
||||
constexpr int map32TilesBL = 0x20000;
|
||||
constexpr int map32TilesBR = 0x23400;
|
||||
constexpr int overworldPalGroup1 = 0xDE6C8;
|
||||
constexpr int overworldPalGroup2 = 0xDE86C;
|
||||
constexpr int overworldPalGroup3 = 0xDE604;
|
||||
constexpr int overworldMapPalette = 0x7D1C;
|
||||
constexpr int overworldSpritePalette = 0x7B41;
|
||||
constexpr int overworldMapPaletteGroup = 0x75504;
|
||||
constexpr int overworldSpritePaletteGroup = 0x75580;
|
||||
constexpr int overworldSpriteset = 0x7A41;
|
||||
constexpr int overworldSpecialGFXGroup = 0x16821;
|
||||
constexpr int overworldSpecialPALGroup = 0x16831;
|
||||
|
||||
constexpr int overworldSpritesBegining = 0x4C881;
|
||||
constexpr int overworldSpritesAgahnim = 0x4CA21;
|
||||
constexpr int overworldSpritesZelda = 0x4C901;
|
||||
|
||||
constexpr int overworldItemsPointers = 0xDC2F9;
|
||||
constexpr int overworldItemsAddress = 0xDC8B9; // 1BC2F9
|
||||
constexpr int overworldItemsBank = 0xDC8BF;
|
||||
constexpr int overworldItemsEndData = 0xDC89C; // 0DC89E
|
||||
|
||||
constexpr int mapGfx = 0x7C9C;
|
||||
constexpr int overlayPointers = 0x77664;
|
||||
constexpr int overlayPointersBank = 0x0E;
|
||||
|
||||
constexpr int overworldTilesType = 0x71459;
|
||||
constexpr int overworldMessages = 0x3F51D;
|
||||
|
||||
constexpr int overworldMusicBegining = 0x14303;
|
||||
constexpr int overworldMusicZelda = 0x14303 + 0x40;
|
||||
constexpr int overworldMusicMasterSword = 0x14303 + 0x80;
|
||||
constexpr int overworldMusicAgahim = 0x14303 + 0xC0;
|
||||
constexpr int overworldMusicDW = 0x14403;
|
||||
|
||||
constexpr int overworldEntranceAllowedTilesLeft = 0xDB8C1;
|
||||
constexpr int overworldEntranceAllowedTilesRight = 0xDB917;
|
||||
|
||||
// 0x00 = small maps, 0x20 = large maps
|
||||
constexpr int overworldMapSize = 0x12844;
|
||||
|
||||
// 0x01 = small maps, 0x03 = large maps
|
||||
constexpr int overworldMapSizeHighByte = 0x12884;
|
||||
|
||||
// relative to the WORLD + 0x200 per map
|
||||
// large map that are not == parent id = same position as their parent!
|
||||
// eg for X position small maps :
|
||||
// 0000, 0200, 0400, 0600, 0800, 0A00, 0C00, 0E00
|
||||
// all Large map would be :
|
||||
// 0000, 0000, 0400, 0400, 0800, 0800, 0C00, 0C00
|
||||
|
||||
constexpr int overworldTransitionPositionY = 0x128C4;
|
||||
constexpr int overworldTransitionPositionX = 0x12944;
|
||||
|
||||
constexpr int overworldScreenSize = 0x1788D;
|
||||
|
||||
// ============================================================================
|
||||
// Overworld Exits/Entrances Variables
|
||||
// ============================================================================
|
||||
constexpr int OWExitRoomId = 0x15D8A; // 0x15E07 Credits sequences
|
||||
// 105C2 Ending maps
|
||||
// 105E2 Sprite Group Table for Ending
|
||||
constexpr int OWExitMapId = 0x15E28;
|
||||
constexpr int OWExitVram = 0x15E77;
|
||||
constexpr int OWExitYScroll = 0x15F15;
|
||||
constexpr int OWExitXScroll = 0x15FB3;
|
||||
constexpr int OWExitYPlayer = 0x16051;
|
||||
constexpr int OWExitXPlayer = 0x160EF;
|
||||
constexpr int OWExitYCamera = 0x1618D;
|
||||
constexpr int OWExitXCamera = 0x1622B;
|
||||
constexpr int OWExitDoorPosition = 0x15724;
|
||||
constexpr int OWExitUnk1 = 0x162C9;
|
||||
constexpr int OWExitUnk2 = 0x16318;
|
||||
constexpr int OWExitDoorType1 = 0x16367;
|
||||
constexpr int OWExitDoorType2 = 0x16405;
|
||||
constexpr int OWEntranceMap = 0xDB96F;
|
||||
constexpr int OWEntrancePos = 0xDBA71;
|
||||
constexpr int OWEntranceEntranceId = 0xDBB73;
|
||||
constexpr int OWHolePos = 0xDB800; //(0x13 entries, 2 bytes each) modified(less
|
||||
// 0x400) map16 coordinates for each hole
|
||||
constexpr int OWHoleArea =
|
||||
0xDB826; //(0x13 entries, 2 bytes each) corresponding
|
||||
// area numbers for each hole
|
||||
constexpr int OWHoleEntrance =
|
||||
0xDB84C; //(0x13 entries, 1 byte each) corresponding entrance numbers
|
||||
|
||||
constexpr int OWExitMapIdWhirlpool = 0x16AE5; // JP = ;016849
|
||||
constexpr int OWExitVramWhirlpool = 0x16B07; // JP = ;01686B
|
||||
constexpr int OWExitYScrollWhirlpool = 0x16B29; // JP = ;01688D
|
||||
constexpr int OWExitXScrollWhirlpool = 0x16B4B; // JP = ;016DE7
|
||||
constexpr int OWExitYPlayerWhirlpool = 0x16B6D; // JP = ;016E09
|
||||
constexpr int OWExitXPlayerWhirlpool = 0x16B8F; // JP = ;016E2B
|
||||
constexpr int OWExitYCameraWhirlpool = 0x16BB1; // JP = ;016E4D
|
||||
constexpr int OWExitXCameraWhirlpool = 0x16BD3; // JP = ;016E6F
|
||||
constexpr int OWExitUnk1Whirlpool = 0x16BF5; // JP = ;016E91
|
||||
constexpr int OWExitUnk2Whirlpool = 0x16C17; // JP = ;016EB3
|
||||
constexpr int OWWhirlpoolPosition = 0x16CF8; // JP = ;016F94
|
||||
|
||||
// ============================================================================
|
||||
// Dungeon Related Variables
|
||||
// ============================================================================
|
||||
|
||||
// That could be turned into a pointer :
|
||||
constexpr int dungeons_palettes_groups = 0x75460; // JP 0x67DD0
|
||||
constexpr int dungeons_main_bg_palette_pointers = 0xDEC4B; // JP Same
|
||||
constexpr int dungeons_palettes =
|
||||
0xDD734; // JP Same (where all dungeons palettes are)
|
||||
|
||||
// That could be turned into a pointer :
|
||||
constexpr int room_items_pointers = 0xDB69; // JP 0xDB67
|
||||
|
||||
constexpr int rooms_sprite_pointer = 0x4C298; // JP Same //2byte bank 09D62E
|
||||
constexpr int room_header_pointer = 0xB5DD; // LONG
|
||||
constexpr int room_header_pointers_bank = 0xB5E7; // JP Same
|
||||
|
||||
constexpr int gfx_groups_pointer = 0x6237;
|
||||
constexpr int room_object_layout_pointer = 0x882D;
|
||||
|
||||
constexpr int room_object_pointer = 0x874C; // Long pointer
|
||||
|
||||
constexpr int chests_length_pointer = 0xEBF6;
|
||||
constexpr int chests_data_pointer1 = 0xEBFB;
|
||||
// constexpr int chests_data_pointer2 = 0xEC0A; //Disabled for now could be used
|
||||
// for expansion constexpr int chests_data_pointer3 = 0xEC10; //Disabled for now
|
||||
// could be used for expansion
|
||||
|
||||
constexpr int blocks_length = 0x8896; // word value
|
||||
constexpr int blocks_pointer1 = 0x15AFA;
|
||||
constexpr int blocks_pointer2 = 0x15B01;
|
||||
constexpr int blocks_pointer3 = 0x15B08;
|
||||
constexpr int blocks_pointer4 = 0x15B0F;
|
||||
|
||||
constexpr int torch_data = 0x2736A; // JP 0x2704A
|
||||
constexpr int torches_length_pointer = 0x88C1;
|
||||
|
||||
constexpr int kSpriteBlocksetPointer = 0x5B57;
|
||||
constexpr int sprites_data =
|
||||
0x4D8B0; // It use the unused pointers to have more space //Save purpose
|
||||
constexpr int sprites_data_empty_room = 0x4D8AE;
|
||||
constexpr int sprites_end_data = 0x4EC9E;
|
||||
|
||||
constexpr int pit_pointer = 0x394AB;
|
||||
constexpr int pit_count = 0x394A6;
|
||||
|
||||
constexpr int doorPointers = 0xF83C0;
|
||||
|
||||
// doors
|
||||
constexpr int door_gfx_up = 0x4D9E;
|
||||
constexpr int door_gfx_down = 0x4E06;
|
||||
constexpr int door_gfx_cavexit_down = 0x4E06;
|
||||
constexpr int door_gfx_left = 0x4E66;
|
||||
constexpr int door_gfx_right = 0x4EC6;
|
||||
|
||||
constexpr int door_pos_up = 0x197E;
|
||||
constexpr int door_pos_down = 0x1996;
|
||||
constexpr int door_pos_left = 0x19AE;
|
||||
constexpr int door_pos_right = 0x19C6;
|
||||
|
||||
// TEXT EDITOR RELATED CONSTANTS
|
||||
constexpr int gfx_font = 0x70000; // 2bpp format
|
||||
constexpr int text_data = 0xE0000;
|
||||
@@ -344,96 +195,7 @@ constexpr int text_data2 = 0x75F40;
|
||||
constexpr int pointers_dictionaries = 0x74703;
|
||||
constexpr int characters_width = 0x74ADF;
|
||||
|
||||
// ============================================================================
|
||||
// Dungeon Entrances Related Variables
|
||||
// ============================================================================
|
||||
|
||||
// 0x14577 word value for each room
|
||||
constexpr int entrance_room = 0x14813;
|
||||
|
||||
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
|
||||
constexpr int entrance_scrolledge = 0x1491D; // 0x14681
|
||||
constexpr int entrance_yscroll = 0x14D45; // 0x14AA9 2 bytes each room
|
||||
constexpr int entrance_xscroll = 0x14E4F; // 0x14BB3 2 bytes
|
||||
constexpr int entrance_yposition = 0x14F59; // 0x14CBD 2bytes
|
||||
constexpr int entrance_xposition = 0x15063; // 0x14DC7 2bytes
|
||||
constexpr int entrance_camerayposition = 0x1516D; // 0x14ED1 2bytes
|
||||
constexpr int entrance_cameraxposition = 0x15277; // 0x14FDB 2bytes
|
||||
|
||||
constexpr int entrance_gfx_group = 0x5D97;
|
||||
constexpr int entrance_blockset = 0x15381; // 0x150E5 1byte
|
||||
constexpr int entrance_floor = 0x15406; // 0x1516A 1byte
|
||||
constexpr int entrance_dungeon = 0x1548B; // 0x151EF 1byte (dungeon id)
|
||||
constexpr int entrance_door = 0x15510; // 0x15274 1byte
|
||||
|
||||
// 1 byte, ---b ---a b = bg2, a = need to check
|
||||
constexpr int entrance_ladderbg = 0x15595; // 0x152F9
|
||||
constexpr int entrance_scrolling = 0x1561A; // 0x1537E 1byte --h- --v-
|
||||
constexpr int entrance_scrollquadrant = 0x1569F; // 0x15403 1byte
|
||||
constexpr int entrance_exit = 0x15724; // 0x15488 2byte word
|
||||
constexpr int entrance_music = 0x1582E; // 0x15592
|
||||
|
||||
// word value for each room
|
||||
constexpr int startingentrance_room = 0x15B6E; // 0x158D2
|
||||
|
||||
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
|
||||
constexpr int startingentrance_scrolledge = 0x15B7C; // 0x158E0
|
||||
constexpr int startingentrance_yscroll = 0x15BB4; // 0x14AA9 //2bytes each room
|
||||
constexpr int startingentrance_xscroll = 0x15BC2; // 0x14BB3 //2bytes
|
||||
constexpr int startingentrance_yposition = 0x15BD0; // 0x14CBD 2bytes
|
||||
constexpr int startingentrance_xposition = 0x15BDE; // 0x14DC7 2bytes
|
||||
constexpr int startingentrance_camerayposition = 0x15BEC; // 0x14ED1 2bytes
|
||||
constexpr int startingentrance_cameraxposition = 0x15BFA; // 0x14FDB 2bytes
|
||||
|
||||
constexpr int startingentrance_blockset = 0x15C08; // 0x150E5 1byte
|
||||
constexpr int startingentrance_floor = 0x15C0F; // 0x1516A 1byte
|
||||
constexpr int startingentrance_dungeon = 0x15C16; // 0x151EF 1byte (dungeon id)
|
||||
|
||||
constexpr int startingentrance_door = 0x15C2B; // 0x15274 1byte
|
||||
|
||||
// 1 byte, ---b ---a b = bg2, a = need to check
|
||||
constexpr int startingentrance_ladderbg = 0x15C1D; // 0x152F9
|
||||
// 1byte --h- --v-
|
||||
constexpr int startingentrance_scrolling = 0x15C24; // 0x1537E
|
||||
constexpr int startingentrance_scrollquadrant = 0x15C2B; // 0x15403 1byte
|
||||
constexpr int startingentrance_exit = 0x15C32; // 0x15488 //2byte word
|
||||
constexpr int startingentrance_music = 0x15C4E; // 0x15592
|
||||
constexpr int startingentrance_entrance = 0x15C40;
|
||||
|
||||
constexpr int items_data_start = 0xDDE9; // save purpose
|
||||
constexpr int items_data_end = 0xE6B2; // save purpose
|
||||
constexpr int initial_equipement = 0x271A6;
|
||||
constexpr int messages_id_dungeon = 0x3F61D;
|
||||
|
||||
// item id you get instead if you already have that item
|
||||
constexpr int chests_backupitems = 0x3B528;
|
||||
constexpr int chests_yoffset = 0x4836C;
|
||||
constexpr int chests_xoffset = 0x4836C + (76 * 1);
|
||||
constexpr int chests_itemsgfx = 0x4836C + (76 * 2);
|
||||
constexpr int chests_itemswide = 0x4836C + (76 * 3);
|
||||
constexpr int chests_itemsproperties = 0x4836C + (76 * 4);
|
||||
constexpr int chests_sramaddress = 0x4836C + (76 * 5);
|
||||
constexpr int chests_sramvalue = 0x4836C + (76 * 7);
|
||||
constexpr int chests_msgid = 0x442DD;
|
||||
|
||||
constexpr int dungeons_startrooms = 0x7939;
|
||||
constexpr int dungeons_endrooms = 0x792D;
|
||||
constexpr int dungeons_bossrooms = 0x10954; // short value
|
||||
|
||||
// Bed Related Values (Starting location)
|
||||
constexpr int bedPositionX = 0x039A37; // short value
|
||||
constexpr int bedPositionY = 0x039A32; // short value
|
||||
|
||||
// short value (on 2 different bytes)
|
||||
constexpr int bedPositionResetXLow = 0x02DE53;
|
||||
constexpr int bedPositionResetXHigh = 0x02DE58;
|
||||
|
||||
// short value (on 2 different bytes)
|
||||
constexpr int bedPositionResetYLow = 0x02DE5D;
|
||||
constexpr int bedPositionResetYHigh = 0x02DE62;
|
||||
|
||||
constexpr int bedSheetPositionX = 0x0480BD; // short value
|
||||
constexpr int bedSheetPositionY = 0x0480B8; // short value
|
||||
|
||||
// ============================================================================
|
||||
// Gravestones related variables
|
||||
@@ -482,30 +244,6 @@ constexpr int customAreaSpecificBGPalette =
|
||||
constexpr int customAreaSpecificBGASM = 0x140150;
|
||||
constexpr int customAreaSpecificBGEnabled =
|
||||
0x140140; // 1 byte, not 0 if enabled
|
||||
constexpr int overworldCustomMosaicArray = 0x1301F0;
|
||||
// ============================================================================
|
||||
// Dungeon Map Related Variables
|
||||
// ============================================================================
|
||||
|
||||
constexpr int dungeonMap_rooms_ptr = 0x57605; // 14 pointers of map data
|
||||
constexpr int dungeonMap_floors = 0x575D9; // 14 words values
|
||||
|
||||
constexpr int dungeonMap_gfx_ptr = 0x57BE4; // 14 pointers of gfx data
|
||||
|
||||
// data start for floors/gfx MUST skip 575D9 to 57621 (pointers)
|
||||
constexpr int dungeonMap_datastart = 0x57039;
|
||||
|
||||
// IF Byte = 0xB9 dungeon maps are not expanded
|
||||
constexpr int dungeonMap_expCheck = 0x56652;
|
||||
constexpr int dungeonMap_tile16 = 0x57009;
|
||||
constexpr int dungeonMap_tile16Exp = 0x109010;
|
||||
|
||||
// 14 words values 0x000F = no boss
|
||||
constexpr int dungeonMap_bossrooms = 0x56807;
|
||||
constexpr int triforceVertices = 0x04FFD2; // group of 3, X, Y ,Z
|
||||
constexpr int TriforceFaces = 0x04FFE4; // group of 5
|
||||
|
||||
constexpr int crystalVertices = 0x04FF98;
|
||||
|
||||
// ============================================================================
|
||||
// Names
|
||||
@@ -602,455 +340,6 @@ static const absl::string_view SecretItemNames[] = {
|
||||
|
||||
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
|
||||
|
||||
static const absl::string_view Type1RoomObjectNames[] = {
|
||||
"Ceiling ↔",
|
||||
"Wall (top, north) ↔",
|
||||
"Wall (top, south) ↔",
|
||||
"Wall (bottom, north) ↔",
|
||||
"Wall (bottom, south) ↔",
|
||||
"Wall columns (north) ↔",
|
||||
"Wall columns (south) ↔",
|
||||
"Deep wall (north) ↔",
|
||||
"Deep wall (south) ↔",
|
||||
"Diagonal wall A ◤ (top) ↔",
|
||||
"Diagonal wall A ◣ (top) ↔",
|
||||
"Diagonal wall A ◥ (top) ↔",
|
||||
"Diagonal wall A ◢ (top) ↔",
|
||||
"Diagonal wall B ◤ (top) ↔",
|
||||
"Diagonal wall B ◣ (top) ↔",
|
||||
"Diagonal wall B ◥ (top) ↔",
|
||||
"Diagonal wall B ◢ (top) ↔",
|
||||
"Diagonal wall C ◤ (top) ↔",
|
||||
"Diagonal wall C ◣ (top) ↔",
|
||||
"Diagonal wall C ◥ (top) ↔",
|
||||
"Diagonal wall C ◢ (top) ↔",
|
||||
"Diagonal wall A ◤ (bottom) ↔",
|
||||
"Diagonal wall A ◣ (bottom) ↔",
|
||||
"Diagonal wall A ◥ (bottom) ↔",
|
||||
"Diagonal wall A ◢ (bottom) ↔",
|
||||
"Diagonal wall B ◤ (bottom) ↔",
|
||||
"Diagonal wall B ◣ (bottom) ↔",
|
||||
"Diagonal wall B ◥ (bottom) ↔",
|
||||
"Diagonal wall B ◢ (bottom) ↔",
|
||||
"Diagonal wall C ◤ (bottom) ↔",
|
||||
"Diagonal wall C ◣ (bottom) ↔",
|
||||
"Diagonal wall C ◥ (bottom) ↔",
|
||||
"Diagonal wall C ◢ (bottom) ↔",
|
||||
"Platform stairs ↔",
|
||||
"Rail ↔",
|
||||
"Pit edge ┏━┓ A (north) ↔",
|
||||
"Pit edge ┏━┓ B (north) ↔",
|
||||
"Pit edge ┏━┓ C (north) ↔",
|
||||
"Pit edge ┏━┓ D (north) ↔",
|
||||
"Pit edge ┏━┓ E (north) ↔",
|
||||
"Pit edge ┗━┛ (south) ↔",
|
||||
"Pit edge ━━━ (south) ↔",
|
||||
"Pit edge ━━━ (north) ↔",
|
||||
"Pit edge ━━┛ (south) ↔",
|
||||
"Pit edge ┗━━ (south) ↔",
|
||||
"Pit edge ━━┓ (north) ↔",
|
||||
"Pit edge ┏━━ (north) ↔",
|
||||
"Rail wall (north) ↔",
|
||||
"Rail wall (south) ↔",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Carpet ↔",
|
||||
"Carpet trim ↔",
|
||||
"Weird door", // TODO: WEIRD DOOR OBJECT NEEDS INVESTIGATION
|
||||
"Drapes (north) ↔",
|
||||
"Drapes (west, odd) ↔",
|
||||
"Statues ↔",
|
||||
"Columns ↔",
|
||||
"Wall decors (north) ↔",
|
||||
"Wall decors (south) ↔",
|
||||
"Chairs in pairs ↔",
|
||||
"Tall torches ↔",
|
||||
"Supports (north) ↔",
|
||||
"Water edge ┏━┓ (concave) ↔",
|
||||
"Water edge ┗━┛ (concave) ↔",
|
||||
"Water edge ┏━┓ (convex) ↔",
|
||||
"Water edge ┗━┛ (convex) ↔",
|
||||
"Water edge ┏━┛ (concave) ↔",
|
||||
"Water edge ┗━┓ (concave) ↔",
|
||||
"Water edge ┗━┓ (convex) ↔",
|
||||
"Water edge ┏━┛ (convex) ↔",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Supports (south) ↔",
|
||||
"Bar ↔",
|
||||
"Shelf A ↔",
|
||||
"Shelf B ↔",
|
||||
"Shelf C ↔",
|
||||
"Somaria path ↔",
|
||||
"Cannon hole A (north) ↔",
|
||||
"Cannon hole A (south) ↔",
|
||||
"Pipe path ↔",
|
||||
"Nothing",
|
||||
"Wall torches (north) ↔",
|
||||
"Wall torches (south) ↔",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Cannon hole B (north) ↔",
|
||||
"Cannon hole B (south) ↔",
|
||||
"Thick rail ↔",
|
||||
"Blocks ↔",
|
||||
"Long rail ↔",
|
||||
"Ceiling ↕",
|
||||
"Wall (top, west) ↕",
|
||||
"Wall (top, east) ↕",
|
||||
"Wall (bottom, west) ↕",
|
||||
"Wall (bottom, east) ↕",
|
||||
"Wall columns (west) ↕",
|
||||
"Wall columns (east) ↕",
|
||||
"Deep wall (west) ↕",
|
||||
"Deep wall (east) ↕",
|
||||
"Rail ↕",
|
||||
"Pit edge (west) ↕",
|
||||
"Pit edge (east) ↕",
|
||||
"Rail wall (west) ↕",
|
||||
"Rail wall (east) ↕",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Carpet ↕",
|
||||
"Carpet trim ↕",
|
||||
"Nothing",
|
||||
"Drapes (west) ↕",
|
||||
"Drapes (east) ↕",
|
||||
"Columns ↕",
|
||||
"Wall decors (west) ↕",
|
||||
"Wall decors (east) ↕",
|
||||
"Supports (west) ↕",
|
||||
"Water edge (west) ↕",
|
||||
"Water edge (east) ↕",
|
||||
"Supports (east) ↕",
|
||||
"Somaria path ↕",
|
||||
"Pipe path ↕",
|
||||
"Nothing",
|
||||
"Wall torches (west) ↕",
|
||||
"Wall torches (east) ↕",
|
||||
"Wall decors tight A (west) ↕",
|
||||
"Wall decors tight A (east) ↕",
|
||||
"Wall decors tight B (west) ↕",
|
||||
"Wall decors tight B (east) ↕",
|
||||
"Cannon hole (west) ↕",
|
||||
"Cannon hole (east) ↕",
|
||||
"Tall torches ↕",
|
||||
"Thick rail ↕",
|
||||
"Blocks ↕",
|
||||
"Long rail ↕",
|
||||
"Jump ledge (west) ↕",
|
||||
"Jump ledge (east) ↕",
|
||||
"Rug trim (west) ↕",
|
||||
"Rug trim (east) ↕",
|
||||
"Bar ↕",
|
||||
"Wall flair (west) ↕",
|
||||
"Wall flair (east) ↕",
|
||||
"Blue pegs ↕",
|
||||
"Orange pegs ↕",
|
||||
"Invisible floor ↕",
|
||||
"Fake pots ↕",
|
||||
"Hammer pegs ↕",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Diagonal ceiling A ◤",
|
||||
"Diagonal ceiling A ◣",
|
||||
"Diagonal ceiling A ◥",
|
||||
"Diagonal ceiling A ◢",
|
||||
"Pit ⇲",
|
||||
"Diagonal layer 2 mask A ◤",
|
||||
"Diagonal layer 2 mask A ◣",
|
||||
"Diagonal layer 2 mask A ◥",
|
||||
"Diagonal layer 2 mask A ◢",
|
||||
"Diagonal layer 2 mask B ◤", // TODO: VERIFY
|
||||
"Diagonal layer 2 mask B ◣", // TODO: VERIFY
|
||||
"Diagonal layer 2 mask B ◥", // TODO: VERIFY
|
||||
"Diagonal layer 2 mask B ◢", // TODO: VERIFY
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Jump ledge (north) ↔",
|
||||
"Jump ledge (south) ↔",
|
||||
"Rug ↔",
|
||||
"Rug trim (north) ↔",
|
||||
"Rug trim (south) ↔",
|
||||
"Archery game curtains ↔",
|
||||
"Wall flair (north) ↔",
|
||||
"Wall flair (south) ↔",
|
||||
"Blue pegs ↔",
|
||||
"Orange pegs ↔",
|
||||
"Invisible floor ↔",
|
||||
"Fake pressure plates ↔",
|
||||
"Fake pots ↔",
|
||||
"Hammer pegs ↔",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Ceiling (large) ⇲",
|
||||
"Chest platform (tall) ⇲",
|
||||
"Layer 2 pit mask (large) ⇲",
|
||||
"Layer 2 pit mask (medium) ⇲",
|
||||
"Floor 1 ⇲",
|
||||
"Floor 3 ⇲",
|
||||
"Layer 2 mask (large) ⇲",
|
||||
"Floor 4 ⇲",
|
||||
"Water floor ⇲ ",
|
||||
"Flood water (medium) ⇲ ",
|
||||
"Conveyor floor ⇲ ",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Moving wall (west) ⇲",
|
||||
"Moving wall (east) ⇲",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Icy floor A ⇲",
|
||||
"Icy floor B ⇲",
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Moving wall flag", // TODO: WTF IS THIS?
|
||||
"Layer 2 mask (medium) ⇲",
|
||||
"Flood water (large) ⇲",
|
||||
"Layer 2 swim mask ⇲",
|
||||
"Flood water B (large) ⇲",
|
||||
"Floor 2 ⇲",
|
||||
"Chest platform (short) ⇲",
|
||||
"Table / rock ⇲",
|
||||
"Spike blocks ⇲",
|
||||
"Spiked floor ⇲",
|
||||
"Floor 7 ⇲",
|
||||
"Tiled floor ⇲",
|
||||
"Rupee floor ⇲",
|
||||
"Conveyor upwards ⇲",
|
||||
"Conveyor downwards ⇲",
|
||||
"Conveyor leftwards ⇲",
|
||||
"Conveyor rightwards ⇲",
|
||||
"Heavy current water ⇲",
|
||||
"Floor 10 ⇲",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
"Nothing",
|
||||
};
|
||||
|
||||
static const absl::string_view Type2RoomObjectNames[] = {
|
||||
"Corner (top, concave) ▛",
|
||||
"Corner (top, concave) ▙",
|
||||
"Corner (top, concave) ▜",
|
||||
"Corner (top, concave) ▟",
|
||||
"Corner (top, convex) ▟",
|
||||
"Corner (top, convex) ▜",
|
||||
"Corner (top, convex) ▙",
|
||||
"Corner (top, convex) ▛",
|
||||
"Corner (bottom, concave) ▛",
|
||||
"Corner (bottom, concave) ▙",
|
||||
"Corner (bottom, concave) ▜",
|
||||
"Corner (bottom, concave) ▟",
|
||||
"Corner (bottom, convex) ▟",
|
||||
"Corner (bottom, convex) ▜",
|
||||
"Corner (bottom, convex) ▙",
|
||||
"Corner (bottom, convex) ▛",
|
||||
"Kinked corner north (bottom) ▜",
|
||||
"Kinked corner south (bottom) ▟",
|
||||
"Kinked corner north (bottom) ▛",
|
||||
"Kinked corner south (bottom) ▙",
|
||||
"Kinked corner west (bottom) ▙",
|
||||
"Kinked corner west (bottom) ▛",
|
||||
"Kinked corner east (bottom) ▟",
|
||||
"Kinked corner east (bottom) ▜",
|
||||
"Deep corner (concave) ▛",
|
||||
"Deep corner (concave) ▙",
|
||||
"Deep corner (concave) ▜",
|
||||
"Deep corner (concave) ▟",
|
||||
"Large brazier",
|
||||
"Statue",
|
||||
"Star tile (disabled)",
|
||||
"Star tile (enabled)",
|
||||
"Small torch (lit)",
|
||||
"Barrel",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Table",
|
||||
"Fairy statue",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Chair",
|
||||
"Bed",
|
||||
"Fireplace",
|
||||
"Mario portrait",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Interroom stairs (up)",
|
||||
"Interroom stairs (down)",
|
||||
"Interroom stairs B (down)",
|
||||
"Intraroom stairs north B", // TODO: VERIFY LAYER HANDLING
|
||||
"Intraroom stairs north (separate layers)",
|
||||
"Intraroom stairs north (merged layers)",
|
||||
"Intraroom stairs north (swim layer)",
|
||||
"Block",
|
||||
"Water ladder (north)",
|
||||
"Water ladder (south)", // TODO: NEEDS IN GAME VERIFICATION
|
||||
"Dam floodgate",
|
||||
"Interroom spiral stairs up (top)",
|
||||
"Interroom spiral stairs down (top)",
|
||||
"Interroom spiral stairs up (bottom)",
|
||||
"Interroom spiral stairs down (bottom)",
|
||||
"Sanctuary wall (north)",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Pew",
|
||||
"Magic bat altar",
|
||||
};
|
||||
|
||||
static const absl::string_view Type3RoomObjectNames[] = {
|
||||
"Waterfall face (empty)",
|
||||
"Waterfall face (short)",
|
||||
"Waterfall face (long)",
|
||||
"Somaria path endpoint",
|
||||
"Somaria path intersection ╋",
|
||||
"Somaria path corner ┏",
|
||||
"Somaria path corner ┗",
|
||||
"Somaria path corner ┓",
|
||||
"Somaria path corner ┛",
|
||||
"Somaria path intersection ┳",
|
||||
"Somaria path intersection ┻",
|
||||
"Somaria path intersection ┣",
|
||||
"Somaria path intersection ┫",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Somaria path 2-way endpoint",
|
||||
"Somaria path crossover",
|
||||
"Babasu hole (north)",
|
||||
"Babasu hole (south)",
|
||||
"9 blue rupees",
|
||||
"Telepathy tile",
|
||||
"Warp door", // TODO: NEEDS IN GAME VERIFICATION THAT THIS IS USELESS
|
||||
"Kholdstare's shell",
|
||||
"Hammer peg",
|
||||
"Prison cell",
|
||||
"Big key lock",
|
||||
"Chest",
|
||||
"Chest (open)",
|
||||
"Intraroom stairs south", // TODO: VERIFY LAYER HANDLING
|
||||
"Intraroom stairs south (separate layers)",
|
||||
"Intraroom stairs south (merged layers)",
|
||||
"Interroom straight stairs up (north, top)",
|
||||
"Interroom straight stairs down (north, top)",
|
||||
"Interroom straight stairs up (south, top)",
|
||||
"Interroom straight stairs down (south, top)",
|
||||
"Deep corner (convex) ▟",
|
||||
"Deep corner (convex) ▜",
|
||||
"Deep corner (convex) ▙",
|
||||
"Deep corner (convex) ▛",
|
||||
"Interroom straight stairs up (north, bottom)",
|
||||
"Interroom straight stairs down (north, bottom)",
|
||||
"Interroom straight stairs up (south, bottom)",
|
||||
"Interroom straight stairs down (south, bottom)",
|
||||
"Lamp cones",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Liftable large block",
|
||||
"Agahnim's altar",
|
||||
"Agahnim's boss room",
|
||||
"Pot",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Big chest",
|
||||
"Big chest (open)",
|
||||
"Intraroom stairs south (swim layer)",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Pipe end (south)",
|
||||
"Pipe end (north)",
|
||||
"Pipe end (east)",
|
||||
"Pipe end (west)",
|
||||
"Pipe corner ▛",
|
||||
"Pipe corner ▙",
|
||||
"Pipe corner ▜",
|
||||
"Pipe corner ▟",
|
||||
"Pipe-rock intersection ⯊",
|
||||
"Pipe-rock intersection ⯋",
|
||||
"Pipe-rock intersection ◖",
|
||||
"Pipe-rock intersection ◗",
|
||||
"Pipe crossover",
|
||||
"Bombable floor",
|
||||
"Fake bombable floor",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Warp tile",
|
||||
"Tool rack",
|
||||
"Furnace",
|
||||
"Tub (wide)",
|
||||
"Anvil",
|
||||
"Warp tile (disabled)",
|
||||
"Pressure plate",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Blue peg",
|
||||
"Orange peg",
|
||||
"Fortune teller room",
|
||||
"Unknown", // TODO: NEEDS IN GAME CHECKING
|
||||
"Bar corner ▛",
|
||||
"Bar corner ▙",
|
||||
"Bar corner ▜",
|
||||
"Bar corner ▟",
|
||||
"Decorative bowl",
|
||||
"Tub (tall)",
|
||||
"Bookcase",
|
||||
"Range",
|
||||
"Suitcase",
|
||||
"Bar bottles",
|
||||
"Arrow game hole (west)",
|
||||
"Arrow game hole (east)",
|
||||
"Vitreous goo gfx",
|
||||
"Fake pressure plate",
|
||||
"Medusa head",
|
||||
"4-way shooter block",
|
||||
"Pit",
|
||||
"Wall crack (north)",
|
||||
"Wall crack (south)",
|
||||
"Wall crack (west)",
|
||||
"Wall crack (east)",
|
||||
"Large decor",
|
||||
"Water grate (north)",
|
||||
"Water grate (south)",
|
||||
"Water grate (west)",
|
||||
"Water grate (east)",
|
||||
"Window sunlight",
|
||||
"Floor sunlight",
|
||||
"Trinexx's shell",
|
||||
"Layer 2 mask (full)",
|
||||
"Boss entrance",
|
||||
"Minigame chest",
|
||||
"Ganon door",
|
||||
"Triforce wall ornament",
|
||||
"Triforce floor tiles",
|
||||
"Freezor hole",
|
||||
"Pile of bones",
|
||||
"Vitreous goo damage",
|
||||
"Arrow tile ↑",
|
||||
"Arrow tile ↓",
|
||||
"Arrow tile →",
|
||||
"Nothing",
|
||||
};
|
||||
|
||||
static const absl::string_view TileTypeNames[] = {
|
||||
"$00 Nothing (standard floor)",
|
||||
"$01 Collision",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_mixer.h>
|
||||
#include <imgui/backends/imgui_impl_sdl.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
||||
#include <imgui/backends/imgui_impl_sdl2.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/core/platform/font_loader.h"
|
||||
#include "app/editor/master_editor.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/style.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/style.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -28,12 +29,46 @@ void InitializeKeymap() {
|
||||
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
|
||||
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
|
||||
io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN);
|
||||
io.KeyMap[ImGuiKey_LeftArrow] = SDL_GetScancodeFromKey(SDLK_LEFT);
|
||||
io.KeyMap[ImGuiKey_RightArrow] = SDL_GetScancodeFromKey(SDLK_RIGHT);
|
||||
io.KeyMap[ImGuiKey_Delete] = SDL_GetScancodeFromKey(SDLK_DELETE);
|
||||
io.KeyMap[ImGuiKey_Escape] = SDL_GetScancodeFromKey(SDLK_ESCAPE);
|
||||
io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
|
||||
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
|
||||
io.KeyMap[ImGuiKey_PageUp] = SDL_GetScancodeFromKey(SDLK_PAGEUP);
|
||||
io.KeyMap[ImGuiKey_PageDown] = SDL_GetScancodeFromKey(SDLK_PAGEDOWN);
|
||||
io.KeyMap[ImGuiKey_Home] = SDL_GetScancodeFromKey(SDLK_HOME);
|
||||
io.KeyMap[ImGuiKey_Space] = SDL_GetScancodeFromKey(SDLK_SPACE);
|
||||
io.KeyMap[ImGuiKey_1] = SDL_GetScancodeFromKey(SDLK_1);
|
||||
io.KeyMap[ImGuiKey_2] = SDL_GetScancodeFromKey(SDLK_2);
|
||||
io.KeyMap[ImGuiKey_3] = SDL_GetScancodeFromKey(SDLK_3);
|
||||
io.KeyMap[ImGuiKey_4] = SDL_GetScancodeFromKey(SDLK_4);
|
||||
io.KeyMap[ImGuiKey_5] = SDL_GetScancodeFromKey(SDLK_5);
|
||||
io.KeyMap[ImGuiKey_6] = SDL_GetScancodeFromKey(SDLK_6);
|
||||
io.KeyMap[ImGuiKey_7] = SDL_GetScancodeFromKey(SDLK_7);
|
||||
io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8);
|
||||
io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9);
|
||||
io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0);
|
||||
}
|
||||
|
||||
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
|
||||
SDL_SetClipboardText(text);
|
||||
}
|
||||
|
||||
const char *ImGui_ImplSDL2_GetClipboardText(void *user_data) {
|
||||
return SDL_GetClipboardText();
|
||||
}
|
||||
|
||||
void InitializeClipboard() {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
|
||||
io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
|
||||
io.ClipboardUserData = nullptr;
|
||||
}
|
||||
|
||||
void HandleKeyDown(SDL_Event &event) {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_UP:
|
||||
case SDLK_DOWN:
|
||||
@@ -76,15 +111,14 @@ void HandleMouseMovement(int &wheel) {
|
||||
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
||||
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
|
||||
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
|
||||
io.MouseDown[2] = buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE);
|
||||
io.MouseWheel = static_cast<float>(wheel);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Controller::isActive() const { return active_; }
|
||||
|
||||
absl::Status Controller::onEntry() {
|
||||
RETURN_IF_ERROR(CreateWindow())
|
||||
absl::Status Controller::OnEntry() {
|
||||
RETURN_IF_ERROR(CreateSDL_Window())
|
||||
RETURN_IF_ERROR(CreateRenderer())
|
||||
RETURN_IF_ERROR(CreateGuiContext())
|
||||
InitializeKeymap();
|
||||
@@ -93,7 +127,7 @@ absl::Status Controller::onEntry() {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void Controller::onInput() {
|
||||
void Controller::OnInput() {
|
||||
int wheel = 0;
|
||||
SDL_Event event;
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
@@ -124,7 +158,6 @@ void Controller::onInput() {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -133,33 +166,40 @@ void Controller::onInput() {
|
||||
HandleMouseMovement(wheel);
|
||||
}
|
||||
|
||||
void Controller::onLoad() { master_editor_.UpdateScreen(); }
|
||||
void Controller::OnLoad() { PRINT_IF_ERROR(master_editor_.Update()); }
|
||||
|
||||
void Controller::doRender() const {
|
||||
SDL_RenderClear(renderer_.get());
|
||||
void Controller::DoRender() const {
|
||||
ImGui::Render();
|
||||
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_RenderClear(renderer_.get());
|
||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_RenderPresent(renderer_.get());
|
||||
}
|
||||
|
||||
void Controller::onExit() const {
|
||||
ImGui_ImplSDLRenderer_Shutdown();
|
||||
void Controller::OnExit() {
|
||||
master_editor_.Shutdown();
|
||||
Mix_CloseAudio();
|
||||
ImGui_ImplSDLRenderer2_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
absl::Status Controller::CreateWindow() {
|
||||
absl::Status Controller::CreateSDL_Window() {
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
|
||||
} else {
|
||||
SDL_DisplayMode displayMode;
|
||||
SDL_GetCurrentDisplayMode(0, &displayMode);
|
||||
int screenWidth = displayMode.w * 0.8;
|
||||
int screenHeight = displayMode.h * 0.8;
|
||||
|
||||
window_ = std::unique_ptr<SDL_Window, sdl_deleter>(
|
||||
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // initial y position
|
||||
kScreenWidth, // width, in pixels
|
||||
kScreenHeight, // height, in pixels
|
||||
screenWidth, // width, in pixels
|
||||
screenHeight, // height, in pixels
|
||||
SDL_WINDOW_RESIZABLE),
|
||||
sdl_deleter());
|
||||
if (window_ == nullptr) {
|
||||
@@ -167,7 +207,7 @@ absl::Status Controller::CreateWindow() {
|
||||
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
|
||||
}
|
||||
// Initialize SDL_mixer
|
||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
|
||||
if (Mix_OpenAudio(32000, MIX_DEFAULT_FORMAT, 2, 1024) < 0) {
|
||||
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
|
||||
Mix_GetError());
|
||||
}
|
||||
@@ -191,35 +231,87 @@ absl::Status Controller::CreateRenderer() {
|
||||
}
|
||||
|
||||
absl::Status Controller::CreateGuiContext() {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
if (flags()->kUseNewImGuiInput) {
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
}
|
||||
|
||||
// Initialize ImGui for SDL
|
||||
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
|
||||
ImGui_ImplSDLRenderer_Init(renderer_.get());
|
||||
ImGui_ImplSDLRenderer2_Init(renderer_.get());
|
||||
|
||||
// Load available fonts
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Karla-Regular.ttf", 14.0f);
|
||||
if (flags()->kLoadSystemFonts) {
|
||||
LoadSystemFonts();
|
||||
} else {
|
||||
RETURN_IF_ERROR(LoadFontFamilies());
|
||||
}
|
||||
|
||||
// merge in icons from Google Material Design
|
||||
// Set the default style
|
||||
gui::ColorsYaze();
|
||||
|
||||
// Build a new ImGui frame
|
||||
ImGui_ImplSDLRenderer2_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(window_.get());
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
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";
|
||||
static const float FONT_SIZE_DEFAULT = 14.0f;
|
||||
static const float FONT_SIZE_DROID_SANS = 16.0f;
|
||||
static const float ICON_FONT_SIZE = 18.0f;
|
||||
|
||||
// Icon configuration
|
||||
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||
ImFontConfig icons_config;
|
||||
icons_config.MergeMode = true;
|
||||
icons_config.GlyphOffset.y = 5.0f;
|
||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||
icons_config.PixelSnapH = true;
|
||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, 18.0f, &icons_config,
|
||||
icons_ranges);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Roboto-Medium.ttf", 14.0f);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/Cousine-Regular.ttf", 14.0f);
|
||||
io.Fonts->AddFontFromFileTTF("assets/font/DroidSans.ttf", 16.0f);
|
||||
|
||||
// Set the default style
|
||||
gui::ColorsYaze();
|
||||
// Japanese font configuration
|
||||
ImFontConfig japanese_font_config;
|
||||
japanese_font_config.MergeMode = true;
|
||||
icons_config.GlyphOffset.y = 5.0f;
|
||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||
icons_config.PixelSnapH = true;
|
||||
|
||||
// Build a new ImGui frame
|
||||
ImGui_ImplSDLRenderer_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(window_.get());
|
||||
// List of fonts to be loaded
|
||||
std::vector<const char *> font_paths = {KARLA_REGULAR, ROBOTO_MEDIUM,
|
||||
COUSINE_REGULAR, IBM_PLEX_JP};
|
||||
|
||||
// Load fonts with associated icon and Japanese merges
|
||||
for (const auto &font_path : font_paths) {
|
||||
float font_size =
|
||||
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
|
||||
|
||||
if (!io.Fonts->AddFontFromFileTTF(font_path, font_size)) {
|
||||
return absl::InternalError(
|
||||
absl::StrFormat("Failed to load font from %s", font_path));
|
||||
}
|
||||
|
||||
// Merge icon set
|
||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE,
|
||||
&icons_config, icons_ranges);
|
||||
|
||||
// Merge Japanese font
|
||||
io.Fonts->AddFontFromFileTTF(NOTO_SANS_JP, 18.0f, &japanese_font_config,
|
||||
io.Fonts->GetGlyphRangesJapanese());
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
#define YAZE_APP_CORE_CONTROLLER_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <imgui/backends/imgui_impl_sdl.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
||||
#include <imgui/backends/imgui_impl_sdl2.h>
|
||||
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/editor/master_editor.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/style.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/style.h"
|
||||
|
||||
int main(int argc, char **argv);
|
||||
|
||||
@@ -20,26 +22,34 @@ namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
class Controller {
|
||||
class Controller : public ExperimentFlags {
|
||||
public:
|
||||
bool isActive() const;
|
||||
absl::Status onEntry();
|
||||
void onInput();
|
||||
void onLoad();
|
||||
void onLoadDelta();
|
||||
void doRender() const;
|
||||
void onExit() const;
|
||||
bool IsActive() const { return active_; }
|
||||
absl::Status OnEntry();
|
||||
void OnInput();
|
||||
void OnLoad();
|
||||
void DoRender() const;
|
||||
void OnExit();
|
||||
|
||||
private:
|
||||
struct sdl_deleter {
|
||||
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
|
||||
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
|
||||
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); }
|
||||
};
|
||||
|
||||
absl::Status CreateWindow();
|
||||
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);
|
||||
|
||||
21
src/app/core/editor.h
Normal file
21
src/app/core/editor.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef YAZE_APP_CORE_EDITOR_H
|
||||
#define YAZE_APP_CORE_EDITOR_H
|
||||
|
||||
#include "absl/status/status.h"
|
||||
|
||||
class Editor {
|
||||
public:
|
||||
Editor() = default;
|
||||
virtual ~Editor() = default;
|
||||
|
||||
virtual absl::Status Cut() = 0;
|
||||
virtual absl::Status Copy() = 0;
|
||||
virtual absl::Status Paste() = 0;
|
||||
|
||||
virtual absl::Status Undo() = 0;
|
||||
virtual absl::Status Redo() = 0;
|
||||
|
||||
virtual absl::Status Update() = 0;
|
||||
};
|
||||
|
||||
#endif // YAZE_APP_CORE_EDITOR_H
|
||||
137
src/app/core/labeling.cc
Normal file
137
src/app/core/labeling.cc
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "app/core/labeling.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
bool ResourceLabelManager::LoadLabels(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
// Create the file if it does not exist
|
||||
std::ofstream create_file(filename);
|
||||
if (!create_file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
create_file.close();
|
||||
file.open(filename);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
filename_ = filename;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
std::istringstream iss(line);
|
||||
std::string type, key, value;
|
||||
if (std::getline(iss, type, ',') && std::getline(iss, key, ',') &&
|
||||
std::getline(iss, value)) {
|
||||
labels_[type][key] = value;
|
||||
}
|
||||
}
|
||||
labels_loaded_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceLabelManager::SaveLabels() {
|
||||
if (!labels_loaded_) {
|
||||
return false;
|
||||
}
|
||||
std::ofstream file(filename_);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& type_pair : labels_) {
|
||||
for (const auto& label_pair : type_pair.second) {
|
||||
file << type_pair.first << "," << label_pair.first << ","
|
||||
<< label_pair.second << std::endl;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResourceLabelManager::DisplayLabels(bool* p_open) {
|
||||
if (!labels_loaded_) {
|
||||
ImGui::Text("No labels loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Begin("Resource Labels", p_open)) {
|
||||
for (const auto& type_pair : labels_) {
|
||||
if (ImGui::TreeNode(type_pair.first.c_str())) {
|
||||
for (const auto& label_pair : type_pair.second) {
|
||||
std::string label_id = type_pair.first + "_" + label_pair.first;
|
||||
ImGui::Text("%s: %s", label_pair.first.c_str(),
|
||||
label_pair.second.c_str());
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("Update Labels")) {
|
||||
if (SaveLabels()) {
|
||||
ImGui::Text("Labels updated successfully!");
|
||||
} else {
|
||||
ImGui::Text("Failed to update labels.");
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ResourceLabelManager::EditLabel(const std::string& type,
|
||||
const std::string& key,
|
||||
const std::string& newValue) {
|
||||
labels_[type][key] = newValue;
|
||||
}
|
||||
|
||||
void ResourceLabelManager::SelectableLabelWithNameEdit(
|
||||
bool selected, const std::string& type, const std::string& key,
|
||||
const std::string& defaultValue) {
|
||||
std::string label = CreateOrGetLabel(type, key, defaultValue);
|
||||
ImGui::Selectable(label.c_str(), selected,
|
||||
ImGuiSelectableFlags_AllowDoubleClick);
|
||||
std::string label_id = type + "_" + key;
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
ImGui::OpenPopup(label_id.c_str());
|
||||
}
|
||||
|
||||
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,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
labels_[type][key] = new_label;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ResourceLabelManager::CreateOrGetLabel(
|
||||
const std::string& type, const std::string& key,
|
||||
const std::string& defaultValue) {
|
||||
if (labels_.find(type) == labels_.end()) {
|
||||
labels_[type] = std::unordered_map<std::string, std::string>();
|
||||
}
|
||||
if (labels_[type].find(key) == labels_[type].end()) {
|
||||
labels_[type][key] = defaultValue;
|
||||
}
|
||||
return labels_[type][key];
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
57
src/app/core/labeling.h
Normal file
57
src/app/core/labeling.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef YAZE_APP_CORE_LABELING_H_
|
||||
#define YAZE_APP_CORE_LABELING_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/constants.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace core {
|
||||
|
||||
// Default types
|
||||
static constexpr absl::string_view kDefaultTypes[] = {
|
||||
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
|
||||
|
||||
class ResourceLabelManager {
|
||||
public:
|
||||
ResourceLabelManager() = default;
|
||||
|
||||
bool LoadLabels(const std::string& filename);
|
||||
bool SaveLabels();
|
||||
void DisplayLabels(bool* p_open);
|
||||
void EditLabel(const std::string& type, const std::string& key,
|
||||
const std::string& newValue);
|
||||
void SelectableLabelWithNameEdit(bool selected, const std::string& type,
|
||||
const std::string& key,
|
||||
const std::string& defaultValue);
|
||||
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||
const std::string& defaultValue);
|
||||
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||
const absl::string_view& defaultValue);
|
||||
|
||||
private:
|
||||
bool labels_loaded_ = false;
|
||||
std::string filename_;
|
||||
struct ResourceType {
|
||||
std::string key_name;
|
||||
std::string display_description;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
|
||||
labels_;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_CORE_LABELING_H_
|
||||
14
src/app/core/platform/app_delegate.h
Normal file
14
src/app/core/platform/app_delegate.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void InitializeCocoa();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||
218
src/app/core/platform/app_delegate.mm
Normal file
218
src/app/core/platform/app_delegate.mm
Normal file
@@ -0,0 +1,218 @@
|
||||
// AppDelegate.mm
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "app/core/controller.h"
|
||||
#import "app/core/editor.h"
|
||||
#import "app/core/platform/app_delegate.h"
|
||||
#import "app/core/platform/file_dialog.h"
|
||||
#import "app/rom.h"
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||
- (void)setupMenus;
|
||||
// - (void)changeApplicationIcon;
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
// - (void)changeApplicationIcon {
|
||||
// NSImage *newIcon = [NSImage imageNamed:@"newIcon"];
|
||||
// [NSApp setApplicationIconImage:newIcon];
|
||||
// }
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[self setupMenus];
|
||||
}
|
||||
|
||||
- (void)setupMenus {
|
||||
NSMenu *mainMenu = [NSApp mainMenu];
|
||||
|
||||
NSMenuItem *fileMenuItem = [mainMenu itemWithTitle:@"File"];
|
||||
if (!fileMenuItem) {
|
||||
NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"];
|
||||
fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""];
|
||||
[fileMenuItem setSubmenu:fileMenu];
|
||||
|
||||
NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:@"Open"
|
||||
action:@selector(openFileAction:)
|
||||
keyEquivalent:@"o"];
|
||||
[fileMenu addItem:openItem];
|
||||
|
||||
// Open Recent
|
||||
NSMenu *openRecentMenu = [[NSMenu alloc] initWithTitle:@"Open Recent"];
|
||||
NSMenuItem *openRecentMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open Recent"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[openRecentMenuItem setSubmenu:openRecentMenu];
|
||||
[fileMenu addItem:openRecentMenuItem];
|
||||
|
||||
// Add a separator
|
||||
[fileMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Save
|
||||
NSMenuItem *saveItem = [[NSMenuItem alloc] initWithTitle:@"Save" action:nil keyEquivalent:@"s"];
|
||||
[fileMenu addItem:saveItem];
|
||||
|
||||
// Separator
|
||||
[fileMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Options submenu
|
||||
NSMenu *optionsMenu = [[NSMenu alloc] initWithTitle:@"Options"];
|
||||
NSMenuItem *optionsMenuItem = [[NSMenuItem alloc] initWithTitle:@"Options"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[optionsMenuItem setSubmenu:optionsMenu];
|
||||
|
||||
// Flag checkmark field
|
||||
NSMenuItem *flagItem = [[NSMenuItem alloc] initWithTitle:@"Flag"
|
||||
action:@selector(toggleFlagAction:)
|
||||
keyEquivalent:@""];
|
||||
[flagItem setTarget:self];
|
||||
[flagItem setState:NSControlStateValueOff];
|
||||
[optionsMenu addItem:flagItem];
|
||||
[fileMenu addItem:optionsMenuItem];
|
||||
|
||||
[mainMenu insertItem:fileMenuItem atIndex:1];
|
||||
}
|
||||
|
||||
NSMenuItem *editMenuItem = [mainMenu itemWithTitle:@"Edit"];
|
||||
if (!editMenuItem) {
|
||||
NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
|
||||
editMenuItem = [[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""];
|
||||
[editMenuItem setSubmenu:editMenu];
|
||||
|
||||
NSMenuItem *undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:nil keyEquivalent:@"z"];
|
||||
|
||||
[editMenu addItem:undoItem];
|
||||
|
||||
NSMenuItem *redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:nil keyEquivalent:@"Z"];
|
||||
|
||||
[editMenu addItem:redoItem];
|
||||
|
||||
// Add a separator
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
NSMenuItem *cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
|
||||
action:@selector(cutAction:)
|
||||
keyEquivalent:@"x"];
|
||||
[editMenu addItem:cutItem];
|
||||
|
||||
NSMenuItem *copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:nil keyEquivalent:@"c"];
|
||||
[editMenu addItem:copyItem];
|
||||
|
||||
NSMenuItem *pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
|
||||
action:nil
|
||||
keyEquivalent:@"v"];
|
||||
|
||||
[editMenu addItem:pasteItem];
|
||||
|
||||
// Add a separator
|
||||
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
NSMenuItem *selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All"
|
||||
action:nil
|
||||
keyEquivalent:@"a"];
|
||||
|
||||
[editMenu addItem:selectAllItem];
|
||||
|
||||
[mainMenu insertItem:editMenuItem atIndex:2];
|
||||
}
|
||||
|
||||
NSMenuItem *viewMenuItem = [mainMenu itemWithTitle:@"View"];
|
||||
if (!viewMenuItem) {
|
||||
NSMenu *viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
|
||||
viewMenuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
|
||||
[viewMenuItem setSubmenu:viewMenu];
|
||||
|
||||
// Emulator view button
|
||||
NSMenuItem *emulatorViewItem = [[NSMenuItem alloc] initWithTitle:@"Emulator View"
|
||||
action:nil
|
||||
keyEquivalent:@"1"];
|
||||
|
||||
[viewMenu addItem:emulatorViewItem];
|
||||
|
||||
// Hex Editor View
|
||||
NSMenuItem *hexEditorViewItem = [[NSMenuItem alloc] initWithTitle:@"Hex Editor View"
|
||||
action:nil
|
||||
keyEquivalent:@"2"];
|
||||
|
||||
[viewMenu addItem:hexEditorViewItem];
|
||||
|
||||
// Disassembly view button
|
||||
NSMenuItem *disassemblyViewItem = [[NSMenuItem alloc] initWithTitle:@"Disassembly View"
|
||||
action:nil
|
||||
keyEquivalent:@"3"];
|
||||
|
||||
[viewMenu addItem:disassemblyViewItem];
|
||||
|
||||
// Memory view button
|
||||
NSMenuItem *memoryViewItem = [[NSMenuItem alloc] initWithTitle:@"Memory View"
|
||||
action:nil
|
||||
keyEquivalent:@"4"];
|
||||
|
||||
[viewMenu addItem:memoryViewItem];
|
||||
|
||||
// Add a separator
|
||||
[viewMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// Toggle fullscreen button
|
||||
NSMenuItem *toggleFullscreenItem = [[NSMenuItem alloc] initWithTitle:@"Toggle Fullscreen"
|
||||
action:nil
|
||||
keyEquivalent:@"f"];
|
||||
|
||||
[viewMenu addItem:toggleFullscreenItem];
|
||||
|
||||
[mainMenu insertItem:viewMenuItem atIndex:3];
|
||||
}
|
||||
|
||||
NSMenuItem *helpMenuItem = [mainMenu itemWithTitle:@"Help"];
|
||||
if (!helpMenuItem) {
|
||||
NSMenu *helpMenu = [[NSMenu alloc] initWithTitle:@"Help"];
|
||||
helpMenuItem = [[NSMenuItem alloc] initWithTitle:@"Help" action:nil keyEquivalent:@""];
|
||||
[helpMenuItem setSubmenu:helpMenu];
|
||||
|
||||
// URL to online documentation
|
||||
NSMenuItem *documentationItem = [[NSMenuItem alloc] initWithTitle:@"Documentation"
|
||||
action:nil
|
||||
keyEquivalent:@"?"];
|
||||
[helpMenu addItem:documentationItem];
|
||||
|
||||
[mainMenu insertItem:helpMenuItem atIndex:4];
|
||||
}
|
||||
}
|
||||
|
||||
// Action method for the New menu item
|
||||
- (void)newFileAction:(id)sender {
|
||||
NSLog(@"New File action triggered");
|
||||
}
|
||||
|
||||
- (void)toggleFlagAction:(id)sender {
|
||||
NSMenuItem *flagItem = (NSMenuItem *)sender;
|
||||
if ([flagItem state] == NSControlStateValueOff) {
|
||||
[flagItem setState:NSControlStateValueOn];
|
||||
} else {
|
||||
[flagItem setState:NSControlStateValueOff];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)openFileAction:(id)sender {
|
||||
yaze::app::SharedROM::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog());
|
||||
}
|
||||
|
||||
- (void)cutAction:(id)sender {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
- (void)openRecentFileAction:(id)sender {
|
||||
NSLog(@"Open Recent File action triggered");
|
||||
}
|
||||
|
||||
extern "C" void InitializeCocoa() {
|
||||
@autoreleasepool {
|
||||
AppDelegate *delegate = [[AppDelegate alloc] init];
|
||||
[NSApplication sharedApplication];
|
||||
[NSApp setDelegate:delegate];
|
||||
[NSApp finishLaunching];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
8
src/app/core/platform/clipboard.cc
Normal file
8
src/app/core/platform/clipboard.cc
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "app/core/platform/clipboard.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
|
||||
int& height) {}
|
||||
27
src/app/core/platform/clipboard.h
Normal file
27
src/app/core/platform/clipboard.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||
#define YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
||||
|
||||
#endif
|
||||
|
||||
#endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||
42
src/app/core/platform/clipboard.mm
Normal file
42
src/app/core/platform/clipboard.mm
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "clipboard.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
|
||||
NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()];
|
||||
NSImage* image = [[NSImage alloc] initWithData:data];
|
||||
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard clearContents];
|
||||
[pasteboard writeObjects:@[ image ]];
|
||||
}
|
||||
|
||||
void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
NSArray* classArray = [NSArray arrayWithObject:[NSImage class]];
|
||||
NSDictionary* options = [NSDictionary dictionary];
|
||||
|
||||
NSImage* image = [pasteboard readObjectsForClasses:classArray options:options].firstObject;
|
||||
if (!image) {
|
||||
width = height = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Assuming the image is in an RGBA format
|
||||
CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil];
|
||||
width = (int)CGImageGetWidth(cgImage);
|
||||
height = (int)CGImageGetHeight(cgImage);
|
||||
|
||||
size_t bytesPerRow = 4 * width;
|
||||
size_t totalBytes = bytesPerRow * height;
|
||||
pixel_data.resize(totalBytes);
|
||||
|
||||
CGContextRef context = CGBitmapContextCreate(
|
||||
pixel_data.data(), width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(),
|
||||
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
||||
CGContextRelease(context);
|
||||
}
|
||||
65
src/app/core/platform/file_dialog.h
Normal file
65
src/app/core/platform/file_dialog.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Include Windows-specific headers
|
||||
#include <shobjidl.h>
|
||||
#include <windows.h>
|
||||
|
||||
class FileDialogWrapper {
|
||||
public:
|
||||
static std::string ShowOpenFileDialog() {
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
|
||||
IFileDialog *pfd = NULL;
|
||||
HRESULT hr =
|
||||
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
|
||||
IID_IFileDialog, reinterpret_cast<void **>(&pfd));
|
||||
std::string file_path_windows;
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Show the dialog
|
||||
hr = pfd->Show(NULL);
|
||||
if (SUCCEEDED(hr)) {
|
||||
IShellItem *psiResult;
|
||||
hr = pfd->GetResult(&psiResult);
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Get the file path
|
||||
PWSTR pszFilePath;
|
||||
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
||||
char str[128];
|
||||
wcstombs(str, pszFilePath, 128);
|
||||
file_path_windows = str;
|
||||
psiResult->Release();
|
||||
CoTaskMemFree(pszFilePath);
|
||||
}
|
||||
}
|
||||
pfd->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
return file_path_windows;
|
||||
}
|
||||
};
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <string>
|
||||
|
||||
class FileDialogWrapper {
|
||||
public:
|
||||
static std::string ShowOpenFileDialog();
|
||||
};
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
class FileDialogWrapper {
|
||||
public:
|
||||
static std::string ShowOpenFileDialog() {
|
||||
// Linux-specific file dialog implementation using GTK
|
||||
// ...
|
||||
return "file_path_linux";
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
#error "Unsupported platform."
|
||||
#endif
|
||||
17
src/app/core/platform/file_dialog.mm
Normal file
17
src/app/core/platform/file_dialog.mm
Normal file
@@ -0,0 +1,17 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include "app/core/platform/file_dialog.h"
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
||||
[openPanel setCanChooseFiles:YES];
|
||||
[openPanel setCanChooseDirectories:NO];
|
||||
[openPanel setAllowsMultipleSelection:NO];
|
||||
|
||||
if ([openPanel runModal] == NSModalResponseOK) {
|
||||
NSURL* url = [[openPanel URLs] objectAtIndex:0];
|
||||
NSString* path = [url path];
|
||||
return std::string([path UTF8String]);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
81
src/app/core/platform/font_loader.cc
Normal file
81
src/app/core/platform/font_loader.cc
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "app/core/platform/font_loader.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
|
||||
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
|
||||
DWORD FontType, LPARAM lParam) {
|
||||
// Step 3: Load the font into ImGui
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LoadSystemFonts() {
|
||||
HKEY hKey;
|
||||
std::vector<std::string> fontPaths;
|
||||
|
||||
// Open the registry key where fonts are listed
|
||||
if (RegOpenKeyEx(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"), 0,
|
||||
KEY_READ, &hKey) == ERROR_SUCCESS) {
|
||||
DWORD valueCount;
|
||||
DWORD maxValueNameSize;
|
||||
DWORD maxValueDataSize;
|
||||
|
||||
// Query the number of entries and the maximum size of the names and values
|
||||
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
|
||||
&maxValueNameSize, &maxValueDataSize, NULL, NULL);
|
||||
|
||||
char* valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
|
||||
BYTE* valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
|
||||
|
||||
// Enumerate all font entries
|
||||
for (DWORD i = 0; i < valueCount; i++) {
|
||||
DWORD valueNameSize = maxValueNameSize + 1; // +1 for null terminator
|
||||
DWORD valueDataSize = maxValueDataSize + 1; // +1 for null terminator
|
||||
DWORD valueType;
|
||||
|
||||
// Clear buffers
|
||||
memset(valueName, 0, valueNameSize);
|
||||
memset(valueData, 0, valueDataSize);
|
||||
|
||||
// Get the font name and file path
|
||||
if (RegEnumValue(hKey, i, valueName, &valueNameSize, NULL, &valueType,
|
||||
valueData, &valueDataSize) == ERROR_SUCCESS) {
|
||||
if (valueType == REG_SZ) {
|
||||
// Add the font file path to the vector
|
||||
std::string fontPath((char*)valueData);
|
||||
fontPaths.push_back(fontPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] valueName;
|
||||
delete[] valueData;
|
||||
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
for (const auto& fontPath : fontPaths) {
|
||||
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
void LoadSystemFonts() {
|
||||
// Load Linux System Fonts into ImGui
|
||||
// ...
|
||||
}
|
||||
|
||||
#endif
|
||||
15
src/app/core/platform/font_loader.h
Normal file
15
src/app/core/platform/font_loader.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// FontLoader.h
|
||||
#ifndef FONTLOADER_H
|
||||
#define FONTLOADER_H
|
||||
|
||||
// Function declaration for loading system fonts into ImGui
|
||||
void LoadSystemFonts();
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
// Windows specific function declaration for loading system fonts into ImGui
|
||||
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
|
||||
DWORD FontType, LPARAM lParam);
|
||||
#endif
|
||||
|
||||
#endif // FONTLOADER_H
|
||||
50
src/app/core/platform/font_loader.mm
Normal file
50
src/app/core/platform/font_loader.mm
Normal file
@@ -0,0 +1,50 @@
|
||||
// FontLoader.mm
|
||||
#include "app/core/platform/font_loader.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreText/CoreText.h>
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "app/gui/icons.h"
|
||||
|
||||
void LoadSystemFonts() {
|
||||
// List of common macOS system fonts
|
||||
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
|
||||
|
||||
for (NSString *fontName in fontNames) {
|
||||
NSFont *font = [NSFont fontWithName:fontName size:14.0];
|
||||
if (!font) {
|
||||
NSLog(@"Font not found: %@", fontName);
|
||||
continue;
|
||||
}
|
||||
|
||||
CTFontDescriptorRef fontDescriptor =
|
||||
CTFontDescriptorCreateWithNameAndSize((CFStringRef)font.fontName, font.pointSize);
|
||||
CFURLRef fontURL = (CFURLRef)CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontURLAttribute);
|
||||
NSString *fontPath = [(NSURL *)fontURL path];
|
||||
CFRelease(fontDescriptor);
|
||||
|
||||
if (fontPath != nil && [[NSFileManager defaultManager] isReadableFileAtPath:fontPath]) {
|
||||
// Load the font into ImGui
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
ImFontConfig icons_config;
|
||||
icons_config.MergeMode = true;
|
||||
icons_config.GlyphOffset.y = 5.0f;
|
||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||
icons_config.PixelSnapH = true;
|
||||
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||
static const float ICON_FONT_SIZE = 18.0f;
|
||||
ImFont *imFont = io.Fonts->AddFontFromFileTTF([fontPath UTF8String], 14.0f);
|
||||
if (!imFont) {
|
||||
NSLog(@"Failed to load font: %@", fontPath);
|
||||
}
|
||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE, &icons_config,
|
||||
icons_ranges);
|
||||
} else {
|
||||
NSLog(@"Font file not accessible: %@", fontPath);
|
||||
}
|
||||
|
||||
if (fontURL) {
|
||||
CFRelease(fontURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
add_library(delta-service delta.proto)
|
||||
target_link_libraries(delta-service
|
||||
PUBLIC
|
||||
protobuf::libprotobuf
|
||||
gRPC::grpc
|
||||
gRPC::grpc++
|
||||
)
|
||||
|
||||
target_include_directories(delta-service
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${PROTOBUF_INCLUDE_PATH}
|
||||
)
|
||||
|
||||
get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
|
||||
|
||||
# compile the message types
|
||||
protobuf_generate(TARGET delta-service LANGUAGE cpp)
|
||||
|
||||
# compile the GRPC services
|
||||
protobuf_generate(
|
||||
TARGET
|
||||
delta-service
|
||||
LANGUAGE
|
||||
grpc
|
||||
GENERATE_EXTENSIONS
|
||||
.grpc.pb.h
|
||||
.grpc.pb.cc
|
||||
PLUGIN
|
||||
"protoc-gen-grpc=${grpc_cpp_plugin_location}"
|
||||
)
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "client.h"
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
void Client::CreateChannel() {
|
||||
auto channel = grpc::CreateChannel("localhost:50051",
|
||||
grpc::InsecureChannelCredentials());
|
||||
stub_ = ::YazeDelta::NewStub(channel);
|
||||
}
|
||||
|
||||
absl::Status Client::InitRepo(std::string author_name,
|
||||
std::string project_name) {
|
||||
Repository new_repo;
|
||||
new_repo.set_author_name(author_name);
|
||||
new_repo.set_project_name(project_name);
|
||||
new_repo.mutable_tree()->Add();
|
||||
|
||||
InitRequest request;
|
||||
request.set_allocated_repo(&new_repo);
|
||||
|
||||
InitResponse response;
|
||||
Status status = stub_->Init(&rpc_context, request, &response);
|
||||
|
||||
if (!status.ok()) {
|
||||
std::cerr << status.error_code() << ": " << status.error_message()
|
||||
<< std::endl;
|
||||
return absl::InternalError(status.error_message());
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef YAZE_APP_DELTA_CLIENT_H
|
||||
#define YAZE_APP_DELTA_CLIENT_H
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
class Client {
|
||||
public:
|
||||
Client() = default;
|
||||
void CreateChannel();
|
||||
absl::Status InitRepo(std::string author_name, std::string project_name);
|
||||
|
||||
private:
|
||||
ClientContext rpc_context;
|
||||
std::vector<Repository> repos_;
|
||||
std::unique_ptr<YazeDelta::Stub> stub_;
|
||||
};
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
#if defined(_WIN32)
|
||||
#define main SDL_main
|
||||
#endif
|
||||
|
||||
#include "absl/debugging/failure_signal_handler.h"
|
||||
#include "absl/debugging/symbolize.h"
|
||||
#include "app/core/controller.h"
|
||||
#include "app/delta/viewer.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
absl::InitializeSymbolizer(argv[0]);
|
||||
|
||||
absl::FailureSignalHandlerOptions options;
|
||||
absl::InstallFailureSignalHandler(options);
|
||||
|
||||
yaze::app::core::Controller controller;
|
||||
yaze::app::delta::Viewer viewer;
|
||||
|
||||
auto entry_status = controller.onEntry();
|
||||
if (!entry_status.ok()) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while (controller.isActive()) {
|
||||
controller.onInput();
|
||||
viewer.Update();
|
||||
controller.doRender();
|
||||
}
|
||||
controller.onExit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
syntax = "proto3";
|
||||
option cc_enable_arenas = true;
|
||||
|
||||
service YazeDelta {
|
||||
rpc Init(InitRequest) returns (InitResponse) {}
|
||||
|
||||
rpc Clone(CloneRequest) returns (CloneResponse) {}
|
||||
|
||||
rpc Push(PushRequest) returns (PushResponse) {}
|
||||
rpc Pull(PullRequest) returns (PullResponse) {}
|
||||
|
||||
rpc Sync(stream SyncRequest) returns (stream SyncResponse) {}
|
||||
|
||||
rpc CreateBranch(CreateBranchRequest) returns (CreateBranchResponse) {}
|
||||
rpc DeleteBranch(DeleteBranchRequest) returns (DeleteBranchResponse) {}
|
||||
|
||||
rpc Merge(MergeRequest) returns (MergeResponse) {}
|
||||
rpc UndoMerge(UndoMergeRequest) returns (UndoMergeResponse) {}
|
||||
}
|
||||
|
||||
enum ChangeType {
|
||||
OVERWORLD_MAP = 0;
|
||||
DUNGEON_MAP = 1;
|
||||
MONOLOGUE = 2;
|
||||
PALETTE = 3;
|
||||
OBJECT = 4;
|
||||
ASSEMBLY = 5;
|
||||
MISC = 6;
|
||||
}
|
||||
|
||||
message Delta {
|
||||
int64 offset = 1;
|
||||
int64 length = 2;
|
||||
bytes data = 3;
|
||||
ChangeType type = 4;
|
||||
}
|
||||
|
||||
message Commit {
|
||||
int64 commit_id = 1;
|
||||
int64 parent_commit_id = 2;
|
||||
string author_name = 3;
|
||||
string message_header = 4;
|
||||
optional string message_body = 5;
|
||||
repeated Delta delta = 6;
|
||||
int64 signature = 7;
|
||||
}
|
||||
|
||||
message Branch {
|
||||
string branch_name = 1;
|
||||
optional string parent_name = 2;
|
||||
repeated Commit commits = 3;
|
||||
}
|
||||
|
||||
message Repository {
|
||||
string project_name = 1;
|
||||
string author_name = 2;
|
||||
int64 signature = 3;
|
||||
optional bool locked = 4;
|
||||
optional string password = 5;
|
||||
repeated Branch tree = 6;
|
||||
}
|
||||
|
||||
message InitRequest {
|
||||
Repository repo = 1;
|
||||
}
|
||||
|
||||
message InitResponse {
|
||||
int32 response = 1;
|
||||
}
|
||||
|
||||
message CloneRequest {}
|
||||
|
||||
message CloneResponse {}
|
||||
|
||||
message PushRequest {
|
||||
string author_name = 1;
|
||||
string repository_name= 2;
|
||||
string branch_name = 3;
|
||||
repeated Commit commits = 4;
|
||||
}
|
||||
message PushResponse {}
|
||||
|
||||
message PullRequest {
|
||||
string repository_name = 1;
|
||||
string branch_name = 2;
|
||||
repeated Commit commits = 3;
|
||||
}
|
||||
|
||||
message PullResponse {}
|
||||
|
||||
message SyncRequest {}
|
||||
|
||||
message SyncResponse {}
|
||||
|
||||
message CreateBranchRequest {}
|
||||
message CreateBranchResponse {}
|
||||
|
||||
message DeleteBranchRequest {}
|
||||
message DeleteBranchResponse {}
|
||||
|
||||
message MergeRequest {}
|
||||
message MergeResponse {}
|
||||
|
||||
message UndoMergeRequest {}
|
||||
message UndoMergeResponse {}
|
||||
@@ -1,87 +0,0 @@
|
||||
#include "service.h"
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Channel;
|
||||
using grpc::ClientAsyncResponseReader;
|
||||
using grpc::ClientContext;
|
||||
using grpc::CompletionQueue;
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::Status;
|
||||
|
||||
namespace {
|
||||
auto FindRepository(std::vector<Repository>& repos, const std::string& name) {
|
||||
for (auto& repo : repos) {
|
||||
if (repo.project_name() == name) {
|
||||
return repo.mutable_tree();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto FindBranch(google::protobuf::RepeatedPtrField<Branch>* repo,
|
||||
const std::string& branch_name) {
|
||||
for (auto it = repo->begin(); it != repo->end(); ++it) {
|
||||
if (it->branch_name() == branch_name) {
|
||||
return it->mutable_commits();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status DeltaService::Init(grpc::ServerContext* context,
|
||||
const InitRequest* request, InitResponse* reply) {
|
||||
std::filesystem::create_directories("./.yaze");
|
||||
repos_.push_back(request->repo());
|
||||
// std::ofstream commit_stream("./.yaze/commits");
|
||||
// for (const auto& repo : repos_) {
|
||||
// for (const auto& branch : repo.tree()) {
|
||||
// for (const auto& commit : branch.commits()) {
|
||||
// commit_stream << commit.DebugString();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Push(grpc::ServerContext* context,
|
||||
const PushRequest* request, PushResponse* reply) {
|
||||
const auto& repository_name = request->repository_name();
|
||||
const auto& branch_name = request->branch_name();
|
||||
auto repo = FindRepository(repos_, repository_name);
|
||||
auto mutable_commits = FindBranch(repo, branch_name);
|
||||
auto size = request->commits().size();
|
||||
for (int i = 1; i < size; ++i) {
|
||||
*mutable_commits->Add() = request->commits().at(i);
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Pull(grpc::ServerContext* context,
|
||||
const PullRequest* request, PullResponse* reply) {
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status DeltaService::Clone(grpc::ServerContext* context,
|
||||
const CloneRequest* request, CloneResponse* reply) {
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifndef YAZE_APP_DELTA_SERVICE_H
|
||||
#define YAZE_APP_DELTA_SERVICE_H
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <google/protobuf/message.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include <grpcpp/health_check_service_interface.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "src/app/delta/delta.grpc.pb.h"
|
||||
#include "src/app/delta/delta.pb.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
|
||||
using grpc::Status;
|
||||
|
||||
class DeltaService final : public ::YazeDelta::Service {
|
||||
public:
|
||||
Status Init(grpc::ServerContext* context, const InitRequest* request,
|
||||
InitResponse* reply) override;
|
||||
|
||||
Status Push(grpc::ServerContext* context, const PushRequest* request,
|
||||
PushResponse* reply) override;
|
||||
|
||||
Status Pull(grpc::ServerContext* context, const PullRequest* request,
|
||||
PullResponse* reply) override;
|
||||
|
||||
Status Clone(grpc::ServerContext* context, const CloneRequest* request,
|
||||
CloneResponse* reply) override;
|
||||
|
||||
auto Repos() const { return repos_; }
|
||||
|
||||
private:
|
||||
std::vector<Repository> repos_;
|
||||
};
|
||||
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
@@ -1,229 +0,0 @@
|
||||
#include "viewer.h"
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
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(ImVec2(0, 0));
|
||||
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||
|
||||
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Viewer::Update() {
|
||||
NewMasterFrame();
|
||||
DrawYazeMenu();
|
||||
DrawFileDialog();
|
||||
|
||||
ImGui::Text(ICON_MD_CHANGE_HISTORY);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%s", rom_.GetTitle());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Button(ICON_MD_SYNC);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_ARROW_UPWARD);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_ARROW_DOWNWARD);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_MERGE);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_LAN);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_COMMIT);
|
||||
ImGui::SameLine();
|
||||
ImGui::Button(ICON_MD_DIFFERENCE);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::SetNextItemWidth(75.f);
|
||||
ImGui::Button(ICON_MD_SEND);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("Server Address", &client_address_);
|
||||
|
||||
ImGui::SetNextItemWidth(75.f);
|
||||
ImGui::Button(ICON_MD_DOWNLOAD);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputText("Repository Source", &client_address_);
|
||||
|
||||
ImGui::Separator();
|
||||
DrawBranchTree();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Viewer::DrawFileDialog() {
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
rom_.LoadFromFile(filePathName);
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawYazeMenu() {
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawViewMenu();
|
||||
END_MENU_BAR()
|
||||
}
|
||||
|
||||
void Viewer::DrawFileMenu() const {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||
".sfc,.smc", ".");
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") {}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawViewMenu() {
|
||||
static bool show_imgui_metrics = false;
|
||||
static bool show_imgui_style_editor = false;
|
||||
static bool show_memory_editor = false;
|
||||
static bool show_imgui_demo = false;
|
||||
|
||||
if (show_imgui_metrics) {
|
||||
ImGui::ShowMetricsWindow(&show_imgui_metrics);
|
||||
}
|
||||
|
||||
if (show_memory_editor) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow("Memory Editor", (void*)&rom_, rom_.size());
|
||||
}
|
||||
|
||||
if (show_imgui_demo) {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
|
||||
if (show_imgui_style_editor) {
|
||||
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
|
||||
ImGui::ShowStyleEditor();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
||||
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("GUI Tools")) {
|
||||
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
|
||||
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
|
||||
&show_imgui_style_editor);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewer::DrawBranchTree() {
|
||||
static ImGuiTableFlags flags =
|
||||
ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH |
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_NoBordersInBody;
|
||||
|
||||
if (ImGui::BeginTable("3ways", 3, flags)) {
|
||||
// The first column will use the default _WidthStretch when ScrollX is Off
|
||||
// and _WidthFixed when ScrollX is On
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed,
|
||||
10 * 12.0f);
|
||||
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
|
||||
10 * 18.0f);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// Simple storage to output a dummy file-system.
|
||||
struct MyTreeNode {
|
||||
const char* Name;
|
||||
const char* Type;
|
||||
int Size;
|
||||
int ChildIdx;
|
||||
int ChildCount;
|
||||
static void DisplayNode(const MyTreeNode* node,
|
||||
const MyTreeNode* all_nodes) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
const bool is_folder = (node->ChildCount > 0);
|
||||
if (is_folder) {
|
||||
bool open =
|
||||
ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("--");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(node->Type);
|
||||
if (open) {
|
||||
for (int child_n = 0; child_n < node->ChildCount; child_n++)
|
||||
DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
} else {
|
||||
ImGui::TreeNodeEx(
|
||||
node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet |
|
||||
ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
||||
ImGuiTreeNodeFlags_SpanFullWidth);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", node->Size);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(node->Type);
|
||||
}
|
||||
}
|
||||
};
|
||||
static const MyTreeNode nodes[] = {
|
||||
{"lttp-redux", "Repository", -1, 1, 3},
|
||||
{"main", "Branch", -1, 4, 2},
|
||||
{"hyrule-castle", "Branch", -1, 4, 2},
|
||||
{"lost-woods", "Branch", -1, 6, 3},
|
||||
{"Added some bushes", "Commit", 1024, -1, -1},
|
||||
{"Constructed a new house", "Commit", 123000, -1, -1},
|
||||
{"File1_b.wav", "Commit", 456000, -1, -1},
|
||||
{"Image001.png", "Commit", 203128, -1, -1},
|
||||
{"Copy of Image001.png", "Commit", 203256, -1, -1},
|
||||
{"Copy of Image001 (Final2).png", "Commit", 203512, -1, -1},
|
||||
};
|
||||
|
||||
MyTreeNode::DisplayNode(&nodes[0], nodes);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef YAZE_APP_DELTA_VIEWER_H
|
||||
#define YAZE_APP_DELTA_VIEWER_H
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/delta/client.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace delta {
|
||||
class Viewer {
|
||||
public:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void DrawFileDialog();
|
||||
|
||||
void DrawYazeMenu();
|
||||
void DrawFileMenu() const;
|
||||
void DrawViewMenu();
|
||||
|
||||
void DrawBranchTree();
|
||||
|
||||
std::string client_address_;
|
||||
|
||||
ROM rom_;
|
||||
Client client_;
|
||||
};
|
||||
} // namespace delta
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
26
src/app/editor/context/gfx_context.cc
Normal file
26
src/app/editor/context/gfx_context.cc
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "app/editor/context/gfx_context.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "app/core/editor.h"
|
||||
#include "app/gui/pipeline.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/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
|
||||
std::unordered_map<uint8_t, gfx::Paletteset> GfxContext::palettesets_;
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
36
src/app/editor/context/gfx_context.h
Normal file
36
src/app/editor/context/gfx_context.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/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 {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class GfxContext {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
protected:
|
||||
// Palettesets for the tile16 individual tiles
|
||||
static std::unordered_map<uint8_t, gfx::Paletteset> palettesets_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||
@@ -1,59 +1,853 @@
|
||||
#include "dungeon_editor.h"
|
||||
|
||||
#include "gui/icons.h"
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/labeling.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/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"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
void DungeonEditor::Update() {
|
||||
constexpr ImGuiTableFlags kDungeonObjectTableFlags =
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersV;
|
||||
|
||||
using ImGui::TableHeadersRow;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::TableNextRow;
|
||||
using ImGui::TableSetupColumn;
|
||||
|
||||
absl::Status DungeonEditor::Update() {
|
||||
if (!is_loaded_ && rom()->is_loaded()) {
|
||||
for (int i = 0; i < 0x100 + 40; i++) {
|
||||
rooms_.emplace_back(zelda3::dungeon::Room(i));
|
||||
rooms_[i].LoadHeader();
|
||||
rooms_[i].LoadRoomFromROM();
|
||||
if (flags()->kDrawDungeonRoomGraphics) {
|
||||
rooms_[i].LoadRoomGraphics();
|
||||
}
|
||||
|
||||
room_size_pointers_.push_back(rooms_[i].room_size_ptr());
|
||||
if (rooms_[i].room_size_ptr() != 0x0A8000) {
|
||||
room_size_addresses_[i] = rooms_[i].room_size_ptr();
|
||||
}
|
||||
|
||||
auto dungeon_palette_ptr = rom()->paletteset_ids[rooms_[i].palette][0];
|
||||
ASSIGN_OR_RETURN(auto palette_id,
|
||||
rom()->ReadWord(0xDEC4B + dungeon_palette_ptr));
|
||||
int p_id = palette_id / 180;
|
||||
auto color = rom()->palette_group("dungeon_main")[p_id][3];
|
||||
|
||||
room_palette_[rooms_[i].palette] = color.rgb();
|
||||
}
|
||||
|
||||
LoadDungeonRoomSize();
|
||||
LoadRoomEntrances();
|
||||
|
||||
// Load the palette group and palette for the dungeon
|
||||
full_palette_ =
|
||||
rom()->palette_group("dungeon_main")[current_palette_group_id_];
|
||||
ASSIGN_OR_RETURN(current_palette_group_,
|
||||
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
is_loaded_ = true;
|
||||
}
|
||||
|
||||
if (refresh_graphics_) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int block = rooms_[current_room_id_].blocks()[i];
|
||||
graphics_bin_[block].get()->ApplyPaletteWithTransparent(
|
||||
current_palette_group_[current_palette_id_], 0);
|
||||
rom()->UpdateBitmap(graphics_bin_[block].get(), true);
|
||||
}
|
||||
for (int i = 9; i < 16; i++) {
|
||||
int block = rooms_[current_room_id_].blocks()[i];
|
||||
graphics_bin_[block].get()->ApplyPaletteWithTransparent(
|
||||
rom()->palette_group("sprites_aux1")[current_palette_id_], 0);
|
||||
rom()->UpdateBitmap(graphics_bin_[block].get(), true);
|
||||
}
|
||||
|
||||
refresh_graphics_ = false;
|
||||
}
|
||||
|
||||
TAB_BAR("##DungeonEditorTabBar")
|
||||
TAB_ITEM("Room Editor")
|
||||
UpdateDungeonRoomView();
|
||||
END_TAB_ITEM()
|
||||
TAB_ITEM("Usage Statistics")
|
||||
if (is_loaded_) {
|
||||
static bool calc_stats = false;
|
||||
if (!calc_stats) {
|
||||
CalculateUsageStats();
|
||||
calc_stats = true;
|
||||
}
|
||||
DrawUsageStats();
|
||||
}
|
||||
END_TAB_ITEM()
|
||||
END_TAB_BAR()
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void DungeonEditor::LoadDungeonRoomSize() {
|
||||
std::map<int, std::vector<int>> rooms_by_bank;
|
||||
for (const auto& room : room_size_addresses_) {
|
||||
int bank = room.second >> 16;
|
||||
rooms_by_bank[bank].push_back(room.second);
|
||||
}
|
||||
|
||||
// Process and calculate room sizes within each bank
|
||||
for (auto& bank_rooms : rooms_by_bank) {
|
||||
// Sort the rooms within this bank
|
||||
std::sort(bank_rooms.second.begin(), bank_rooms.second.end());
|
||||
|
||||
for (size_t i = 0; i < bank_rooms.second.size(); ++i) {
|
||||
int room_ptr = bank_rooms.second[i];
|
||||
|
||||
// Identify the room ID for the current room pointer
|
||||
int room_id =
|
||||
std::find_if(room_size_addresses_.begin(), room_size_addresses_.end(),
|
||||
[room_ptr](const auto& entry) {
|
||||
return entry.second == room_ptr;
|
||||
})
|
||||
->first;
|
||||
|
||||
if (room_ptr != 0x0A8000) {
|
||||
if (i < bank_rooms.second.size() - 1) {
|
||||
// Calculate size as difference between current room and next room
|
||||
// in the same bank
|
||||
rooms_[room_id].set_room_size(bank_rooms.second[i + 1] - room_ptr);
|
||||
} else {
|
||||
// Calculate size for the last room in this bank
|
||||
int bank_end_address = (bank_rooms.first << 16) | 0xFFFF;
|
||||
rooms_[room_id].set_room_size(bank_end_address - room_ptr + 1);
|
||||
}
|
||||
total_room_size_ += rooms_[room_id].room_size();
|
||||
} else {
|
||||
// Room with address 0x0A8000
|
||||
rooms_[room_id].set_room_size(0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::UpdateDungeonRoomView() {
|
||||
DrawToolset();
|
||||
canvas_.DrawBackground();
|
||||
|
||||
if (palette_showing_) {
|
||||
ImGui::Begin("Palette Editor", &palette_showing_, 0);
|
||||
current_palette_ =
|
||||
rom()->palette_group("dungeon_main")[current_palette_group_id_];
|
||||
gui::SelectablePalettePipeline(current_palette_id_, refresh_graphics_,
|
||||
current_palette_);
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("#DungeonEditTable", 3, kDungeonTableFlags,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Room Selector");
|
||||
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Object Selector");
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
TAB_BAR("##DungeonRoomTabBar");
|
||||
TAB_ITEM("Rooms");
|
||||
DrawRoomSelector();
|
||||
END_TAB_ITEM();
|
||||
TAB_ITEM("Entrances");
|
||||
DrawEntranceSelector();
|
||||
END_TAB_ITEM();
|
||||
END_TAB_BAR();
|
||||
|
||||
TableNextColumn();
|
||||
DrawDungeonTabView();
|
||||
|
||||
TableNextColumn();
|
||||
DrawTileSelector();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("#undoTool");
|
||||
TableSetupColumn("#redoTool");
|
||||
TableSetupColumn("#separator");
|
||||
TableSetupColumn("#anyTool");
|
||||
|
||||
TableSetupColumn("#bg1Tool");
|
||||
TableSetupColumn("#bg2Tool");
|
||||
TableSetupColumn("#bg3Tool");
|
||||
TableSetupColumn("#separator");
|
||||
TableSetupColumn("#spriteTool");
|
||||
TableSetupColumn("#itemTool");
|
||||
TableSetupColumn("#doorTool");
|
||||
TableSetupColumn("#blockTool");
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_UNDO)) {
|
||||
PRINT_IF_ERROR(Undo());
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_REDO)) {
|
||||
PRINT_IF_ERROR(Redo());
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::Text(ICON_MD_MORE_VERT);
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_NONE,
|
||||
background_type_ == kBackgroundAny)) {
|
||||
background_type_ = kBackgroundAny;
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_1,
|
||||
background_type_ == kBackground1)) {
|
||||
background_type_ = kBackground1;
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_2,
|
||||
background_type_ == kBackground2)) {
|
||||
background_type_ = kBackground2;
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_FILTER_3,
|
||||
background_type_ == kBackground3)) {
|
||||
background_type_ = kBackground3;
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::Text(ICON_MD_MORE_VERT);
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
|
||||
placement_type_ = kSprite;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Sprites");
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
|
||||
placement_type_ = kItem;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Items");
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
|
||||
placement_type_ = kDoor;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Doors");
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) {
|
||||
placement_type_ = kBlock;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Blocks");
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::Button(ICON_MD_PALETTE)) {
|
||||
palette_showing_ = !palette_showing_;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawRoomSelector() {
|
||||
if (rom()->is_loaded()) {
|
||||
gui::InputHexWord("Room ID", ¤t_room_id_);
|
||||
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)) {
|
||||
int i = 0;
|
||||
for (const auto each_room_name : zelda3::dungeon::kRoomNames) {
|
||||
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||
current_room_id_ == i, "Dungeon Room Names",
|
||||
core::UppercaseHexByte(i), zelda3::dungeon::kRoomNames[i].data());
|
||||
if (ImGui::IsItemClicked()) {
|
||||
// TODO: Jump to tab if room is already open
|
||||
current_room_id_ = i;
|
||||
if (!active_rooms_.contains(i)) {
|
||||
active_rooms_.push_back(i);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawEntranceSelector() {
|
||||
if (rom()->is_loaded()) {
|
||||
gui::InputHexWord("Entrance ID",
|
||||
&entrances_[current_entrance_id_].entrance_id_);
|
||||
|
||||
gui::InputHexWord("Room ID", &entrances_[current_entrance_id_].room_, 50.f,
|
||||
true);
|
||||
ImGui::SameLine();
|
||||
gui::InputHexByte("Dungeon ID",
|
||||
&entrances_[current_entrance_id_].dungeon_id_, 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)) {
|
||||
for (int i = 0; i < 0x85 + 7; i++) {
|
||||
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||
current_entrance_id_ == i, "Dungeon Entrance Names",
|
||||
core::UppercaseHexByte(i),
|
||||
zelda3::dungeon::kEntranceNames[i].data());
|
||||
|
||||
if (ImGui::IsItemClicked()) {
|
||||
current_entrance_id_ = i;
|
||||
if (!active_rooms_.contains(i)) {
|
||||
active_rooms_.push_back(entrances_[i].room_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawDungeonTabView() {
|
||||
static int next_tab_id = 0;
|
||||
|
||||
if (ImGui::BeginTabBar("MyTabBar", kDungeonTabBarFlags)) {
|
||||
if (ImGui::TabItemButton("+", kDungeonTabFlags)) {
|
||||
if (std::find(active_rooms_.begin(), active_rooms_.end(),
|
||||
current_room_id_) != active_rooms_.end()) {
|
||||
// Room is already open
|
||||
next_tab_id++;
|
||||
}
|
||||
active_rooms_.push_back(next_tab_id++); // Add new tab
|
||||
}
|
||||
|
||||
// Submit our regular tabs
|
||||
for (int n = 0; n < active_rooms_.Size;) {
|
||||
bool open = true;
|
||||
|
||||
if (active_rooms_[n] > sizeof(zelda3::dungeon::kRoomNames) / 4) {
|
||||
active_rooms_.erase(active_rooms_.Data + n);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem(
|
||||
zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open,
|
||||
ImGuiTabItemFlags_None)) {
|
||||
DrawDungeonCanvas(active_rooms_[n]);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (!open)
|
||||
active_rooms_.erase(active_rooms_.Data + n);
|
||||
else
|
||||
n++;
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
||||
ImGui::BeginGroup();
|
||||
|
||||
gui::InputHexByte("Layout", &rooms_[room_id].layout);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Blockset", &rooms_[room_id].blockset);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Palette", &rooms_[room_id].palette);
|
||||
|
||||
gui::InputHexByte("Floor1", &rooms_[room_id].floor1);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexByte("Floor2", &rooms_[room_id].floor2);
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::InputHexWord("Message ID", &rooms_[room_id].message_id_);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::EndGroup();
|
||||
|
||||
canvas_.DrawBackground(ImVec2(0x200, 0x200));
|
||||
canvas_.DrawContextMenu();
|
||||
if (is_loaded_) {
|
||||
canvas_.DrawBitmap(rooms_[room_id].layer1(), 0, 0);
|
||||
}
|
||||
canvas_.DrawGrid();
|
||||
canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#undoTool");
|
||||
ImGui::TableSetupColumn("#redoTool");
|
||||
ImGui::TableSetupColumn("#history");
|
||||
ImGui::TableSetupColumn("#separator");
|
||||
ImGui::TableSetupColumn("#bg1Tool");
|
||||
ImGui::TableSetupColumn("#bg2Tool");
|
||||
ImGui::TableSetupColumn("#bg3Tool");
|
||||
ImGui::TableSetupColumn("#itemTool");
|
||||
ImGui::TableSetupColumn("#spriteTool");
|
||||
void DungeonEditor::DrawRoomGraphics() {
|
||||
const auto height = 0x40;
|
||||
room_gfx_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||
room_gfx_canvas_.DrawContextMenu();
|
||||
room_gfx_canvas_.DrawTileSelector(32);
|
||||
if (is_loaded_) {
|
||||
auto blocks = rooms_[current_room_id_].blocks();
|
||||
int current_block = 0;
|
||||
for (int block : blocks) {
|
||||
int offset = height * (current_block + 1);
|
||||
int top_left_y = room_gfx_canvas_.zero_point().y + 2;
|
||||
if (current_block >= 1) {
|
||||
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
|
||||
}
|
||||
room_gfx_canvas_.draw_list()->AddImage(
|
||||
(void*)graphics_bin_[block].get()->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));
|
||||
current_block += 1;
|
||||
}
|
||||
}
|
||||
room_gfx_canvas_.DrawGrid(32.0f);
|
||||
room_gfx_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_UNDO);
|
||||
void DungeonEditor::DrawTileSelector() {
|
||||
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
if (ImGui::BeginTabItem("Room Graphics")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3);
|
||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawRoomGraphics();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_REDO);
|
||||
if (ImGui::BeginTabItem("Object Renderer")) {
|
||||
DrawObjectRenderer();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
||||
void DungeonEditor::DrawObjectRenderer() {
|
||||
if (ImGui::BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Canvas");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(ICON_MD_MORE_VERT);
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_1);
|
||||
int selected_object = 0;
|
||||
int i = 0;
|
||||
for (const auto object_name : zelda3::dungeon::Type1RoomObjectNames) {
|
||||
if (ImGui::Selectable(object_name.data(), selected_object == i)) {
|
||||
selected_object = i;
|
||||
current_object_ = i;
|
||||
object_renderer_.LoadObject(i,
|
||||
rooms_[current_room_id_].mutable_blocks());
|
||||
rom()->RenderBitmap(object_renderer_.bitmap());
|
||||
object_loaded_ = true;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_2);
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_FILTER_3);
|
||||
// Right side of the table - Canvas
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1),
|
||||
true);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_GRASS);
|
||||
object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||
object_canvas_.DrawContextMenu();
|
||||
object_canvas_.DrawTileSelector(32);
|
||||
if (object_loaded_) {
|
||||
object_canvas_.DrawBitmap(*object_renderer_.bitmap(), 0, 0);
|
||||
}
|
||||
object_canvas_.DrawGrid(32.0f);
|
||||
object_canvas_.DrawOverlay();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_MD_PEST_CONTROL_RODENT);
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if (object_loaded_) {
|
||||
ImGui::Begin("Memory Viewer", &object_loaded_, 0);
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawContents((void*)object_renderer_.mutable_memory(),
|
||||
object_renderer_.mutable_memory()->size());
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
// Create a hash map of the usage for elements of each Dungeon Room such as
|
||||
// the blockset, spriteset, palette, etc. This is so we can keep track of
|
||||
// which graphics sets and palette sets are in use and which are not.
|
||||
|
||||
for (const auto& room : rooms_) {
|
||||
// Blockset
|
||||
if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) {
|
||||
blockset_usage_[room.blockset] = 1;
|
||||
} else {
|
||||
blockset_usage_[room.blockset] += 1;
|
||||
}
|
||||
|
||||
// Spriteset
|
||||
if (spriteset_usage_.find(room.spriteset) == spriteset_usage_.end()) {
|
||||
spriteset_usage_[room.spriteset] = 1;
|
||||
} else {
|
||||
spriteset_usage_[room.spriteset] += 1;
|
||||
}
|
||||
|
||||
// Palette
|
||||
if (palette_usage_.find(room.palette) == palette_usage_.end()) {
|
||||
palette_usage_[room.palette] = 1;
|
||||
} else {
|
||||
palette_usage_[room.palette] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonEditor::RenderSetUsage(
|
||||
const absl::flat_hash_map<uint16_t, int>& usage_map, uint16_t& selected_set,
|
||||
int spriteset_offset) {
|
||||
// Sort the usage map by set number
|
||||
std::vector<std::pair<uint16_t, int>> sorted_usage(usage_map.begin(),
|
||||
usage_map.end());
|
||||
std::sort(sorted_usage.begin(), sorted_usage.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
|
||||
for (const auto& [set, count] : sorted_usage) {
|
||||
std::string display_str;
|
||||
if (spriteset_offset != 0x00) {
|
||||
display_str = absl::StrFormat("%#02x, %#02x: %d", set,
|
||||
(set + spriteset_offset), count);
|
||||
} else {
|
||||
display_str =
|
||||
absl::StrFormat("%#02x: %d", (set + spriteset_offset), count);
|
||||
}
|
||||
if (ImGui::Selectable(display_str.c_str(), selected_set == set)) {
|
||||
selected_set = set; // Update the selected set when clicked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Calculate the unused sets in a usage map
|
||||
// Range for blocksets 0-0x24
|
||||
// Range for spritesets 0-0x8F
|
||||
// Range for palettes 0-0x47
|
||||
template <typename T>
|
||||
void RenderUnusedSets(const absl::flat_hash_map<T, int>& usage_map, int max_set,
|
||||
int spriteset_offset = 0x00) {
|
||||
std::vector<int> unused_sets;
|
||||
for (int i = 0; i < max_set; i++) {
|
||||
if (usage_map.find(i) == usage_map.end()) {
|
||||
unused_sets.push_back(i);
|
||||
}
|
||||
}
|
||||
for (const auto& set : unused_sets) {
|
||||
if (spriteset_offset != 0x00) {
|
||||
ImGui::Text("%#02x, %#02x", set, (set + spriteset_offset));
|
||||
} else {
|
||||
ImGui::Text("%#02x", set);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DungeonEditor::DrawUsageStats() {
|
||||
if (ImGui::Button("Refresh")) {
|
||||
selected_blockset_ = 0xFFFF;
|
||||
selected_spriteset_ = 0xFFFF;
|
||||
selected_palette_ = 0xFFFF;
|
||||
spriteset_usage_.clear();
|
||||
blockset_usage_.clear();
|
||||
palette_usage_.clear();
|
||||
CalculateUsageStats();
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
if (ImGui::BeginTable("DungeonUsageStatsTable", 8,
|
||||
kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit,
|
||||
ImGui::GetContentRegionAvail())) {
|
||||
TableSetupColumn("Blockset Usage");
|
||||
TableSetupColumn("Unused Blockset");
|
||||
TableSetupColumn("Palette Usage");
|
||||
TableSetupColumn("Unused Palette");
|
||||
TableSetupColumn("Spriteset Usage");
|
||||
TableSetupColumn("Unused Spriteset");
|
||||
TableSetupColumn("Usage Grid");
|
||||
TableSetupColumn("Group Preview");
|
||||
TableHeadersRow();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderSetUsage(blockset_usage_, selected_blockset_);
|
||||
ImGui::EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderUnusedSets(blockset_usage_, 0x25);
|
||||
ImGui::EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("PaletteUsageScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderSetUsage(palette_usage_, selected_palette_);
|
||||
ImGui::EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderUnusedSets(palette_usage_, 0x48);
|
||||
ImGui::EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
|
||||
ImGui::BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderSetUsage(spriteset_usage_, selected_spriteset_, 0x40);
|
||||
ImGui::EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_HorizontalScrollbar);
|
||||
RenderUnusedSets(spriteset_usage_, 0x90, 0x40);
|
||||
ImGui::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());
|
||||
DrawUsageGrid();
|
||||
ImGui::EndChild();
|
||||
|
||||
TableNextColumn();
|
||||
if (selected_blockset_ < 0x25) {
|
||||
gfx_group_editor_.SetSelectedBlockset(selected_blockset_);
|
||||
gfx_group_editor_.DrawBlocksetViewer(true);
|
||||
} else if (selected_spriteset_ < 0x90) {
|
||||
gfx_group_editor_.SetSelectedSpriteset(selected_spriteset_ + 0x40);
|
||||
gfx_group_editor_.DrawSpritesetViewer(true);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
void DungeonEditor::DrawUsageGrid() {
|
||||
// Create a grid of 295 small squares which is 16 squares wide
|
||||
// Each square represents a room in the game
|
||||
// When you hover a square it should show a hover tooltip with the properties
|
||||
// of the room such as the blockset, spriteset, palette, etc. Calculate the
|
||||
// number of rows
|
||||
int totalSquares = 296;
|
||||
int squaresWide = 16;
|
||||
int squaresTall = (totalSquares + squaresWide - 1) /
|
||||
squaresWide; // Ceiling of totalSquares/squaresWide
|
||||
|
||||
// Loop through each row
|
||||
for (int row = 0; row < squaresTall; ++row) {
|
||||
// Start a new line for each row
|
||||
ImGui::NewLine();
|
||||
|
||||
// Loop through each column in the row
|
||||
for (int col = 0; col < squaresWide; ++col) {
|
||||
// Check if we have reached 295 squares
|
||||
if (row * squaresWide + col >= totalSquares) {
|
||||
break;
|
||||
}
|
||||
// Determine if this square should be highlighted
|
||||
const auto& room = rooms_[row * squaresWide + col];
|
||||
|
||||
// Create a button or selectable for each square
|
||||
ImGui::BeginGroup();
|
||||
ImVec4 color = room_palette_[room.palette];
|
||||
color.x = color.x / 255;
|
||||
color.y = color.y / 255;
|
||||
color.z = color.z / 255;
|
||||
color.w = 1.0f;
|
||||
if (rooms_[row * squaresWide + col].room_size() > 0xFFFF) {
|
||||
color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
|
||||
}
|
||||
if (rooms_[row * squaresWide + col].room_size() == 0) {
|
||||
color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, color);
|
||||
// Make the button text darker
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
bool highlight = room.blockset == selected_blockset_ ||
|
||||
room.spriteset == selected_spriteset_ ||
|
||||
room.palette == selected_palette_;
|
||||
|
||||
// Set highlight color if needed
|
||||
if (highlight) {
|
||||
ImGui::PushStyleColor(
|
||||
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))) {
|
||||
// Switch over to the room editor tab
|
||||
// and add a room tab by the ID of the square
|
||||
// that was clicked
|
||||
}
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
ImGui::OpenPopup(
|
||||
absl::StrFormat("RoomContextMenu%d", row * squaresWide + col)
|
||||
.c_str());
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndGroup();
|
||||
|
||||
// Reset style if it was highlighted
|
||||
if (highlight) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// Check if the square is hovered
|
||||
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());
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
// Keep squares in the same line
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -3,22 +3,133 @@
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/core/labeling.h"
|
||||
#include "app/editor/modules/gfx_group_editor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_entrance.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
class DungeonEditor {
|
||||
|
||||
constexpr ImGuiTabItemFlags kDungeonTabFlags =
|
||||
ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip;
|
||||
|
||||
constexpr ImGuiTabBarFlags kDungeonTabBarFlags =
|
||||
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||
ImGuiTabBarFlags_TabListPopupButton;
|
||||
|
||||
constexpr ImGuiTableFlags kDungeonTableFlags =
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersV;
|
||||
|
||||
class DungeonEditor : public Editor,
|
||||
public SharedROM,
|
||||
public core::ExperimentFlags {
|
||||
public:
|
||||
void Update();
|
||||
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(); }
|
||||
|
||||
void add_room(int i) { active_rooms_.push_back(i); }
|
||||
|
||||
private:
|
||||
void LoadDungeonRoomSize();
|
||||
|
||||
void UpdateDungeonRoomView();
|
||||
|
||||
void DrawToolset();
|
||||
void DrawRoomSelector();
|
||||
void DrawEntranceSelector();
|
||||
|
||||
void DrawDungeonTabView();
|
||||
void DrawDungeonCanvas(int room_id);
|
||||
|
||||
void DrawRoomGraphics();
|
||||
void DrawTileSelector();
|
||||
void DrawObjectRenderer();
|
||||
|
||||
void LoadRoomEntrances();
|
||||
|
||||
void CalculateUsageStats();
|
||||
void DrawUsageStats();
|
||||
void DrawUsageGrid();
|
||||
void RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
|
||||
uint16_t& selected_set, int spriteset_offset = 0x00);
|
||||
|
||||
enum BackgroundType {
|
||||
kNoBackground,
|
||||
kBackground1,
|
||||
kBackground2,
|
||||
kBackground3,
|
||||
kBackgroundAny,
|
||||
};
|
||||
enum PlacementType { kNoType, kSprite, kItem, kDoor, kBlock };
|
||||
|
||||
int background_type_ = kNoBackground;
|
||||
int placement_type_ = kNoType;
|
||||
int current_object_ = 0;
|
||||
|
||||
bool is_loaded_ = false;
|
||||
bool object_loaded_ = false;
|
||||
bool palette_showing_ = false;
|
||||
bool refresh_graphics_ = false;
|
||||
bool show_object_render_ = false;
|
||||
|
||||
uint16_t current_entrance_id_ = 0;
|
||||
uint16_t current_room_id_ = 0;
|
||||
uint64_t current_palette_id_ = 0;
|
||||
uint64_t current_palette_group_id_ = 0;
|
||||
|
||||
ImVector<int> active_rooms_;
|
||||
|
||||
GfxGroupEditor gfx_group_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
gfx::SnesPalette current_palette_;
|
||||
gfx::SnesPalette full_palette_;
|
||||
gfx::PaletteGroup current_palette_group_;
|
||||
|
||||
gui::Canvas canvas_;
|
||||
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
||||
gui::Canvas room_gfx_canvas_;
|
||||
gui::Canvas object_canvas_;
|
||||
|
||||
gfx::Bitmap room_gfx_bmp_;
|
||||
gfx::BitmapManager graphics_bin_;
|
||||
|
||||
std::vector<gfx::Bitmap*> room_gfx_sheets_;
|
||||
std::vector<zelda3::dungeon::Room> rooms_;
|
||||
std::vector<zelda3::dungeon::RoomEntrance> entrances_;
|
||||
std::vector<gfx::BitmapManager> room_graphics_;
|
||||
zelda3::dungeon::DungeonObjectRenderer object_renderer_;
|
||||
|
||||
absl::flat_hash_map<uint16_t, int> spriteset_usage_;
|
||||
absl::flat_hash_map<uint16_t, int> blockset_usage_;
|
||||
absl::flat_hash_map<uint16_t, int> palette_usage_;
|
||||
|
||||
std::vector<int64_t> room_size_pointers_;
|
||||
|
||||
uint16_t selected_blockset_ = 0xFFFF; // 0xFFFF indicates no selection
|
||||
uint16_t selected_spriteset_ = 0xFFFF;
|
||||
uint16_t selected_palette_ = 0xFFFF;
|
||||
|
||||
uint64_t total_room_size_ = 0;
|
||||
|
||||
std::unordered_map<int, int> room_size_addresses_;
|
||||
std::unordered_map<int, ImVec4> room_palette_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
788
src/app/editor/graphics_editor.cc
Normal file
788
src/app/editor/graphics_editor.cc
Normal file
@@ -0,0 +1,788 @@
|
||||
#include "app/editor/graphics_editor.h"
|
||||
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/platform/clipboard.h"
|
||||
#include "app/editor/modules/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/canvas.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/gui/style.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using ImGui::Button;
|
||||
using ImGui::InputInt;
|
||||
using ImGui::InputText;
|
||||
using ImGui::SameLine;
|
||||
using ImGui::TableNextColumn;
|
||||
|
||||
constexpr ImGuiTableFlags kGfxEditTableFlags =
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
|
||||
ImGuiTableFlags_SizingFixedFit;
|
||||
|
||||
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
|
||||
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||
ImGuiTabBarFlags_TabListPopupButton;
|
||||
|
||||
absl::Status GraphicsEditor::Update() {
|
||||
TAB_BAR("##TabBar")
|
||||
status_ = UpdateGfxEdit();
|
||||
status_ = UpdateScadView();
|
||||
status_ = UpdateLinkGfxView();
|
||||
END_TAB_BAR()
|
||||
CLEAR_AND_RETURN_STATUS(status_)
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateGfxEdit() {
|
||||
TAB_ITEM("Graphics Editor")
|
||||
|
||||
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
|
||||
ImVec2(0, 0))) {
|
||||
for (const auto& name : kGfxEditColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
NEXT_COLUMN();
|
||||
status_ = UpdateGfxSheetList();
|
||||
|
||||
NEXT_COLUMN();
|
||||
if (rom()->is_loaded()) {
|
||||
DrawGfxEditToolset();
|
||||
status_ = UpdateGfxTabView();
|
||||
}
|
||||
|
||||
NEXT_COLUMN();
|
||||
if (rom()->is_loaded()) {
|
||||
status_ = UpdatePaletteColumn();
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
|
||||
END_TAB_ITEM()
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void GraphicsEditor::DrawGfxEditToolset() {
|
||||
if (ImGui::BeginTable("##GfxEditToolset", 9, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
for (const auto& name :
|
||||
{"Select", "Pencil", "Fill", "Copy Sheet", "Paste Sheet", "Zoom Out",
|
||||
"Zoom In", "Current Color", "Tile Size"})
|
||||
ImGui::TableSetupColumn(name);
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_SELECT_ALL)) {
|
||||
gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_DRAW)) {
|
||||
gfx_edit_mode_ = GfxEditMode::kPencil;
|
||||
}
|
||||
HOVER_HINT("Draw with current color");
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
|
||||
gfx_edit_mode_ = GfxEditMode::kFill;
|
||||
}
|
||||
HOVER_HINT("Fill with current color");
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_CONTENT_COPY)) {
|
||||
std::vector<uint8_t> png_data =
|
||||
rom()->bitmap_manager().shared_bitmap(current_sheet_)->GetPngData();
|
||||
CopyImageToClipboard(png_data);
|
||||
}
|
||||
HOVER_HINT("Copy to Clipboard");
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_CONTENT_PASTE)) {
|
||||
std::vector<uint8_t> png_data;
|
||||
int width, height;
|
||||
GetImageFromClipboard(png_data, width, height);
|
||||
if (png_data.size() > 0) {
|
||||
rom()
|
||||
->mutable_bitmap_manager()
|
||||
->mutable_bitmap(current_sheet_)
|
||||
->Create(width, height, 8, png_data);
|
||||
rom()->UpdateBitmap(rom()
|
||||
->mutable_bitmap_manager()
|
||||
->mutable_bitmap(current_sheet_)
|
||||
.get());
|
||||
}
|
||||
}
|
||||
HOVER_HINT("Paste from Clipboard");
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_ZOOM_OUT)) {
|
||||
if (current_scale_ >= 0.0f) {
|
||||
current_scale_ -= 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_ZOOM_IN)) {
|
||||
if (current_scale_ <= 16.0f) {
|
||||
current_scale_ += 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
auto bitmap = rom()->bitmap_manager()[current_sheet_];
|
||||
auto palette = bitmap->palette();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
ImGui::SameLine();
|
||||
auto color =
|
||||
ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
|
||||
palette[i].rgb().z / 255.0f, 255.0f);
|
||||
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
|
||||
color)) {
|
||||
current_color_ = color;
|
||||
}
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
gui::InputHexByte("Tile Size", &tile_size_, 0x01);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateGfxSheetList() {
|
||||
ImGui::BeginChild(
|
||||
"##GfxEditChild", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
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();
|
||||
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_,
|
||||
graphics_bin_canvas_.zero_point().y +
|
||||
value.get()->height() * sheet_scale_));
|
||||
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||
current_sheet_ = key;
|
||||
open_sheets_.insert(key);
|
||||
}
|
||||
|
||||
// Add a slightly transparent rectangle behind the text
|
||||
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
|
||||
graphics_bin_canvas_.zero_point().y + 2);
|
||||
ImVec2 text_size =
|
||||
ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str());
|
||||
ImVec2 rent_min(text_pos.x, text_pos.y);
|
||||
ImVec2 rent_max(text_pos.x + text_size.x, text_pos.y + text_size.y);
|
||||
|
||||
graphics_bin_canvas_.draw_list()->AddRectFilled(rent_min, rent_max,
|
||||
IM_COL32(0, 125, 0, 128));
|
||||
|
||||
graphics_bin_canvas_.draw_list()->AddText(
|
||||
text_pos, IM_COL32(125, 255, 125, 255),
|
||||
absl::StrFormat("%02X", key).c_str());
|
||||
}
|
||||
graphics_bin_canvas_.DrawGrid(16.0f);
|
||||
graphics_bin_canvas_.DrawOverlay();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndChild();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateGfxTabView() {
|
||||
static int next_tab_id = 0;
|
||||
|
||||
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
|
||||
if (ImGui::TabItemButton(
|
||||
"+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
|
||||
open_sheets_.insert(next_tab_id++);
|
||||
}
|
||||
|
||||
for (auto& sheet_id : open_sheets_) {
|
||||
bool open = true;
|
||||
if (ImGui::BeginTabItem(absl::StrFormat("%d", sheet_id).c_str(), &open,
|
||||
ImGuiTabItemFlags_None)) {
|
||||
current_sheet_ = sheet_id;
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
release_queue_.push(sheet_id);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
release_queue_.push(sheet_id);
|
||||
child_window_sheets_.insert(sheet_id);
|
||||
}
|
||||
}
|
||||
|
||||
const auto child_id =
|
||||
absl::StrFormat("##GfxEditPaletteChildWindow%d", sheet_id);
|
||||
ImGui::BeginChild(child_id.c_str(), ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
||||
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
||||
|
||||
gfx::Bitmap& current_bitmap =
|
||||
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
|
||||
|
||||
auto draw_tile_event = [&]() {
|
||||
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, ¤t_bitmap,
|
||||
current_color_);
|
||||
rom()->UpdateBitmap(¤t_bitmap, true);
|
||||
};
|
||||
|
||||
current_sheet_canvas_.UpdateColorPainter(
|
||||
*rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
|
||||
tile_size_, current_scale_);
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (!open) release_queue_.push(sheet_id);
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
// Release any tabs that were closed
|
||||
while (!release_queue_.empty()) {
|
||||
auto sheet_id = release_queue_.top();
|
||||
open_sheets_.erase(sheet_id);
|
||||
release_queue_.pop();
|
||||
}
|
||||
|
||||
// Draw any child windows that were created
|
||||
if (!child_window_sheets_.empty()) {
|
||||
int id_to_release = -1;
|
||||
for (const auto& id : child_window_sheets_) {
|
||||
bool active = true;
|
||||
ImGui::SetNextWindowPos(ImGui::GetIO().MousePos, ImGuiCond_Once);
|
||||
ImGui::SetNextWindowSize(ImVec2(0x100 + 1 * 16, 0x40 + 1 * 16),
|
||||
ImGuiCond_Once);
|
||||
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
|
||||
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
|
||||
current_sheet_ = id;
|
||||
// ImVec2(0x100, 0x40),
|
||||
current_sheet_canvas_.UpdateColorPainter(
|
||||
*rom()->bitmap_manager()[id], current_color_,
|
||||
[&]() {
|
||||
|
||||
},
|
||||
tile_size_, current_scale_);
|
||||
ImGui::End();
|
||||
|
||||
if (active == false) {
|
||||
id_to_release = id;
|
||||
}
|
||||
}
|
||||
if (id_to_release != -1) {
|
||||
child_window_sheets_.erase(id_to_release);
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdatePaletteColumn() {
|
||||
auto palette_group = rom()->palette_group(
|
||||
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
|
||||
|
||||
auto palette = palette_group[edit_palette_index_];
|
||||
|
||||
if (rom()->is_loaded()) {
|
||||
gui::TextWithSeparators("ROM Palette");
|
||||
ImGui::SetNextItemWidth(100.f);
|
||||
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
|
||||
kPaletteGroupAddressesKeys,
|
||||
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_) {
|
||||
rom()->bitmap_manager()[current_sheet_]->ApplyPaletteWithTransparent(
|
||||
palette, edit_palette_sub_index_);
|
||||
rom()->UpdateBitmap(
|
||||
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get());
|
||||
refresh_graphics_ = false;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateLinkGfxView() {
|
||||
TAB_ITEM("Player Animations")
|
||||
|
||||
const auto link_gfx_offset = 0x80000;
|
||||
const auto link_gfx_length = 0x7000;
|
||||
|
||||
// TODO: Finish Rom::LoadLinkGraphics and implement this
|
||||
if (ImGui::Button("Load Link Graphics (Experimental)")) {
|
||||
if (rom()->is_loaded()) {
|
||||
// Load Links graphics from the ROM
|
||||
rom()->LoadLinkGraphics();
|
||||
|
||||
// Split it into the pose data frames
|
||||
// Create an animation step display for the poses
|
||||
// Allow the user to modify the frames used in an anim step
|
||||
// LinkOAM_AnimationSteps:
|
||||
// #_0D85FB
|
||||
}
|
||||
}
|
||||
|
||||
END_TAB_ITEM()
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::UpdateScadView() {
|
||||
TAB_ITEM("Prototype")
|
||||
|
||||
RETURN_IF_ERROR(DrawToolset())
|
||||
|
||||
if (open_memory_editor_) {
|
||||
ImGui::Begin("Memory Editor", &open_memory_editor_);
|
||||
RETURN_IF_ERROR(DrawMemoryEditor())
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags)
|
||||
SETUP_COLUMN("File Import (BIN, CGX, ROM)")
|
||||
SETUP_COLUMN("Palette (COL)")
|
||||
ImGui::TableSetupColumn("Tilemaps and Objects (SCR, PNL, OBJ)",
|
||||
ImGuiTableColumnFlags_WidthFixed);
|
||||
SETUP_COLUMN("Graphics Preview")
|
||||
TABLE_HEADERS()
|
||||
NEXT_COLUMN() {
|
||||
status_ = DrawCgxImport();
|
||||
status_ = DrawClipboardImport();
|
||||
status_ = DrawFileImport();
|
||||
status_ = DrawExperimentalFeatures();
|
||||
}
|
||||
|
||||
NEXT_COLUMN() { status_ = DrawPaletteControls(); }
|
||||
|
||||
NEXT_COLUMN()
|
||||
gui::BitmapCanvasPipeline(scr_canvas_, scr_bitmap_, 0x200, 0x200, 0x20,
|
||||
scr_loaded_, false, 0);
|
||||
status_ = DrawScrImport();
|
||||
|
||||
NEXT_COLUMN()
|
||||
if (super_donkey_) {
|
||||
if (refresh_graphics_) {
|
||||
for (int i = 0; i < graphics_bin_.size(); i++) {
|
||||
graphics_bin_[i].ApplyPalette(
|
||||
col_file_palette_group_[current_palette_index_]);
|
||||
rom()->UpdateBitmap(&graphics_bin_[i]);
|
||||
}
|
||||
refresh_graphics_ = false;
|
||||
}
|
||||
// Load the full graphics space from `super_donkey_1.bin`
|
||||
gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
|
||||
super_donkey_, graphics_bin_);
|
||||
} else if (cgx_loaded_ && col_file_) {
|
||||
// Load the CGX graphics
|
||||
gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20,
|
||||
cgx_loaded_, true, 5);
|
||||
} else {
|
||||
// Load the BIN/Clipboard Graphics
|
||||
gui::BitmapCanvasPipeline(import_canvas_, bin_bitmap_, 0x100, 16384, 0x20,
|
||||
gfx_loaded_, true, 2);
|
||||
}
|
||||
END_TABLE()
|
||||
|
||||
END_TAB_ITEM()
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawToolset() {
|
||||
if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
for (const auto& name : kGfxToolsetColumnNames)
|
||||
ImGui::TableSetupColumn(name.data());
|
||||
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_MEMORY)) {
|
||||
if (!open_memory_editor_) {
|
||||
open_memory_editor_ = true;
|
||||
} else {
|
||||
open_memory_editor_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
TEXT_COLUMN("Open Memory Editor") // Separator
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawCgxImport() {
|
||||
gui::TextWithSeparators("Cgx Import");
|
||||
InputInt("BPP", ¤t_bpp_);
|
||||
|
||||
InputText("##CGXFile", cgx_file_name_, sizeof(cgx_file_name_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline("ImportCgxKey", ".CGX,.cgx\0", "Open CGX", [this]() {
|
||||
strncpy(cgx_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(cgx_file_path_));
|
||||
strncpy(cgx_file_name_,
|
||||
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
|
||||
sizeof(cgx_file_name_));
|
||||
is_open_ = true;
|
||||
cgx_loaded_ = true;
|
||||
});
|
||||
gui::ButtonPipe("Copy CGX Path",
|
||||
[this]() { ImGui::SetClipboardText(cgx_file_path_); });
|
||||
|
||||
gui::ButtonPipe("Load CGX Data", [this]() {
|
||||
status_ = gfx::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
|
||||
decoded_cgx_, extra_cgx_data_);
|
||||
|
||||
cgx_bitmap_.InitializeFromData(0x80, 0x200, 8, decoded_cgx_);
|
||||
if (col_file_) {
|
||||
cgx_bitmap_.ApplyPalette(decoded_col_);
|
||||
rom()->RenderBitmap(&cgx_bitmap_);
|
||||
}
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawScrImport() {
|
||||
InputText("##ScrFile", scr_file_name_, sizeof(scr_file_name_));
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportScrKey", ".SCR,.scr,.BAK\0", "Open SCR", [this]() {
|
||||
strncpy(scr_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(scr_file_path_));
|
||||
strncpy(scr_file_name_,
|
||||
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
|
||||
sizeof(scr_file_name_));
|
||||
is_open_ = true;
|
||||
scr_loaded_ = true;
|
||||
});
|
||||
|
||||
InputInt("SCR Mod", &scr_mod_value_);
|
||||
|
||||
gui::ButtonPipe("Load Scr Data", [this]() {
|
||||
status_ = gfx::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
|
||||
|
||||
decoded_scr_data_.resize(0x100 * 0x100);
|
||||
status_ = gfx::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_,
|
||||
decoded_cgx_);
|
||||
|
||||
scr_bitmap_.InitializeFromData(0x100, 0x100, 8, decoded_scr_data_);
|
||||
if (scr_loaded_) {
|
||||
scr_bitmap_.ApplyPalette(decoded_col_);
|
||||
rom()->RenderBitmap(&scr_bitmap_);
|
||||
}
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawPaletteControls() {
|
||||
gui::TextWithSeparators("COL Import");
|
||||
InputText("##ColFile", col_file_name_, sizeof(col_file_name_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportColKey", ".COL,.col,.BAK,.bak\0", "Open COL", [this]() {
|
||||
strncpy(col_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(col_file_path_));
|
||||
strncpy(col_file_name_,
|
||||
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
|
||||
sizeof(col_file_name_));
|
||||
status_ = temp_rom_.LoadFromFile(col_file_path_,
|
||||
/*z3_load=*/false);
|
||||
auto col_data_ = gfx::GetColFileData(temp_rom_.data());
|
||||
if (col_file_palette_group_.size() != 0) {
|
||||
col_file_palette_group_.Clear();
|
||||
}
|
||||
auto col_file_palette_group_status =
|
||||
gfx::CreatePaletteGroupFromColFile(col_data_);
|
||||
if (col_file_palette_group_status.ok()) {
|
||||
col_file_palette_group_ = col_file_palette_group_status.value();
|
||||
}
|
||||
col_file_palette_ = gfx::SnesPalette(col_data_);
|
||||
|
||||
// gigaleak dev format based code
|
||||
decoded_col_ = gfx::DecodeColFile(col_file_path_);
|
||||
col_file_ = true;
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
gui::ButtonPipe("Copy COL Path",
|
||||
[this]() { ImGui::SetClipboardText(col_file_path_); });
|
||||
|
||||
if (rom()->is_loaded()) {
|
||||
gui::TextWithSeparators("ROM Palette");
|
||||
gui::InputHex("Palette Index", ¤t_palette_index_);
|
||||
ImGui::Combo("Palette", ¤t_palette_, kPaletteGroupAddressesKeys,
|
||||
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
||||
}
|
||||
|
||||
if (col_file_palette_.size() != 0) {
|
||||
gui::SelectablePalettePipeline(current_palette_index_, refresh_graphics_,
|
||||
col_file_palette_);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawObjImport() {
|
||||
gui::TextWithSeparators("OBJ Import");
|
||||
|
||||
InputText("##ObjFile", obj_file_path_, sizeof(obj_file_path_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportObjKey", ".obj,.OBJ,.bak,.BAK\0", "Open OBJ", [this]() {
|
||||
strncpy(file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(file_path_));
|
||||
status_ = temp_rom_.LoadFromFile(file_path_);
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawTilemapImport() {
|
||||
gui::TextWithSeparators("Tilemap Import");
|
||||
|
||||
InputText("##TMapFile", tilemap_file_path_, sizeof(tilemap_file_path_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline(
|
||||
"ImportTilemapKey", ".DAT,.dat,.BIN,.bin,.hex,.HEX\0", "Open Tilemap",
|
||||
[this]() {
|
||||
strncpy(tilemap_file_path_,
|
||||
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(tilemap_file_path_));
|
||||
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
|
||||
|
||||
// Extract the high and low bytes from the file.
|
||||
auto decomp_sheet = gfx::lc_lz2::DecompressV2(
|
||||
tilemap_rom_.data(), gfx::lc_lz2::kNintendoMode1);
|
||||
tilemap_loaded_ = true;
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawFileImport() {
|
||||
gui::TextWithSeparators("BIN Import");
|
||||
|
||||
InputText("##ROMFile", file_path_, sizeof(file_path_));
|
||||
SameLine();
|
||||
|
||||
gui::FileDialogPipeline("ImportDlgKey", ".bin,.hex\0", "Open BIN", [this]() {
|
||||
strncpy(file_path_, ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
|
||||
sizeof(file_path_));
|
||||
status_ = temp_rom_.LoadFromFile(file_path_);
|
||||
is_open_ = true;
|
||||
});
|
||||
|
||||
gui::ButtonPipe("Copy File Path",
|
||||
[this]() { ImGui::SetClipboardText(file_path_); });
|
||||
|
||||
gui::InputHex("BIN Offset", ¤t_offset_);
|
||||
gui::InputHex("BIN Size", &bin_size_);
|
||||
|
||||
if (Button("Decompress BIN")) {
|
||||
if (strlen(file_path_) > 0) {
|
||||
RETURN_IF_ERROR(DecompressImportData(bin_size_))
|
||||
} else {
|
||||
return absl::InvalidArgumentError(
|
||||
"Please select a file before importing.");
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawClipboardImport() {
|
||||
gui::TextWithSeparators("Clipboard Import");
|
||||
gui::ButtonPipe("Paste from Clipboard", [this]() {
|
||||
const char* text = ImGui::GetClipboardText();
|
||||
if (text) {
|
||||
const auto clipboard_data = Bytes(text, text + strlen(text));
|
||||
ImGui::MemFree((void*)text);
|
||||
status_ = temp_rom_.LoadFromBytes(clipboard_data);
|
||||
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 (temp_rom_.is_loaded()) {
|
||||
status_ = DecompressImportData(0x40000);
|
||||
} else {
|
||||
status_ = absl::InvalidArgumentError(
|
||||
"Please paste data into the clipboard before "
|
||||
"decompressing.");
|
||||
}
|
||||
});
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawExperimentalFeatures() {
|
||||
gui::TextWithSeparators("Experimental");
|
||||
if (Button("Decompress Super Donkey Full")) {
|
||||
if (strlen(file_path_) > 0) {
|
||||
RETURN_IF_ERROR(DecompressSuperDonkey())
|
||||
} else {
|
||||
return absl::InvalidArgumentError(
|
||||
"Please select `super_donkey_1.bin` before "
|
||||
"importing.");
|
||||
}
|
||||
}
|
||||
ImGui::SetItemTooltip(
|
||||
"Requires `super_donkey_1.bin` to be imported under the "
|
||||
"BIN import section.");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DrawMemoryEditor() {
|
||||
std::string title = "Memory Editor";
|
||||
if (is_open_) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow(title.c_str(), temp_rom_.data(), temp_rom_.size());
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DecompressImportData(int size) {
|
||||
ASSIGN_OR_RETURN(import_data_, gfx::lc_lz2::DecompressV2(
|
||||
temp_rom_.data(), current_offset_, size))
|
||||
|
||||
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
|
||||
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
|
||||
converted_sheet);
|
||||
|
||||
if (rom()->is_loaded()) {
|
||||
auto palette_group = rom()->palette_group("ow_main");
|
||||
z3_rom_palette_ = palette_group[current_palette_];
|
||||
if (col_file_) {
|
||||
bin_bitmap_.ApplyPalette(col_file_palette_);
|
||||
} else {
|
||||
bin_bitmap_.ApplyPalette(z3_rom_palette_);
|
||||
}
|
||||
}
|
||||
|
||||
rom()->RenderBitmap(&bin_bitmap_);
|
||||
gfx_loaded_ = true;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status GraphicsEditor::DecompressSuperDonkey() {
|
||||
int i = 0;
|
||||
for (const auto& offset : kSuperDonkeyTiles) {
|
||||
int offset_value =
|
||||
std::stoi(offset, nullptr, 16); // convert hex string to int
|
||||
ASSIGN_OR_RETURN(
|
||||
auto decompressed_data,
|
||||
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
|
||||
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
||||
graphics_bin_[i] =
|
||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||
core::kTilesheetDepth, converted_sheet);
|
||||
if (col_file_) {
|
||||
graphics_bin_[i].ApplyPalette(
|
||||
col_file_palette_group_[current_palette_index_]);
|
||||
} else {
|
||||
// ROM palette
|
||||
auto palette_group =
|
||||
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
|
||||
z3_rom_palette_ = palette_group[current_palette_index_];
|
||||
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
||||
}
|
||||
|
||||
rom()->RenderBitmap(&graphics_bin_[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
for (const auto& offset : kSuperDonkeySprites) {
|
||||
int offset_value =
|
||||
std::stoi(offset, nullptr, 16); // convert hex string to int
|
||||
ASSIGN_OR_RETURN(
|
||||
auto decompressed_data,
|
||||
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
|
||||
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
||||
graphics_bin_[i] =
|
||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||
core::kTilesheetDepth, converted_sheet);
|
||||
if (col_file_) {
|
||||
graphics_bin_[i].ApplyPalette(
|
||||
col_file_palette_group_[current_palette_index_]);
|
||||
} else {
|
||||
// ROM palette
|
||||
auto palette_group =
|
||||
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
|
||||
z3_rom_palette_ = palette_group[current_palette_index_];
|
||||
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
||||
}
|
||||
|
||||
rom()->RenderBitmap(&graphics_bin_[i]);
|
||||
i++;
|
||||
}
|
||||
super_donkey_ = true;
|
||||
num_sheets_to_load_ = i;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
194
src/app/editor/graphics_editor.h
Normal file
194
src/app/editor/graphics_editor.h
Normal file
@@ -0,0 +1,194 @@
|
||||
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/editor/modules/palette_editor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
// "99973","A3D80",
|
||||
|
||||
const std::string kSuperDonkeyTiles[] = {
|
||||
"97C05", "98219", "9871E", "98C00", "99084", "995AF", "99DE0", "9A27E",
|
||||
"9A741", "9AC31", "9B07E", "9B55C", "9B963", "9BB99", "9C009", "9C4B4",
|
||||
"9C92B", "9CDD6", "9D2C2", "9E037", "9E527", "9EA56", "9EF65", "9FCD1",
|
||||
"A0193", "A059E", "A0B17", "A0FB6", "A14A5", "A1988", "A1E66", "A232B",
|
||||
"A27F0", "A2B6E", "A302C", "A3453", "A38CA", "A42BB", "A470C", "A4BA9",
|
||||
"A5089", "A5385", "A5742", "A5BCC", "A6017", "A6361", "A66F8"};
|
||||
|
||||
const std::string kSuperDonkeySprites[] = {
|
||||
"A8E5D", "A9435", "A9934", "A9D83", "AA2F1", "AA6D4", "AABE4", "AB127",
|
||||
"AB65A", "ABBDD", "AC38D", "AC797", "ACCC8", "AD0AE", "AD245", "AD554",
|
||||
"ADAAC", "ADECC", "AE453", "AE9D2", "AEF40", "AF3C9", "AF92E", "AFE9D",
|
||||
"B03D2", "B09AC", "B0F0C", "B1430", "B1859", "B1E01", "B229A", "B2854",
|
||||
"B2D27", "B31D7", "B3B58", "B40B5", "B45A5", "B4D64", "B5031", "B555F",
|
||||
"B5F30", "B6858", "B70DD", "B7526", "B79EC", "B7C83", "B80F7", "B85CC",
|
||||
"B8A3F", "B8F97", "B94F2", "B9A20", "B9E9A", "BA3A2", "BA8F6", "BACDC",
|
||||
"BB1F9", "BB781", "BBCCA", "BC26D", "BC7D4", "BCBB0", "BD082", "BD5FC",
|
||||
"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 std::string_view kGfxEditColumnNames[] = {
|
||||
"Tilesheets", "Current Graphics", "Palette Controls"};
|
||||
|
||||
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
|
||||
"#memoryEditor",
|
||||
"##separator_gfx1",
|
||||
};
|
||||
|
||||
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_SizingStretchSame;
|
||||
|
||||
class GraphicsEditor : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
private:
|
||||
enum class GfxEditMode {
|
||||
kSelect,
|
||||
kPencil,
|
||||
kFill,
|
||||
};
|
||||
|
||||
// Graphics Editor Tab
|
||||
absl::Status UpdateGfxEdit();
|
||||
absl::Status UpdateGfxSheetList();
|
||||
absl::Status UpdateGfxTabView();
|
||||
absl::Status UpdatePaletteColumn();
|
||||
void DrawGfxEditToolset();
|
||||
|
||||
// Link Graphics Edit Tab
|
||||
absl::Status UpdateLinkGfxView();
|
||||
|
||||
// Prototype Graphics Viewer
|
||||
absl::Status UpdateScadView();
|
||||
|
||||
// Import Functions
|
||||
absl::Status DrawCgxImport();
|
||||
absl::Status DrawScrImport();
|
||||
absl::Status DrawFileImport();
|
||||
absl::Status DrawObjImport();
|
||||
absl::Status DrawTilemapImport();
|
||||
|
||||
// Other Functions
|
||||
absl::Status DrawToolset();
|
||||
absl::Status DrawPaletteControls();
|
||||
absl::Status DrawClipboardImport();
|
||||
absl::Status DrawExperimentalFeatures();
|
||||
absl::Status DrawMemoryEditor();
|
||||
|
||||
absl::Status DecompressImportData(int size);
|
||||
absl::Status DecompressSuperDonkey();
|
||||
|
||||
// Member Variables
|
||||
ImVec4 current_color_;
|
||||
uint16_t current_sheet_ = 0;
|
||||
uint8_t tile_size_ = 0x01;
|
||||
std::set<uint16_t> open_sheets_;
|
||||
std::set<uint16_t> child_window_sheets_;
|
||||
std::stack<uint16_t> release_queue_;
|
||||
uint64_t edit_palette_group_name_index_ = 0;
|
||||
uint64_t edit_palette_group_index_ = 0;
|
||||
uint64_t edit_palette_index_ = 0;
|
||||
uint64_t edit_palette_sub_index_ = 0;
|
||||
float sheet_scale_ = 2.0f;
|
||||
float current_scale_ = 4.0f;
|
||||
|
||||
// Prototype Graphics Viewer
|
||||
int current_palette_ = 0;
|
||||
uint64_t current_offset_ = 0;
|
||||
uint64_t current_size_ = 0;
|
||||
uint64_t current_palette_index_ = 0;
|
||||
int current_bpp_ = 0;
|
||||
int scr_mod_value_ = 0;
|
||||
|
||||
uint64_t num_sheets_to_load_ = 1;
|
||||
uint64_t bin_size_ = 0;
|
||||
uint64_t clipboard_offset_ = 0;
|
||||
uint64_t clipboard_size_ = 0;
|
||||
|
||||
bool refresh_graphics_ = false;
|
||||
bool open_memory_editor_ = false;
|
||||
bool gfx_loaded_ = false;
|
||||
bool is_open_ = false;
|
||||
bool super_donkey_ = false;
|
||||
bool col_file_ = false;
|
||||
bool cgx_loaded_ = false;
|
||||
bool scr_loaded_ = false;
|
||||
bool obj_loaded_ = false;
|
||||
bool tilemap_loaded_ = false;
|
||||
|
||||
char file_path_[256] = "";
|
||||
char col_file_path_[256] = "";
|
||||
char col_file_name_[256] = "";
|
||||
char cgx_file_path_[256] = "";
|
||||
char cgx_file_name_[256] = "";
|
||||
char scr_file_path_[256] = "";
|
||||
char scr_file_name_[256] = "";
|
||||
char obj_file_path_[256] = "";
|
||||
char tilemap_file_path_[256] = "";
|
||||
char tilemap_file_name_[256] = "";
|
||||
|
||||
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||
|
||||
ROM temp_rom_;
|
||||
ROM tilemap_rom_;
|
||||
zelda3::Overworld overworld_;
|
||||
MemoryEditor cgx_memory_editor_;
|
||||
MemoryEditor col_memory_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
Bytes import_data_;
|
||||
Bytes graphics_buffer_;
|
||||
std::vector<uint8_t> decoded_cgx_;
|
||||
std::vector<uint8_t> cgx_data_;
|
||||
std::vector<uint8_t> extra_cgx_data_;
|
||||
std::vector<SDL_Color> decoded_col_;
|
||||
std::vector<uint8_t> scr_data_;
|
||||
std::vector<uint8_t> decoded_scr_data_;
|
||||
gfx::Bitmap cgx_bitmap_;
|
||||
gfx::Bitmap scr_bitmap_;
|
||||
gfx::Bitmap bin_bitmap_;
|
||||
gfx::Bitmap link_full_sheet_;
|
||||
gfx::BitmapTable graphics_bin_;
|
||||
gfx::BitmapTable clipboard_graphics_bin_;
|
||||
gfx::BitmapTable link_graphics_;
|
||||
gfx::PaletteGroup col_file_palette_group_;
|
||||
gfx::SnesPalette z3_rom_palette_;
|
||||
gfx::SnesPalette col_file_palette_;
|
||||
gfx::SnesPalette link_palette_;
|
||||
gui::Canvas import_canvas_;
|
||||
gui::Canvas scr_canvas_;
|
||||
gui::Canvas super_donkey_canvas_;
|
||||
gui::Canvas current_sheet_canvas_{ImVec2(0x80, 0x20),
|
||||
gui::CanvasGridSize::k8x8};
|
||||
absl::Status status_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||
@@ -4,21 +4,31 @@
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.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/editor/assembly_editor.h"
|
||||
#include "app/core/platform/file_dialog.h"
|
||||
#include "app/editor/dungeon_editor.h"
|
||||
#include "app/editor/music_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/overworld_editor.h"
|
||||
#include "app/editor/screen_editor.h"
|
||||
#include "app/editor/sprite_editor.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"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "gui/widgets.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -34,7 +44,7 @@ constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||
void NewMasterFrame() {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::NewFrame();
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowPos(gui::kZeroPos);
|
||||
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||
|
||||
@@ -54,30 +64,84 @@ bool BeginCentered(const char *name) {
|
||||
return ImGui::Begin(name, nullptr, flags);
|
||||
}
|
||||
|
||||
void DisplayStatus(absl::Status &status) {
|
||||
if (BeginCentered("StatusWindow")) {
|
||||
ImGui::Text("%s", status.ToString().c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::NextColumn();
|
||||
ImGui::Columns(1);
|
||||
ImGui::Separator();
|
||||
ImGui::NewLine();
|
||||
ImGui::SameLine(270);
|
||||
if (ImGui::Button("OK", ImVec2(200, 0))) {
|
||||
status = absl::OkStatus();
|
||||
}
|
||||
ImGui::End();
|
||||
class RecentFilesManager {
|
||||
public:
|
||||
RecentFilesManager(const std::string& filename) : filename_(filename) {}
|
||||
|
||||
void AddFile(const std::string& filePath) {
|
||||
// Add a file to the list, avoiding duplicates
|
||||
auto it = std::find(recentFiles_.begin(), recentFiles_.end(), filePath);
|
||||
if (it == recentFiles_.end()) {
|
||||
recentFiles_.push_back(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void Save() {
|
||||
std::ofstream file(filename_);
|
||||
if (!file.is_open()) {
|
||||
return; // Handle the error appropriately
|
||||
}
|
||||
|
||||
for (const auto& filePath : recentFiles_) {
|
||||
file << filePath << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Load() {
|
||||
std::ifstream file(filename_);
|
||||
if (!file.is_open()) {
|
||||
return; // Handle the error appropriately
|
||||
}
|
||||
|
||||
recentFiles_.clear();
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty()) {
|
||||
recentFiles_.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetRecentFiles() const {
|
||||
return recentFiles_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string filename_;
|
||||
std::vector<std::string> recentFiles_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
using ImGui::BeginMenu;
|
||||
using ImGui::Checkbox;
|
||||
using ImGui::MenuItem;
|
||||
using ImGui::Text;
|
||||
|
||||
void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer) {
|
||||
sdl_renderer_ = renderer;
|
||||
rom_.SetupRenderer(renderer);
|
||||
rom()->SetupRenderer(renderer);
|
||||
}
|
||||
|
||||
void MasterEditor::UpdateScreen() {
|
||||
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();
|
||||
|
||||
DrawYazeMenu();
|
||||
@@ -86,34 +150,83 @@ void MasterEditor::UpdateScreen() {
|
||||
DrawAboutPopup();
|
||||
DrawInfoPopup();
|
||||
|
||||
if (rom()->is_loaded() && !rom_assets_loaded_) {
|
||||
// Initialize overworld graphics, maps, and palettes
|
||||
RETURN_IF_ERROR(overworld_editor_.LoadGraphics());
|
||||
rom_assets_loaded_ = true;
|
||||
}
|
||||
|
||||
TAB_BAR("##TabBar")
|
||||
DrawOverworldEditor();
|
||||
DrawDungeonEditor();
|
||||
DrawMusicEditor();
|
||||
DrawSpriteEditor();
|
||||
DrawScreenEditor();
|
||||
DrawPaletteEditor();
|
||||
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();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void MasterEditor::DrawFileDialog() {
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||
gui::FileDialogPipeline("ChooseFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
status_ = rom_.LoadFromFile(filePathName);
|
||||
overworld_editor_.SetupROM(rom_);
|
||||
screen_editor_.SetupROM(rom_);
|
||||
palette_editor_.SetupROM(rom_);
|
||||
}
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
status_ = rom()->LoadFromFile(filePathName);
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
|
||||
// Load existing recent files
|
||||
manager.Load();
|
||||
|
||||
// Add a new file
|
||||
manager.AddFile(filePathName);
|
||||
|
||||
// Save the updated list
|
||||
manager.Save();
|
||||
});
|
||||
}
|
||||
|
||||
void MasterEditor::DrawStatusPopup() {
|
||||
if (!status_.ok()) {
|
||||
DisplayStatus(status_);
|
||||
show_status_ = true;
|
||||
prev_status_ = status_;
|
||||
}
|
||||
|
||||
if (show_status_ && (BeginCentered("StatusWindow"))) {
|
||||
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)) {
|
||||
show_status_ = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
|
||||
ImGui::SetClipboardText(prev_status_.ToString().c_str());
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,13 +234,13 @@ void MasterEditor::DrawAboutPopup() {
|
||||
if (about_) ImGui::OpenPopup("About");
|
||||
if (ImGui::BeginPopupModal("About", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Yet Another Zelda3 Editor - v0.02");
|
||||
ImGui::Text("Written by: scawful");
|
||||
Text("Yet Another Zelda3 Editor - v%.2f", core::kYazeVersion);
|
||||
Text("Written by: scawful");
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Special Thanks: Zarby89, JaredBrian");
|
||||
Text("Special Thanks: Zarby89, JaredBrian");
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(200, 0))) {
|
||||
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||
about_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
@@ -139,10 +252,10 @@ void MasterEditor::DrawInfoPopup() {
|
||||
if (rom_info_) ImGui::OpenPopup("ROM Information");
|
||||
if (ImGui::BeginPopupModal("ROM Information", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Title: %s", rom_.GetTitle());
|
||||
ImGui::Text("ROM Size: %ld", rom_.size());
|
||||
Text("Title: %s", rom()->title());
|
||||
Text("ROM Size: %ld", rom()->size());
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(200, 0))) {
|
||||
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||
rom_info_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
@@ -151,52 +264,176 @@ void MasterEditor::DrawInfoPopup() {
|
||||
}
|
||||
|
||||
void MasterEditor::DrawYazeMenu() {
|
||||
static bool show_display_settings = false;
|
||||
static bool show_command_line_interface = false;
|
||||
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawEditMenu();
|
||||
DrawViewMenu();
|
||||
DrawHelpMenu();
|
||||
END_MENU_BAR()
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void MasterEditor::DrawFileMenu() const {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
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);
|
||||
gui::DrawDisplaySettings();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (show_command_line_interface) {
|
||||
ImGui::Begin("Command Line Interface", &show_command_line_interface,
|
||||
ImGuiWindowFlags_None);
|
||||
Text("Enter a command:");
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawFileMenu() {
|
||||
static bool save_as_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", ".");
|
||||
}
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") {}
|
||||
MENU_ITEM("Save As..") {}
|
||||
if (BeginMenu("Open Recent")) {
|
||||
static RecentFilesManager manager("recent_files.txt");
|
||||
manager.Load();
|
||||
if (manager.GetRecentFiles().empty()) {
|
||||
MenuItem("No Recent Files", nullptr, false, false);
|
||||
} else {
|
||||
for (const auto& filePath : manager.GetRecentFiles()) {
|
||||
if (MenuItem(filePath.c_str())) {
|
||||
status_ = rom()->LoadFromFile(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
MENU_ITEM2("Save", "Ctrl+S") {
|
||||
if (rom()->is_loaded()) {
|
||||
SaveRom();
|
||||
}
|
||||
}
|
||||
MENU_ITEM("Save As..") { save_as_menu = true; }
|
||||
|
||||
if (rom()->is_loaded()) {
|
||||
MENU_ITEM("Reload") { status_ = rom()->Reload(); }
|
||||
MENU_ITEM("Close") {
|
||||
status_ = rom()->Close();
|
||||
rom_assets_loaded_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// TODO: Make these options matter
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
static bool enabled = true;
|
||||
ImGui::MenuItem("Enabled", "", &enabled);
|
||||
ImGui::BeginChild("child", ImVec2(0, 60), true);
|
||||
for (int i = 0; i < 10; i++) ImGui::Text("Scrolling Text %d", i);
|
||||
ImGui::EndChild();
|
||||
static float f = 0.5f;
|
||||
static int n = 0;
|
||||
ImGui::SliderFloat("Value", &f, 0.0f, 1.0f);
|
||||
ImGui::InputFloat("Input", &f, 0.1f);
|
||||
ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0");
|
||||
if (BeginMenu("Options")) {
|
||||
MenuItem("Backup ROM", "", &backup_rom_);
|
||||
MenuItem("Save New Auto", "", &save_new_auto_);
|
||||
ImGui::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();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (MenuItem("Quit", "Ctrl+Q")) {
|
||||
// TODO: Implement quit confirmation dialog.
|
||||
}
|
||||
|
||||
ImGui::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)) {
|
||||
SaveRom();
|
||||
save_as_menu = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", gui::kDefaultModalSize)) {
|
||||
save_as_menu = false;
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawEditMenu() {
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = overworld_editor_.Undo(); }
|
||||
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = overworld_editor_.Redo(); }
|
||||
if (BeginMenu("Edit")) {
|
||||
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); }
|
||||
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); }
|
||||
ImGui::Separator();
|
||||
MENU_ITEM2("Cut", "Ctrl+X") { status_ = overworld_editor_.Cut(); }
|
||||
MENU_ITEM2("Copy", "Ctrl+C") { status_ = overworld_editor_.Copy(); }
|
||||
MENU_ITEM2("Paste", "Ctrl+V") { status_ = overworld_editor_.Paste(); }
|
||||
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();
|
||||
MENU_ITEM2("Find", "Ctrl+F") {}
|
||||
ImGui::Separator();
|
||||
@@ -207,11 +444,19 @@ void MasterEditor::DrawEditMenu() {
|
||||
|
||||
void MasterEditor::DrawViewMenu() {
|
||||
static bool show_imgui_metrics = false;
|
||||
static bool show_imgui_style_editor = false;
|
||||
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);
|
||||
emulator_.Run();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (show_imgui_metrics) {
|
||||
ImGui::ShowMetricsWindow(&show_imgui_metrics);
|
||||
@@ -219,7 +464,7 @@ void MasterEditor::DrawViewMenu() {
|
||||
|
||||
if (show_memory_editor) {
|
||||
static MemoryEditor mem_edit;
|
||||
mem_edit.DrawWindow("Memory Editor", (void *)&rom_, rom_.size());
|
||||
mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
|
||||
}
|
||||
|
||||
if (show_imgui_demo) {
|
||||
@@ -227,12 +472,12 @@ void MasterEditor::DrawViewMenu() {
|
||||
}
|
||||
|
||||
if (show_asm_editor) {
|
||||
assembly_editor_.Update();
|
||||
assembly_editor_.Update(show_asm_editor);
|
||||
}
|
||||
|
||||
if (show_imgui_style_editor) {
|
||||
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
|
||||
ImGui::ShowStyleEditor();
|
||||
if (show_palette_editor) {
|
||||
ImGui::Begin("Palette Editor", &show_palette_editor);
|
||||
status_ = palette_editor_.Update();
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
@@ -250,7 +495,7 @@ void MasterEditor::DrawViewMenu() {
|
||||
ImGui::TableNextRow();
|
||||
for (int column = 0; column < 3; column++) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Cell %d,%d", column, row);
|
||||
Text("Cell %d,%d", column, row);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
@@ -259,62 +504,105 @@ void MasterEditor::DrawViewMenu() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
||||
ImGui::MenuItem("ASM Editor", nullptr, &show_asm_editor);
|
||||
ImGui::MenuItem("Memory Viewer", nullptr, &show_memory_viewer);
|
||||
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("GUI Tools")) {
|
||||
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
|
||||
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
|
||||
&show_imgui_style_editor);
|
||||
ImGui::EndMenu();
|
||||
if (show_resource_label_manager) {
|
||||
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
|
||||
}
|
||||
|
||||
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);
|
||||
MenuItem("Assembly Editor", nullptr, &show_asm_editor);
|
||||
MenuItem("Palette Editor", nullptr, &show_palette_editor);
|
||||
ImGui::Separator();
|
||||
MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||
MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawHelpMenu() {
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) about_ = true;
|
||||
static bool open_rom_help = false;
|
||||
static bool open_supported_features = 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("About")) about_ = true;
|
||||
ImGui::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 (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||
open_supported_features = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
void MasterEditor::DrawOverworldEditor() {
|
||||
TAB_ITEM("Overworld")
|
||||
status_ = overworld_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
if (open_rom_help) ImGui::OpenPopup("Open a ROM");
|
||||
if (ImGui::BeginPopupModal("Open a ROM", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
Text("File -> Open");
|
||||
Text("Select a ROM file to open");
|
||||
Text("Supported ROMs (headered or unheadered):");
|
||||
Text("The Legend of Zelda: A Link to the Past");
|
||||
Text("US Version 1.0");
|
||||
Text("JP Version 1.0");
|
||||
|
||||
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||
open_rom_help = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void MasterEditor::DrawDungeonEditor() {
|
||||
TAB_ITEM("Dungeon")
|
||||
dungeon_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
void MasterEditor::SaveRom() {
|
||||
if (flags()->kSaveDungeonMaps) {
|
||||
status_ = screen_editor_.SaveDungeonMaps();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
}
|
||||
if (flags()->overworld.kSaveOverworldMaps) {
|
||||
RETURN_VOID_IF_ERROR(
|
||||
status_ = overworld_editor_.overworld()->CreateTile32Tilemap());
|
||||
status_ = overworld_editor_.overworld()->SaveMap32Tiles();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
status_ = overworld_editor_.overworld()->SaveMap16Tiles();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
status_ = overworld_editor_.overworld()->SaveOverworldMaps();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
}
|
||||
if (flags()->overworld.kSaveOverworldEntrances) {
|
||||
status_ = overworld_editor_.overworld()->SaveEntrances();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
}
|
||||
if (flags()->overworld.kSaveOverworldExits) {
|
||||
status_ = overworld_editor_.overworld()->SaveExits();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
}
|
||||
if (flags()->overworld.kSaveOverworldItems) {
|
||||
status_ = overworld_editor_.overworld()->SaveItems();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
}
|
||||
if (flags()->overworld.kSaveOverworldProperties) {
|
||||
status_ = overworld_editor_.overworld()->SaveMapProperties();
|
||||
RETURN_VOID_IF_ERROR(status_);
|
||||
}
|
||||
|
||||
void MasterEditor::DrawPaletteEditor() {
|
||||
TAB_ITEM("Palettes")
|
||||
status_ = palette_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawScreenEditor() {
|
||||
TAB_ITEM("Screens")
|
||||
screen_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawMusicEditor() {
|
||||
TAB_ITEM("Music")
|
||||
music_editor_.Update();
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void MasterEditor::DrawSpriteEditor() {
|
||||
TAB_ITEM("Sprites")
|
||||
END_TAB_ITEM()
|
||||
status_ = rom()->SaveToFile(backup_rom_, save_new_auto_);
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS 1
|
||||
|
||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||
#include <imgui/imgui.h>
|
||||
@@ -8,28 +10,40 @@
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/constants.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "app/gui/pipeline.h"
|
||||
#include "app/editor/context/gfx_context.h"
|
||||
#include "app/editor/dungeon_editor.h"
|
||||
#include "app/editor/music_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/overworld_editor.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/editor/screen_editor.h"
|
||||
#include "app/editor/sprite_editor.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/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class MasterEditor {
|
||||
class MasterEditor : public SharedROM,
|
||||
public GfxContext,
|
||||
public core::ExperimentFlags {
|
||||
public:
|
||||
MasterEditor() { current_editor_ = &overworld_editor_; }
|
||||
|
||||
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer);
|
||||
void UpdateScreen();
|
||||
absl::Status Update();
|
||||
|
||||
void Shutdown() { overworld_editor_.Shutdown(); }
|
||||
|
||||
private:
|
||||
void DrawFileDialog();
|
||||
@@ -38,31 +52,37 @@ class MasterEditor {
|
||||
void DrawInfoPopup();
|
||||
|
||||
void DrawYazeMenu();
|
||||
void DrawFileMenu() const;
|
||||
void DrawFileMenu();
|
||||
void DrawEditMenu();
|
||||
void DrawViewMenu();
|
||||
void DrawHelpMenu();
|
||||
|
||||
void DrawOverworldEditor();
|
||||
void DrawDungeonEditor();
|
||||
void DrawPaletteEditor();
|
||||
void DrawMusicEditor();
|
||||
void DrawScreenEditor();
|
||||
void DrawSpriteEditor();
|
||||
void SaveRom();
|
||||
|
||||
bool about_ = false;
|
||||
bool rom_info_ = false;
|
||||
bool backup_rom_ = false;
|
||||
bool save_new_auto_ = true;
|
||||
bool show_status_ = false;
|
||||
bool rom_assets_loaded_ = false;
|
||||
|
||||
absl::Status status_;
|
||||
absl::Status prev_status_;
|
||||
|
||||
std::shared_ptr<SDL_Renderer> sdl_renderer_;
|
||||
absl::Status status_;
|
||||
|
||||
emu::Emulator emulator_;
|
||||
|
||||
AssemblyEditor assembly_editor_;
|
||||
DungeonEditor dungeon_editor_;
|
||||
GraphicsEditor graphics_editor_;
|
||||
MusicEditor music_editor_;
|
||||
OverworldEditor overworld_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
ScreenEditor screen_editor_;
|
||||
MusicEditor music_editor_;
|
||||
ROM rom_;
|
||||
SpriteEditor sprite_editor_;
|
||||
|
||||
Editor *current_editor_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#include "assembly_editor.h"
|
||||
|
||||
#include "app/gui/widgets.h"
|
||||
#include "core/constants.h"
|
||||
#include "gui/widgets.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
AssemblyEditor::AssemblyEditor() {
|
||||
text_editor_.SetLanguageDefinition(gui::widgets::GetAssemblyLanguageDef());
|
||||
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
|
||||
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
||||
}
|
||||
|
||||
void AssemblyEditor::Update() {
|
||||
ImGui::Begin("Assembly Editor", &file_is_loaded_);
|
||||
void AssemblyEditor::Update(bool& is_loaded) {
|
||||
ImGui::Begin("Assembly Editor", &is_loaded);
|
||||
MENU_BAR()
|
||||
DrawFileMenu();
|
||||
DrawEditMenu();
|
||||
@@ -32,8 +32,6 @@ void AssemblyEditor::Update() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AssemblyEditor::InlineUpdate() {
|
||||
ChangeActiveFile("assets/asm/template_song.asm");
|
||||
auto cpos = text_editor_.GetCursorPosition();
|
||||
@@ -48,10 +46,49 @@ void AssemblyEditor::InlineUpdate() {
|
||||
text_editor_.Render("##asm_editor", ImVec2(0, 0));
|
||||
}
|
||||
|
||||
void AssemblyEditor::ChangeActiveFile(const std::string& filename) {
|
||||
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")) {
|
||||
@@ -16,13 +16,16 @@ class AssemblyEditor {
|
||||
public:
|
||||
AssemblyEditor();
|
||||
|
||||
void Update();
|
||||
void Update(bool &is_loaded);
|
||||
void InlineUpdate();
|
||||
void ChangeActiveFile(const std::string &);
|
||||
void ChangeActiveFile(const std::string_view &);
|
||||
|
||||
private:
|
||||
void DrawFileMenu();
|
||||
void DrawEditMenu();
|
||||
|
||||
void DrawFileView();
|
||||
|
||||
void SetEditorText();
|
||||
|
||||
bool file_is_loaded_ = false;
|
||||
255
src/app/editor/modules/gfx_group_editor.cc
Normal file
255
src/app/editor/modules/gfx_group_editor.cc
Normal file
@@ -0,0 +1,255 @@
|
||||
#include "gfx_group_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/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/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.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")[rom()->paletteset_ids[selected_paletteset][0]]
|
||||
.mutable_palette(0);
|
||||
DrawPaletteFromPaletteGroup(palette);
|
||||
auto &spr_aux_pal1 =
|
||||
*rom()
|
||||
->mutable_palette_group(
|
||||
"sprites_aux1")[rom()->paletteset_ids[selected_paletteset][1]]
|
||||
.mutable_palette(0);
|
||||
DrawPaletteFromPaletteGroup(spr_aux_pal1);
|
||||
auto &spr_aux_pal2 =
|
||||
*rom()
|
||||
->mutable_palette_group(
|
||||
"sprites_aux2")[rom()->paletteset_ids[selected_paletteset][2]]
|
||||
.mutable_palette(0);
|
||||
DrawPaletteFromPaletteGroup(spr_aux_pal2);
|
||||
auto &spr_aux_pal3 =
|
||||
*rom()
|
||||
->mutable_palette_group(
|
||||
"sprites_aux3")[rom()->paletteset_ids[selected_paletteset][3]]
|
||||
.mutable_palette(0);
|
||||
DrawPaletteFromPaletteGroup(spr_aux_pal3);
|
||||
}
|
||||
|
||||
void GfxGroupEditor::InitBlockset(gfx::Bitmap tile16_blockset) {
|
||||
tile16_blockset_bmp_ = tile16_blockset;
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
70
src/app/editor/modules/gfx_group_editor.h
Normal file
70
src/app/editor/modules/gfx_group_editor.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/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/rom.h"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class GfxGroupEditor : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
void DrawBlocksetViewer(bool sheet_only = false);
|
||||
void DrawRoomsetViewer();
|
||||
void DrawSpritesetViewer(bool sheet_only = false);
|
||||
void DrawPaletteViewer();
|
||||
|
||||
void SetSelectedBlockset(uint8_t blockset) { selected_blockset_ = blockset; }
|
||||
void SetSelectedRoomset(uint8_t roomset) { selected_roomset_ = roomset; }
|
||||
void SetSelectedSpriteset(uint8_t spriteset) {
|
||||
selected_spriteset_ = spriteset;
|
||||
}
|
||||
|
||||
void InitBlockset(gfx::Bitmap tile16_blockset);
|
||||
|
||||
private:
|
||||
int preview_palette_id_ = 0;
|
||||
int last_sheet_id_ = 0;
|
||||
uint8_t selected_blockset_ = 0;
|
||||
uint8_t selected_roomset_ = 0;
|
||||
uint8_t selected_spriteset_ = 0;
|
||||
|
||||
PaletteEditor palette_editor_;
|
||||
|
||||
gui::Canvas blockset_canvas_;
|
||||
gui::Canvas roomset_canvas_;
|
||||
gui::Canvas spriteset_canvas_;
|
||||
|
||||
gfx::SnesPalette palette_;
|
||||
gfx::PaletteGroup palette_group_;
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
gui::BitmapViewer gfx_group_viewer_;
|
||||
zelda3::Overworld overworld_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
#endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "snes_spc/demo/demo_util.h"
|
||||
#include "snes_spc/demo/wave_writer.h"
|
||||
#include "snes_spc/snes_spc/spc.h"
|
||||
#include "app/editor/modules/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 {
|
||||
@@ -21,46 +21,46 @@ 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");
|
||||
// /* 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 */
|
||||
// {
|
||||
// /* 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 */
|
||||
// /* 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);
|
||||
// /* 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);
|
||||
}
|
||||
// /* 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));
|
||||
// /* 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);
|
||||
// /* Filter samples */
|
||||
// spc_filter_run(filter, buf, BUF_SIZE);
|
||||
|
||||
wave_write(buf, BUF_SIZE);
|
||||
}
|
||||
// wave_write(buf, BUF_SIZE);
|
||||
// }
|
||||
|
||||
/* Cleanup */
|
||||
spc_filter_delete(filter);
|
||||
spc_delete(snes_spc);
|
||||
wave_close();
|
||||
// /* Cleanup */
|
||||
// spc_filter_delete(filter);
|
||||
// spc_delete(snes_spc);
|
||||
// wave_close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -223,10 +223,10 @@ void MusicEditor::DrawToolset() {
|
||||
|
||||
if (is_playing) {
|
||||
if (!has_loaded_song) {
|
||||
PlaySPC();
|
||||
current_song_ = Mix_LoadMUS("out.wav");
|
||||
Mix_PlayMusic(current_song_, -1);
|
||||
has_loaded_song = true;
|
||||
// PlaySPC();
|
||||
// current_song_ = Mix_LoadMUS("out.wav");
|
||||
// Mix_PlayMusic(current_song_, -1);
|
||||
// has_loaded_song = true;
|
||||
}
|
||||
|
||||
// // If there is no music playing
|
||||
@@ -252,11 +252,13 @@ void MusicEditor::DrawToolset() {
|
||||
ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
|
||||
|
||||
gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
|
||||
if (ImGui::BeginTable("SongToolset", 5, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
if (ImGui::BeginTable("SongToolset", 6, toolset_table_flags_, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("#play");
|
||||
ImGui::TableSetupColumn("#rewind");
|
||||
ImGui::TableSetupColumn("#fastforward");
|
||||
ImGui::TableSetupColumn("#volume");
|
||||
ImGui::TableSetupColumn("#debug");
|
||||
|
||||
ImGui::TableSetupColumn("#slider");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
@@ -271,6 +273,9 @@ void MusicEditor::DrawToolset() {
|
||||
BUTTON_COLUMN(ICON_MD_FAST_REWIND)
|
||||
BUTTON_COLUMN(ICON_MD_FAST_FORWARD)
|
||||
BUTTON_COLUMN(ICON_MD_VOLUME_UP)
|
||||
if (ImGui::Button(ICON_MD_ACCESS_TIME)) {
|
||||
music_tracker_.LoadSongs(*rom());
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SliderInt("Volume", ¤t_volume, 0, 100);
|
||||
ImGui::EndTable();
|
||||
@@ -5,13 +5,15 @@
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/assembly_editor.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "snes_spc/demo/demo_util.h"
|
||||
#include "snes_spc/demo/wave_writer.h"
|
||||
#include "snes_spc/snes_spc/spc.h"
|
||||
#include "app/editor/modules/assembly_editor.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/music/tracker.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 {
|
||||
@@ -52,7 +54,7 @@ static const char* kGameSongs[] = {"Title",
|
||||
static constexpr absl::string_view kSongNotes[] = {
|
||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C",
|
||||
"C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
|
||||
class MusicEditor {
|
||||
class MusicEditor : public SharedROM {
|
||||
public:
|
||||
void Update();
|
||||
|
||||
@@ -63,7 +65,9 @@ class MusicEditor {
|
||||
void DrawSongToolset();
|
||||
void DrawToolset();
|
||||
|
||||
Mix_Music* current_song_ = NULL;
|
||||
zelda3::Tracker music_tracker_;
|
||||
|
||||
// Mix_Music* current_song_ = NULL;
|
||||
|
||||
AssemblyEditor assembly_editor_;
|
||||
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
||||
299
src/app/editor/modules/palette_editor.cc
Normal file
299
src/app/editor/modules/palette_editor.cc
Normal file
@@ -0,0 +1,299 @@
|
||||
#include "palette_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/color.h"
|
||||
#include "app/gui/icons.h"
|
||||
|
||||
static inline float ImSaturate(float f) {
|
||||
return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
|
||||
}
|
||||
|
||||
#define IM_F32_TO_INT8_SAT(_VAL) \
|
||||
((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
|
||||
|
||||
int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
#ifdef IMGUI_USE_STB_SPRINTF
|
||||
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
|
||||
#else
|
||||
int w = vsnprintf(buf, buf_size, fmt, args);
|
||||
#endif
|
||||
va_end(args);
|
||||
if (buf == nullptr) return w;
|
||||
if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
|
||||
buf[w] = 0;
|
||||
return w;
|
||||
}
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
absl::Status PaletteEditor::Update() {
|
||||
if (rom()->is_loaded()) {
|
||||
// Initialize the labels
|
||||
for (int i = 0; i < kNumPalettes; i++) {
|
||||
rom()->resource_label()->CreateOrGetLabel(
|
||||
"Palette Group Name", std::to_string(i),
|
||||
std::string(kPaletteGroupNames[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("paletteEditorTable", 2,
|
||||
ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_SizingStretchSame,
|
||||
ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("Palette Groups",
|
||||
ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
ImGui::TableHeadersRow();
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
for (int category = 0; category < kNumPalettes; ++category) {
|
||||
if (ImGui::TreeNode(kPaletteCategoryNames[category].data())) {
|
||||
status_ = DrawPaletteGroup(category);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (gui::SnesColorEdit4("Color Picker", current_color_,
|
||||
ImGuiColorEditFlags_NoAlpha)) {
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
CLEAR_AND_RETURN_STATUS(status_)
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette, int index) {
|
||||
if (index >= palette.size()) {
|
||||
// Handle error: the index is out of bounds
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current color
|
||||
auto currentColor = palette.GetColor(index).rgb();
|
||||
if (ImGui::ColorPicker4("Color Picker", (float*)&palette[index])) {
|
||||
// The color was modified, update it in the palette
|
||||
palette(index, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditor::ResetColorToOriginal(
|
||||
gfx::SnesPalette& palette, int index,
|
||||
const gfx::SnesPalette& originalPalette) {
|
||||
if (index >= palette.size() || index >= originalPalette.size()) {
|
||||
// Handle error: the index is out of bounds
|
||||
return;
|
||||
}
|
||||
|
||||
auto originalColor = originalPalette.GetColor(index).rgb();
|
||||
palette(index, originalColor);
|
||||
}
|
||||
|
||||
absl::Status PaletteEditor::DrawPaletteGroup(int category) {
|
||||
if (!rom()->is_loaded()) {
|
||||
return absl::NotFoundError("ROM not open, no palettes to display");
|
||||
}
|
||||
|
||||
const auto size =
|
||||
rom()->palette_group(kPaletteGroupNames[category].data()).size();
|
||||
auto palettes =
|
||||
rom()->mutable_palette_group(kPaletteGroupNames[category].data());
|
||||
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 = palettes->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)) {
|
||||
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)) {
|
||||
RETURN_IF_ERROR(rom()->UpdatePaletteColor(kPaletteGroupNames[i].data(), j,
|
||||
n, palette[n]))
|
||||
}
|
||||
|
||||
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
|
||||
121
src/app/editor/modules/palette_editor.h
Normal file
121
src/app/editor/modules/palette_editor.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_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/icons.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
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"};
|
||||
|
||||
struct PaletteChange {
|
||||
std::string group_name;
|
||||
size_t palette_index;
|
||||
size_t color_index;
|
||||
gfx::SnesColor original_color;
|
||||
gfx::SnesColor new_color;
|
||||
};
|
||||
|
||||
class PaletteEditorHistory {
|
||||
public:
|
||||
// Record a change in the palette editor
|
||||
void RecordChange(const std::string& groupName, size_t paletteIndex,
|
||||
size_t colorIndex, const gfx::SnesColor& originalColor,
|
||||
const gfx::SnesColor& newColor) {
|
||||
// Check size and remove the oldest if necessary
|
||||
if (recentChanges.size() >= maxHistorySize) {
|
||||
recentChanges.pop_front();
|
||||
}
|
||||
|
||||
// Push the new change
|
||||
recentChanges.push_back(
|
||||
{groupName, paletteIndex, colorIndex, originalColor, newColor});
|
||||
}
|
||||
|
||||
// Get recent changes for display in the palette editor
|
||||
const std::deque<PaletteChange>& GetRecentChanges() const {
|
||||
return recentChanges;
|
||||
}
|
||||
|
||||
// Restore the original color
|
||||
gfx::SnesColor GetOriginalColor(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 &&
|
||||
change.color_index == colorIndex) {
|
||||
return change.original_color;
|
||||
}
|
||||
}
|
||||
// Handle error or return default (this is just an example,
|
||||
// handle as appropriate for your application)
|
||||
return gfx::SnesColor();
|
||||
}
|
||||
|
||||
private:
|
||||
std::deque<PaletteChange> recentChanges;
|
||||
static const size_t maxHistorySize = 50; // or any other number you deem fit
|
||||
};
|
||||
|
||||
class PaletteEditor : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
absl::Status DrawPaletteGroups();
|
||||
|
||||
void EditColorInPalette(gfx::SnesPalette& palette, int index);
|
||||
void 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);
|
||||
|
||||
private:
|
||||
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
|
||||
|
||||
void InitializeSavedPalette(const gfx::SnesPalette& palette) {
|
||||
for (int n = 0; n < palette.size(); n++) {
|
||||
saved_palette_[n].x = palette.GetColor(n).rgb().x / 255;
|
||||
saved_palette_[n].y = palette.GetColor(n).rgb().y / 255;
|
||||
saved_palette_[n].z = palette.GetColor(n).rgb().z / 255;
|
||||
saved_palette_[n].w = 255; // Alpha
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status status_;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
354
src/app/editor/modules/tile16_editor.cc
Normal file
354
src/app/editor/modules/tile16_editor.cc
Normal file
@@ -0,0 +1,354 @@
|
||||
#include "tile16_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/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/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.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using ImGui::BeginChild;
|
||||
using ImGui::BeginTabBar;
|
||||
using ImGui::BeginTabItem;
|
||||
using ImGui::BeginTable;
|
||||
using ImGui::Combo;
|
||||
using ImGui::EndChild;
|
||||
using ImGui::EndTabBar;
|
||||
using ImGui::EndTabItem;
|
||||
using ImGui::TableHeadersRow;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::TableNextRow;
|
||||
using ImGui::TableSetupColumn;
|
||||
|
||||
absl::Status Tile16Editor::Update() {
|
||||
if (rom()->is_loaded() && !map_blockset_loaded_) {
|
||||
RETURN_IF_ERROR(LoadTile8());
|
||||
ImVector<std::string> tile16_names;
|
||||
for (int i = 0; i < 0x200; ++i) {
|
||||
std::string str = core::UppercaseHexByte(all_tiles_types_[i]);
|
||||
tile16_names.push_back(str);
|
||||
}
|
||||
|
||||
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
|
||||
*tile8_source_canvas_.custom_labels_enabled() = true;
|
||||
}
|
||||
|
||||
if (BeginTabBar("Tile16 Editor Tabs")) {
|
||||
RETURN_IF_ERROR(DrawTile16Editor());
|
||||
RETURN_IF_ERROR(UpdateTile16Transfer());
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::DrawTile16Editor() {
|
||||
if (BeginTabItem("Tile16 Editing")) {
|
||||
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateBlockset());
|
||||
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateTile16Edit());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::UpdateTile16Transfer() {
|
||||
if (BeginTabItem("Tile16 Transfer")) {
|
||||
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||
ImGui::GetContentRegionAvail().x / 2);
|
||||
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||
ImGui::GetContentRegionAvail().x / 2);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateBlockset());
|
||||
|
||||
TableNextColumn();
|
||||
RETURN_IF_ERROR(UpdateTransferTileCanvas());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::UpdateBlockset() {
|
||||
gui::BeginPadding(2);
|
||||
gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
|
||||
blockset_canvas_.DrawBackground();
|
||||
gui::EndPadding();
|
||||
blockset_canvas_.DrawContextMenu();
|
||||
blockset_canvas_.DrawTileSelector(32);
|
||||
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_);
|
||||
blockset_canvas_.DrawGrid();
|
||||
blockset_canvas_.DrawOverlay();
|
||||
ImGui::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_.ApplyPalette(
|
||||
rom()->palette_group("ow_main")[current_palette_]);
|
||||
rom()->RenderBitmap(¤t_tile16_bmp_);
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
|
||||
constexpr int tile8_size = 8;
|
||||
constexpr int tile16_size = 16;
|
||||
|
||||
// Calculate the tile index for x and y based on the click_position
|
||||
// Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
|
||||
int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
|
||||
int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
|
||||
std::cout << "Tile Index X: " << tile_index_x << std::endl;
|
||||
std::cout << "Tile Index Y: " << tile_index_y << std::endl;
|
||||
|
||||
// Calculate the pixel start position within the Tile16
|
||||
ImVec2 start_position;
|
||||
start_position.x = ((tile_index_x) / 4) * 0x40;
|
||||
start_position.y = ((tile_index_y) / 4) * 0x40;
|
||||
std::cout << "Start Position X: " << start_position.x << std::endl;
|
||||
std::cout << "Start Position Y: " << start_position.y << std::endl;
|
||||
|
||||
// Draw the Tile8 to the correct position within the Tile16
|
||||
for (int y = 0; y < tile8_size; ++y) {
|
||||
for (int x = 0; x < tile8_size; ++x) {
|
||||
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(
|
||||
pixel_index,
|
||||
current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::UpdateTile16Edit() {
|
||||
if (ImGui::BeginChild("Tile8 Selector",
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, 0x175),
|
||||
true)) {
|
||||
tile8_source_canvas_.DrawBackground(
|
||||
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4));
|
||||
tile8_source_canvas_.DrawContextMenu();
|
||||
if (tile8_source_canvas_.DrawTileSelector(32)) {
|
||||
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
|
||||
rom()->palette_group("ow_main")[0], current_palette_);
|
||||
rom()->UpdateBitmap(¤t_gfx_individual_[current_tile8_]);
|
||||
}
|
||||
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
|
||||
tile8_source_canvas_.DrawGrid(32.0f);
|
||||
tile8_source_canvas_.DrawOverlay();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
if (!tile8_source_canvas_.points().empty()) {
|
||||
uint16_t x = tile8_source_canvas_.points().front().x / 16;
|
||||
uint16_t y = tile8_source_canvas_.points().front().y / 16;
|
||||
|
||||
current_tile8_ = x + (y * 8);
|
||||
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
|
||||
rom()->palette_group("ow_main")[0], current_palette_);
|
||||
rom()->UpdateBitmap(¤t_gfx_individual_[current_tile8_]);
|
||||
}
|
||||
|
||||
ImGui::Text("Tile16 ID: %d", current_tile16_);
|
||||
ImGui::Text("Tile8 ID: %d", current_tile8_);
|
||||
|
||||
if (ImGui::BeginChild("Tile16 Editor Options",
|
||||
ImVec2(ImGui::GetContentRegionAvail().x, 0x50), true)) {
|
||||
tile16_edit_canvas_.DrawBackground(ImVec2(0x40, 0x40));
|
||||
tile16_edit_canvas_.DrawContextMenu();
|
||||
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_);
|
||||
}
|
||||
}
|
||||
tile16_edit_canvas_.DrawGrid(64.0f);
|
||||
tile16_edit_canvas_.DrawOverlay();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
DrawTileEditControls();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void Tile16Editor::DrawTileEditControls() {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Options:");
|
||||
gui::InputHexByte("Palette", ¬ify_palette.mutable_get());
|
||||
notify_palette.apply_changes();
|
||||
if (notify_palette.modified()) {
|
||||
auto palette = palettesets_[current_palette_].main;
|
||||
auto value = notify_palette.get();
|
||||
if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
|
||||
palette = palettesets_[current_palette_].aux1;
|
||||
value -= 0x04;
|
||||
} else if (notify_palette.get() > 0x06) {
|
||||
palette = palettesets_[current_palette_].aux2;
|
||||
value -= 0x06;
|
||||
}
|
||||
|
||||
if (value > 0x00) {
|
||||
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value);
|
||||
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value);
|
||||
rom()->UpdateBitmap(¤t_gfx_bmp_);
|
||||
rom()->UpdateBitmap(¤t_tile16_bmp_);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Checkbox("X Flip", &x_flip);
|
||||
ImGui::Checkbox("Y Flip", &y_flip);
|
||||
ImGui::Checkbox("Priority Tile", &priority_tile);
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
|
||||
// Create a button for loading another ROM
|
||||
if (ImGui::Button("Load ROM")) {
|
||||
ImGuiFileDialog::Instance()->OpenDialog(
|
||||
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", ".");
|
||||
}
|
||||
gui::FileDialogPipeline(
|
||||
"ChooseTransferFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||
std::string filePathName =
|
||||
ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
transfer_status_ = transfer_rom_.LoadFromFile(filePathName);
|
||||
transfer_started_ = true;
|
||||
});
|
||||
|
||||
// TODO: Implement tile16 transfer
|
||||
if (transfer_started_ && !transfer_blockset_loaded_) {
|
||||
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
|
||||
graphics_bin_ = transfer_rom_.graphics_bin();
|
||||
|
||||
// Load the Link to the Past overworld.
|
||||
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
|
||||
transfer_overworld_.set_current_map(0);
|
||||
palette_ = transfer_overworld_.AreaPalette();
|
||||
|
||||
// Create the tile16 blockset image
|
||||
gui::BuildAndRenderBitmapPipeline(0x80, 0x2000, 0x80,
|
||||
transfer_overworld_.Tile16Blockset(),
|
||||
*rom(), transfer_blockset_bmp_, palette_);
|
||||
transfer_blockset_loaded_ = true;
|
||||
}
|
||||
|
||||
// Create a canvas for holding the tiles which will be exported
|
||||
gui::BitmapCanvasPipeline(transfer_canvas_, transfer_blockset_bmp_, 0x100,
|
||||
(8192 * 2), 0x20, transfer_blockset_loaded_, true,
|
||||
3);
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::InitBlockset(
|
||||
const gfx::Bitmap& tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp,
|
||||
const std::vector<gfx::Bitmap>& tile16_individual,
|
||||
uint8_t all_tiles_types[0x200]) {
|
||||
all_tiles_types_ = all_tiles_types;
|
||||
tile16_blockset_bmp_ = tile16_blockset_bmp;
|
||||
tile16_individual_ = tile16_individual;
|
||||
current_gfx_bmp_ = current_gfx_bmp;
|
||||
tile8_gfx_data_ = current_gfx_bmp_.vector();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Tile16Editor::LoadTile8() {
|
||||
current_gfx_individual_.reserve(1024);
|
||||
|
||||
for (int index = 0; index < 1024; index++) {
|
||||
std::vector<uint8_t> tile_data(0x40, 0x00);
|
||||
|
||||
// Copy the pixel data for the current tile into the vector
|
||||
for (int ty = 0; ty < 8; ty++) {
|
||||
for (int tx = 0; tx < 8; tx++) {
|
||||
// Current Gfx Data is 16 sheets of 8x8 tiles ordered 16 wide by 4 tall
|
||||
|
||||
// Calculate the position in the tile data vector
|
||||
int position = tx + (ty * 0x08);
|
||||
|
||||
// Calculate the position in the current gfx data
|
||||
int num_columns = current_gfx_bmp_.width() / 8;
|
||||
int num_rows = current_gfx_bmp_.height() / 8;
|
||||
int x = (index % num_columns) * 8 + tx;
|
||||
int y = (index / num_columns) * 8 + ty;
|
||||
int gfx_position = x + (y * 0x100);
|
||||
|
||||
// Get the pixel value from the current gfx data
|
||||
uint8_t value = tile8_gfx_data_[gfx_position];
|
||||
|
||||
if (value & 0x80) {
|
||||
value -= 0x88;
|
||||
}
|
||||
|
||||
tile_data[position] = value;
|
||||
}
|
||||
}
|
||||
|
||||
current_gfx_individual_.emplace_back();
|
||||
current_gfx_individual_[index].Create(0x08, 0x08, 0x08, tile_data);
|
||||
current_gfx_individual_[index].ApplyPaletteWithTransparent(
|
||||
rom()->palette_group("ow_main")[0], current_palette_);
|
||||
rom()->RenderBitmap(¤t_gfx_individual_[index]);
|
||||
}
|
||||
|
||||
map_blockset_loaded_ = true;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
126
src/app/editor/modules/tile16_editor.h
Normal file
126
src/app/editor/modules/tile16_editor.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||
#define YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/core/editor.h"
|
||||
#include "app/editor/context/gfx_context.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"
|
||||
#include "app/zelda3/overworld.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
class Tile16Editor : public GfxContext, public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
absl::Status DrawTile16Editor();
|
||||
absl::Status UpdateTile16Transfer();
|
||||
absl::Status UpdateBlockset();
|
||||
|
||||
absl::Status DrawToCurrentTile16(ImVec2 pos);
|
||||
|
||||
absl::Status UpdateTile16Edit();
|
||||
|
||||
void DrawTileEditControls();
|
||||
|
||||
absl::Status UpdateTransferTileCanvas();
|
||||
|
||||
absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
|
||||
gfx::Bitmap current_gfx_bmp,
|
||||
const std::vector<gfx::Bitmap>& tile16_individual,
|
||||
uint8_t all_tiles_types[0x200]);
|
||||
|
||||
absl::Status LoadTile8();
|
||||
|
||||
auto set_tile16(int id) {
|
||||
current_tile16_ = id;
|
||||
current_tile16_bmp_ = tile16_individual_[id];
|
||||
current_tile16_bmp_.ApplyPalette(
|
||||
rom()->palette_group("ow_main")[current_palette_]);
|
||||
rom()->RenderBitmap(¤t_tile16_bmp_);
|
||||
}
|
||||
|
||||
private:
|
||||
bool map_blockset_loaded_ = false;
|
||||
bool transfer_started_ = false;
|
||||
bool transfer_blockset_loaded_ = false;
|
||||
|
||||
int current_tile16_ = 0;
|
||||
int current_tile8_ = 0;
|
||||
uint8_t current_palette_ = 0;
|
||||
|
||||
core::NotifyValue<uint32_t> notify_tile16;
|
||||
core::NotifyValue<uint8_t> notify_palette;
|
||||
|
||||
// Canvas dimensions
|
||||
int canvas_width;
|
||||
int canvas_height;
|
||||
|
||||
// Texture ID for the canvas
|
||||
int texture_id;
|
||||
|
||||
// Various options for the Tile16 Editor
|
||||
bool x_flip;
|
||||
bool y_flip;
|
||||
bool priority_tile;
|
||||
int tile_size;
|
||||
|
||||
uint8_t *all_tiles_types_;
|
||||
|
||||
// Tile16 blockset for selecting the tile to edit
|
||||
gui::Canvas blockset_canvas_{ImVec2(0x100, 0x4000),
|
||||
gui::CanvasGridSize::k32x32};
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
|
||||
// Canvas for editing the selected tile
|
||||
gui::Canvas tile16_edit_canvas_;
|
||||
gfx::Bitmap current_tile16_bmp_;
|
||||
gfx::Bitmap current_tile8_bmp_;
|
||||
|
||||
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
|
||||
gui::Canvas tile8_source_canvas_;
|
||||
gfx::Bitmap current_gfx_bmp_;
|
||||
|
||||
gui::Canvas transfer_canvas_;
|
||||
gfx::Bitmap transfer_blockset_bmp_;
|
||||
gfx::Bitmap transfer_current_bmp_;
|
||||
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
std::vector<gfx::Bitmap> current_gfx_individual_;
|
||||
|
||||
std::vector<uint8_t> current_tile16_data_;
|
||||
|
||||
std::vector<uint8_t> tile8_gfx_data_;
|
||||
|
||||
PaletteEditor palette_editor_;
|
||||
|
||||
gfx::SnesPalette palette_;
|
||||
zelda3::Overworld transfer_overworld_;
|
||||
|
||||
gfx::BitmapTable graphics_bin_;
|
||||
|
||||
ROM transfer_rom_;
|
||||
absl::Status transfer_status_;
|
||||
|
||||
core::TaskManager<std::function<void(int)>> task_manager_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
@@ -10,14 +11,20 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/palette_editor.h"
|
||||
#include "app/core/common.h"
|
||||
#include "app/core/editor.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/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"
|
||||
#include "app/zelda3/overworld.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -31,43 +38,136 @@ static constexpr uint kTile8DisplayHeight = 64;
|
||||
static constexpr float kInputFieldSize = 30.f;
|
||||
|
||||
static constexpr absl::string_view kToolsetColumnNames[] = {
|
||||
"#undoTool", "#redoTool", "#drawTool", "#separator2",
|
||||
"#zoomOutTool", "#zoomInTool", "#separator", "#history",
|
||||
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
|
||||
"#zoomInTool", "#separator", "#drawTool", "#history",
|
||||
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
||||
"#transportTool", "#musicTool"};
|
||||
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
|
||||
"propertiesTool"};
|
||||
|
||||
static constexpr absl::string_view kOverworldSettingsColumnNames[] = {
|
||||
"##1stCol", "##gfxCol", "##palCol", "##sprgfxCol",
|
||||
"##sprpalCol", "##msgidCol", "##2ndCol"};
|
||||
constexpr ImGuiTableFlags kOWMapFlags =
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
|
||||
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
|
||||
constexpr ImGuiTableFlags kOWEditFlags =
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersV;
|
||||
|
||||
class OverworldEditor {
|
||||
constexpr absl::string_view kWorldList =
|
||||
"Light World\0Dark World\0Extra World\0";
|
||||
|
||||
constexpr absl::string_view kGamePartComboString = "Part 0\0Part 1\0Part 2\0";
|
||||
|
||||
constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
|
||||
constexpr absl::string_view kOWEditTable = "##OWEditTable";
|
||||
constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
|
||||
|
||||
class OverworldEditor : public Editor,
|
||||
public SharedROM,
|
||||
public GfxContext,
|
||||
public core::ExperimentFlags {
|
||||
public:
|
||||
absl::Status Update();
|
||||
absl::Status Undo() const { return absl::UnimplementedError("Undo"); }
|
||||
absl::Status Redo() const { return absl::UnimplementedError("Redo"); }
|
||||
absl::Status Cut() const { return absl::UnimplementedError("Cut"); }
|
||||
absl::Status Copy() const { return absl::UnimplementedError("Copy"); }
|
||||
absl::Status Paste() const { return absl::UnimplementedError("Paste"); }
|
||||
void SetupROM(ROM &rom) { rom_ = rom; }
|
||||
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"); }
|
||||
|
||||
auto overworld() { return &overworld_; }
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status LoadGraphics();
|
||||
|
||||
private:
|
||||
absl::Status UpdateOverworldEdit();
|
||||
|
||||
absl::Status DrawToolset();
|
||||
void DrawOverworldMapSettings();
|
||||
|
||||
void DrawOverworldEntrances();
|
||||
void RefreshChildMap(int i);
|
||||
void RefreshOverworldMap();
|
||||
void RefreshMapPalette();
|
||||
void RefreshMapProperties();
|
||||
void RefreshTile16Blockset();
|
||||
|
||||
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling,
|
||||
bool holes = false);
|
||||
void DrawOverworldExits(ImVec2 zero, ImVec2 scrolling);
|
||||
void DrawOverworldItems();
|
||||
void DrawOverworldSprites();
|
||||
|
||||
void DrawOverworldMaps();
|
||||
void DrawOverworldEdits();
|
||||
void RenderUpdatedMapBitmap(const ImVec2& click_position,
|
||||
const Bytes& tile_data);
|
||||
void CheckForOverworldEdits();
|
||||
void CheckForCurrentMap();
|
||||
void CheckForSelectRectangle();
|
||||
void DrawOverworldCanvas();
|
||||
|
||||
void DrawTile16Selector();
|
||||
void DrawTile8Selector();
|
||||
void DrawAreaGraphics();
|
||||
void DrawTileSelector();
|
||||
absl::Status LoadGraphics();
|
||||
|
||||
absl::Status LoadSpriteGraphics();
|
||||
|
||||
void DrawOverworldProperties();
|
||||
|
||||
absl::Status DrawExperimentalModal();
|
||||
|
||||
absl::Status UpdateUsageStats();
|
||||
void DrawUsageGrid();
|
||||
void CalculateUsageStats();
|
||||
|
||||
void LoadAnimatedMaps();
|
||||
void DrawDebugWindow();
|
||||
|
||||
auto gfx_group_editor() const { return gfx_group_editor_; }
|
||||
|
||||
enum class EditingMode {
|
||||
DRAW_TILE,
|
||||
ENTRANCES,
|
||||
EXITS,
|
||||
ITEMS,
|
||||
SPRITES,
|
||||
TRANSPORTS,
|
||||
MUSIC,
|
||||
PAN
|
||||
};
|
||||
|
||||
EditingMode current_mode = EditingMode::DRAW_TILE;
|
||||
EditingMode previous_mode = EditingMode::DRAW_TILE;
|
||||
|
||||
int current_world_ = 0;
|
||||
int current_map_ = 0;
|
||||
int current_parent_ = 0;
|
||||
int game_state_ = 1;
|
||||
int current_tile16_ = 0;
|
||||
int selected_tile_ = 0;
|
||||
|
||||
int current_blockset_ = 0;
|
||||
|
||||
int selected_entrance_ = 0;
|
||||
int selected_usage_map_ = 0xFFFF;
|
||||
|
||||
char map_gfx_[3] = "";
|
||||
char map_palette_[3] = "";
|
||||
char spr_gfx_[3] = "";
|
||||
@@ -75,41 +175,77 @@ class OverworldEditor {
|
||||
char message_id_[5] = "";
|
||||
char staticgfx[16];
|
||||
|
||||
uint32_t tilemap_file_offset_high_ = 0;
|
||||
uint32_t tilemap_file_offset_low_ = 0;
|
||||
uint32_t light_maps_to_load_ = 0x51;
|
||||
uint32_t dark_maps_to_load_ = 0x2A;
|
||||
uint32_t sp_maps_to_load_ = 0x07;
|
||||
|
||||
bool opt_enable_grid = true;
|
||||
bool all_gfx_loaded_ = false;
|
||||
bool map_blockset_loaded_ = false;
|
||||
bool selected_tile_loaded_ = false;
|
||||
bool update_selected_tile_ = true;
|
||||
bool is_dragging_entrance_ = false;
|
||||
bool show_tile16_editor_ = false;
|
||||
bool show_gfx_group_editor_ = false;
|
||||
bool overworld_canvas_fullscreen_ = false;
|
||||
bool middle_mouse_dragging_ = false;
|
||||
|
||||
ImGuiTableFlags toolset_table_flags = ImGuiTableFlags_SizingFixedFit;
|
||||
ImGuiTableFlags ow_map_flags = ImGuiTableFlags_Borders;
|
||||
ImGuiTableFlags ow_edit_flags = ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_SizingStretchSame;
|
||||
bool is_dragging_entity_ = false;
|
||||
zelda3::OverworldEntity* dragged_entity_;
|
||||
zelda3::OverworldEntity* current_entity_;
|
||||
|
||||
int current_entrance_id_ = 0;
|
||||
zelda3::OverworldEntrance current_entrance_;
|
||||
int current_exit_id_ = 0;
|
||||
zelda3::OverworldExit current_exit_;
|
||||
int current_item_id_ = 0;
|
||||
zelda3::OverworldItem current_item_;
|
||||
int current_sprite_id_ = 0;
|
||||
zelda3::Sprite current_sprite_;
|
||||
|
||||
bool show_experimental = false;
|
||||
std::string ow_tilemap_filename_ = "";
|
||||
std::string tile32_configuration_filename_ = "";
|
||||
|
||||
Bytes selected_tile_data_;
|
||||
std::unordered_map<int, gfx::Bitmap> graphics_bin_;
|
||||
std::unordered_map<int, gfx::Bitmap> current_graphics_set_;
|
||||
std::unordered_map<int, gfx::Bitmap> maps_bmp_;
|
||||
std::unordered_map<int, gfx::Bitmap> sprite_previews_;
|
||||
|
||||
std::vector<Bytes> tile16_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile16_individual_;
|
||||
|
||||
ROM rom_;
|
||||
std::vector<Bytes> tile8_individual_data_;
|
||||
std::vector<gfx::Bitmap> tile8_individual_;
|
||||
|
||||
Tile16Editor tile16_editor_;
|
||||
GfxGroupEditor gfx_group_editor_;
|
||||
PaletteEditor palette_editor_;
|
||||
zelda3::Overworld overworld_;
|
||||
|
||||
gfx::SNESPalette palette_;
|
||||
gui::Canvas ow_map_canvas_{ImVec2(0x200 * 8, 0x200 * 8),
|
||||
gui::CanvasGridSize::k64x64};
|
||||
gui::Canvas current_gfx_canvas_{ImVec2(0x100 + 1, 0x10 * 0x40 + 1),
|
||||
gui::CanvasGridSize::k32x32};
|
||||
gui::Canvas blockset_canvas_{ImVec2(0x100 + 1, 0x2000 + 1),
|
||||
gui::CanvasGridSize::k32x32};
|
||||
gui::Canvas graphics_bin_canvas_{
|
||||
ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
|
||||
gui::CanvasGridSize::k16x16};
|
||||
gui::Canvas properties_canvas_;
|
||||
|
||||
gfx::SnesPalette palette_;
|
||||
gfx::Bitmap selected_tile_bmp_;
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
gfx::Bitmap current_gfx_bmp_;
|
||||
gfx::Bitmap all_gfx_bmp;
|
||||
|
||||
gui::Canvas ow_map_canvas_;
|
||||
gui::Canvas current_gfx_canvas_;
|
||||
gui::Canvas blockset_canvas_;
|
||||
gui::Canvas graphics_bin_canvas_;
|
||||
gfx::BitmapTable maps_bmp_;
|
||||
gfx::BitmapTable graphics_bin_;
|
||||
gfx::BitmapTable current_graphics_set_;
|
||||
gfx::BitmapTable sprite_previews_;
|
||||
|
||||
gfx::BitmapTable animated_maps_;
|
||||
|
||||
absl::Status status_;
|
||||
};
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
#include "palette_editor.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
absl::Status PaletteEditor::Update() {
|
||||
for (int i = 0; i < 11; ++i) {
|
||||
if (ImGui::TreeNode(kPaletteCategoryNames[i].data())) {
|
||||
auto size = rom_.GetPaletteGroup(kPaletteGroupNames[i].data()).size;
|
||||
auto palettes = rom_.GetPaletteGroup(kPaletteGroupNames[i].data());
|
||||
for (int j = 0; j < size; j++) {
|
||||
ImGui::Text("%d", j);
|
||||
auto palette = palettes[j];
|
||||
for (int n = 0; n < size; n++) {
|
||||
ImGui::PushID(n);
|
||||
if ((n % 8) != 0)
|
||||
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags =
|
||||
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker;
|
||||
if (ImGui::ColorButton("##palette", palette[n].RGB(),
|
||||
palette_button_flags, ImVec2(20, 20)))
|
||||
current_color_ =
|
||||
ImVec4(palette[n].rgb.x, palette[n].rgb.y, palette[n].rgb.z,
|
||||
current_color_.w); // Preserve alpha!
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
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;
|
||||
static ImVec4 saved_palette[256] = {};
|
||||
if (loaded && !init) {
|
||||
for (int n = 0; n < palette.size_; n++) {
|
||||
saved_palette[n].x = palette.GetColor(n).rgb.x / 255;
|
||||
saved_palette[n].y = palette.GetColor(n).rgb.y / 255;
|
||||
saved_palette[n].z = palette.GetColor(n).rgb.z / 255;
|
||||
saved_palette[n].w = 255; // Alpha
|
||||
}
|
||||
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")) {
|
||||
ImGui::Text("Current Overworld Palette");
|
||||
ImGui::Separator();
|
||||
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");
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
|
||||
ImGuiColorEditFlags_NoPicker |
|
||||
ImGuiColorEditFlags_NoTooltip;
|
||||
if (ImGui::ColorButton("##palette", saved_palette[n],
|
||||
palette_button_flags, 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();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
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"};
|
||||
|
||||
class PaletteEditor {
|
||||
public:
|
||||
absl::Status Update();
|
||||
void DisplayPalette(gfx::SNESPalette& palette, bool loaded);
|
||||
|
||||
auto SetupROM(ROM& rom) { rom_ = rom; }
|
||||
|
||||
private:
|
||||
ImVec4 current_color_;
|
||||
ROM rom_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
@@ -15,9 +15,11 @@
|
||||
#include "app/core/constants.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/icons.h"
|
||||
#include "gui/input.h"
|
||||
#include "app/gfx/tilesheet.h"
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
@@ -27,43 +29,23 @@ ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); }
|
||||
|
||||
void ScreenEditor::Update() {
|
||||
TAB_BAR("##TabBar")
|
||||
TAB_ITEM("Dungeon Maps")
|
||||
if (rom()->is_loaded()) {
|
||||
DrawDungeonMapsEditor();
|
||||
}
|
||||
END_TAB_ITEM()
|
||||
DrawInventoryMenuEditor();
|
||||
DrawOverworldMapEditor();
|
||||
DrawTitleScreenEditor();
|
||||
DrawNamingScreenEditor();
|
||||
DrawOverworldMapEditor();
|
||||
DrawDungeonMapsEditor();
|
||||
DrawMosaicEditor();
|
||||
END_TAB_BAR()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawWorldGrid(int world, int h, int w) {
|
||||
const float time = (float)ImGui::GetTime();
|
||||
|
||||
int i = 0;
|
||||
if (world == 1) {
|
||||
i = 64;
|
||||
} else if (world == 2) {
|
||||
i = 128;
|
||||
}
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++) {
|
||||
if (x > 0) ImGui::SameLine();
|
||||
ImGui::PushID(y * 4 + x);
|
||||
std::string label = absl::StrCat(" #", absl::StrFormat("%x", i));
|
||||
if (ImGui::Selectable(label.c_str(), mosaic_tiles_[i] != 0, 0,
|
||||
ImVec2(35, 25))) {
|
||||
mosaic_tiles_[i] ^= 1;
|
||||
}
|
||||
ImGui::PopID();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawInventoryMenuEditor() {
|
||||
TAB_ITEM("Inventory Menu")
|
||||
|
||||
static bool create = false;
|
||||
if (!create && rom_.isLoaded()) {
|
||||
if (!create && rom()->is_loaded()) {
|
||||
inventory_.Create();
|
||||
palette_ = inventory_.Palette();
|
||||
create = true;
|
||||
@@ -100,77 +82,6 @@ void ScreenEditor::DrawInventoryMenuEditor() {
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawTitleScreenEditor() {
|
||||
TAB_ITEM("Title Screen")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawNamingScreenEditor() {
|
||||
TAB_ITEM("Naming Screen")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawOverworldMapEditor() {
|
||||
TAB_ITEM("Overworld Map")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawDungeonMapsEditor() {
|
||||
TAB_ITEM("Dungeon Maps")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawMosaicEditor() {
|
||||
TAB_ITEM("Mosaic Transitions")
|
||||
|
||||
if (ImGui::BeginTable("Worlds", 3, ImGuiTableFlags_Borders)) {
|
||||
ImGui::TableSetupColumn("Light World");
|
||||
ImGui::TableSetupColumn("Dark World");
|
||||
ImGui::TableSetupColumn("Special World");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(0);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(1);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
DrawWorldGrid(2, 4);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
gui::InputHex("Routine Location", &overworldCustomMosaicASM);
|
||||
|
||||
if (ImGui::Button("Generate Mosaic Assembly")) {
|
||||
auto mosaic =
|
||||
rom_.PatchOverworldMosaic(mosaic_tiles_, overworldCustomMosaicASM);
|
||||
if (!mosaic.ok()) {
|
||||
std::cout << mosaic;
|
||||
}
|
||||
}
|
||||
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawToolset() {
|
||||
static bool show_bg1 = true;
|
||||
static bool show_bg2 = true;
|
||||
static bool show_bg3 = true;
|
||||
|
||||
static bool drawing_bg1 = true;
|
||||
static bool drawing_bg2 = false;
|
||||
static bool drawing_bg3 = false;
|
||||
|
||||
ImGui::Checkbox("Show BG1", &show_bg1);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Show BG2", &show_bg2);
|
||||
|
||||
ImGui::Checkbox("Draw BG1", &drawing_bg1);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Draw BG2", &drawing_bg2);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Draw BG3", &drawing_bg3);
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawInventoryToolset() {
|
||||
if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
@@ -196,6 +107,329 @@ void ScreenEditor::DrawInventoryToolset() {
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status ScreenEditor::LoadDungeonMaps() {
|
||||
std::vector<std::array<uint8_t, 25>> current_floor_rooms_d;
|
||||
std::vector<std::array<uint8_t, 25>> current_floor_gfx_d;
|
||||
int total_floors_d;
|
||||
uint8_t nbr_floor_d;
|
||||
uint8_t nbr_basement_d;
|
||||
|
||||
for (int d = 0; d < 14; d++) {
|
||||
current_floor_rooms_d.clear();
|
||||
current_floor_gfx_d.clear();
|
||||
ASSIGN_OR_RETURN(int ptr,
|
||||
rom()->ReadWord(zelda3::kDungeonMapRoomsPtr + (d * 2)));
|
||||
ASSIGN_OR_RETURN(int ptrGFX,
|
||||
rom()->ReadWord(zelda3::kDungeonMapRoomsPtr + (d * 2)));
|
||||
ptr |= 0x0A0000; // Add bank to the short ptr
|
||||
ptrGFX |= 0x0A0000; // Add bank to the short ptr
|
||||
int pcPtr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
|
||||
int pcPtrGFX =
|
||||
core::SnesToPc(ptrGFX); // Contains data for the next 25 rooms
|
||||
|
||||
ASSIGN_OR_RETURN(ushort bossRoomD,
|
||||
rom()->ReadWord(zelda3::kDungeonMapBossRooms + (d * 2)));
|
||||
|
||||
ASSIGN_OR_RETURN(nbr_basement_d,
|
||||
rom()->ReadByte(zelda3::kDungeonMapFloors + (d * 2)));
|
||||
nbr_basement_d &= 0x0F;
|
||||
|
||||
ASSIGN_OR_RETURN(nbr_floor_d,
|
||||
rom()->ReadByte(zelda3::kDungeonMapFloors + (d * 2)));
|
||||
nbr_floor_d &= 0xF0;
|
||||
nbr_floor_d = nbr_floor_d >> 4;
|
||||
|
||||
total_floors_d = nbr_basement_d + nbr_floor_d;
|
||||
|
||||
dungeon_map_labels_.emplace_back();
|
||||
|
||||
// for each floor in the dungeon
|
||||
for (int i = 0; i < total_floors_d; i++) {
|
||||
dungeon_map_labels_[d].emplace_back();
|
||||
|
||||
std::array<uint8_t, 25> rdata;
|
||||
std::array<uint8_t, 25> gdata;
|
||||
|
||||
// for each room on the floor
|
||||
for (int j = 0; j < 25; j++) {
|
||||
// rdata[j] = 0x0F;
|
||||
gdata[j] = 0xFF;
|
||||
rdata[j] = rom()->data()[pcPtr + j + (i * 25)]; // Set the rooms
|
||||
|
||||
if (rdata[j] == 0x0F) {
|
||||
gdata[j] = 0xFF;
|
||||
} else {
|
||||
gdata[j] = rom()->data()[pcPtrGFX++];
|
||||
}
|
||||
|
||||
std::string label = core::UppercaseHexByte(rdata[j]);
|
||||
dungeon_map_labels_[d][i][j] = label;
|
||||
}
|
||||
|
||||
current_floor_gfx_d.push_back(gdata); // Add new floor gfx data
|
||||
current_floor_rooms_d.push_back(rdata); // Add new floor data
|
||||
}
|
||||
|
||||
dungeon_maps_.emplace_back(bossRoomD, nbr_floor_d, nbr_basement_d,
|
||||
current_floor_rooms_d, current_floor_gfx_d);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ScreenEditor::SaveDungeonMaps() {
|
||||
for (int d = 0; d < 14; d++) {
|
||||
int ptr = zelda3::kDungeonMapRoomsPtr + (d * 2);
|
||||
int ptrGFX = zelda3::kDungeonMapGfxPtr + (d * 2);
|
||||
int pcPtr = core::SnesToPc(ptr);
|
||||
int pcPtrGFX = core::SnesToPc(ptrGFX);
|
||||
|
||||
const int nbr_floors = dungeon_maps_[d].nbr_of_floor;
|
||||
const int nbr_basements = dungeon_maps_[d].nbr_of_basement;
|
||||
for (int i = 0; i < nbr_floors + nbr_basements; i++) {
|
||||
for (int j = 0; j < 25; j++) {
|
||||
// rom()->data()[pcPtr + j + (i * 25)] =
|
||||
// dungeon_maps_[d].floor_rooms[i][j];
|
||||
// rom()->data()[pcPtrGFX++] = dungeon_maps_[d].floor_gfx[i][j];
|
||||
|
||||
RETURN_IF_ERROR(rom()->WriteByte(ptr + j + (i * 25),
|
||||
dungeon_maps_[d].floor_rooms[i][j]));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(ptrGFX + j + (i * 25),
|
||||
dungeon_maps_[d].floor_gfx[i][j]));
|
||||
pcPtrGFX++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ScreenEditor::LoadDungeonMapTile16() {
|
||||
tile16_sheet_.Init(256, 192, gfx::TileType::Tile16);
|
||||
|
||||
for (int i = 0; i < 186; i++) {
|
||||
int addr = zelda3::kDungeonMapTile16;
|
||||
if (rom()->data()[zelda3::kDungeonMapExpCheck] != 0xB9) {
|
||||
addr = zelda3::kDungeonMapTile16Expanded;
|
||||
}
|
||||
|
||||
ASSIGN_OR_RETURN(auto tl, rom()->ReadWord(addr + (i * 8)));
|
||||
gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left
|
||||
|
||||
ASSIGN_OR_RETURN(auto tr, rom()->ReadWord(addr + 2 + (i * 8)));
|
||||
gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right
|
||||
|
||||
ASSIGN_OR_RETURN(auto bl, rom()->ReadWord(addr + 4 + (i * 8)));
|
||||
gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left
|
||||
|
||||
ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8)));
|
||||
gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
|
||||
|
||||
tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (tile16_individual_.count(i) == 0) {
|
||||
auto tile = tile16_sheet_.GetTile16(i);
|
||||
tile16_individual_[i] = tile;
|
||||
rom()->RenderBitmap(&tile16_individual_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawDungeonMapsTabs() {
|
||||
auto current_dungeon = dungeon_maps_[selected_dungeon];
|
||||
if (ImGui::BeginTabBar("##DungeonMapTabs")) {
|
||||
auto nbr_floors =
|
||||
current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
|
||||
for (int i = 0; i < nbr_floors; i++) {
|
||||
std::string tab_name = absl::StrFormat("Floor %d", i + 1);
|
||||
if (i >= current_dungeon.nbr_of_floor) {
|
||||
tab_name = absl::StrFormat("Basement %d",
|
||||
i - current_dungeon.nbr_of_floor + 1);
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem(tab_name.c_str())) {
|
||||
floor_number = i;
|
||||
// screen_canvas_.LoadCustomLabels(dungeon_map_labels_[selected_dungeon]);
|
||||
// screen_canvas_.set_current_labels(floor_number);
|
||||
screen_canvas_.DrawBackground(ImVec2(325, 325));
|
||||
screen_canvas_.DrawTileSelector(64.f);
|
||||
|
||||
auto boss_room = current_dungeon.boss_room;
|
||||
for (int j = 0; j < 25; j++) {
|
||||
if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
|
||||
int tile16_id = current_dungeon.floor_rooms[floor_number][j];
|
||||
int tile_x = (tile16_id % 16) * 16;
|
||||
int tile_y = (tile16_id / 16) * 16;
|
||||
int posX = ((j % 5) * 32);
|
||||
int posY = ((j / 5) * 32);
|
||||
|
||||
if (tile16_individual_.count(tile16_id) == 0) {
|
||||
auto tile = tile16_sheet_.GetTile16(tile16_id);
|
||||
std::cout << "Tile16: " << tile16_id << std::endl;
|
||||
rom()->RenderBitmap(&tile);
|
||||
tile16_individual_[tile16_id] = tile;
|
||||
}
|
||||
screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2),
|
||||
(posY * 2), 4.0f);
|
||||
|
||||
if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
|
||||
screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64,
|
||||
64, core::kRedPen);
|
||||
}
|
||||
|
||||
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);
|
||||
screen_canvas_.DrawOverlay();
|
||||
|
||||
if (!screen_canvas_.points().empty()) {
|
||||
int x = screen_canvas_.points().front().x / 64;
|
||||
int y = screen_canvas_.points().front().y / 64;
|
||||
selected_room = x + (y * 5);
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
gui::InputHexByte(
|
||||
"Selected Room",
|
||||
¤t_dungeon.floor_rooms[floor_number].at(selected_room));
|
||||
|
||||
gui::InputHexWord("Boss Room", ¤t_dungeon.boss_room);
|
||||
|
||||
if (ImGui::Button("Copy Floor", ImVec2(100, 0))) {
|
||||
copy_button_pressed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Paste Floor", ImVec2(100, 0))) {
|
||||
paste_button_pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawDungeonMapsEditor() {
|
||||
if (!dungeon_maps_loaded_) {
|
||||
if (LoadDungeonMaps().ok()) {
|
||||
if (LoadDungeonMapTile16().ok()) {
|
||||
auto bitmap_manager = rom()->mutable_bitmap_manager();
|
||||
sheets_.emplace(0, *bitmap_manager->mutable_bitmap(212));
|
||||
sheets_.emplace(1, *bitmap_manager->mutable_bitmap(213));
|
||||
sheets_.emplace(2, *bitmap_manager->mutable_bitmap(214));
|
||||
sheets_.emplace(3, *bitmap_manager->mutable_bitmap(215));
|
||||
dungeon_maps_loaded_ = true;
|
||||
} else {
|
||||
ImGui::Text("Failed to load dungeon map tile16");
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("Failed to load dungeon maps");
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<std::string> dungeon_names = {
|
||||
"Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
|
||||
"Desert Palace", "Tower of Hera", "Agahnim's Tower",
|
||||
"Palace of Darkness", "Swamp Palace", "Skull Woods",
|
||||
"Thieves' Town", "Ice Palace", "Misery Mire",
|
||||
"Turtle Rock", "Ganon's Tower"};
|
||||
|
||||
if (ImGui::BeginTable("DungeonMapsTable", 4, ImGuiTableFlags_Resizable)) {
|
||||
ImGui::TableSetupColumn("Dungeon");
|
||||
ImGui::TableSetupColumn("Map");
|
||||
ImGui::TableSetupColumn("Rooms Gfx");
|
||||
ImGui::TableSetupColumn("Tiles Gfx");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// Dungeon column
|
||||
ImGui::TableNextColumn();
|
||||
for (int i = 0; i < dungeon_names.size(); i++) {
|
||||
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||
selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
|
||||
dungeon_names[i]);
|
||||
}
|
||||
|
||||
// Map column
|
||||
ImGui::TableNextColumn();
|
||||
DrawDungeonMapsTabs();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
|
||||
tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
|
||||
tilesheet_canvas_.DrawContextMenu();
|
||||
tilesheet_canvas_.DrawTileSelector(32.f);
|
||||
tilesheet_canvas_.DrawBitmap(*tile16_sheet_.bitmap(), 2, true);
|
||||
tilesheet_canvas_.DrawGrid(32.f);
|
||||
tilesheet_canvas_.DrawOverlay();
|
||||
|
||||
if (!tilesheet_canvas_.points().empty()) {
|
||||
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
|
||||
(tilesheet_canvas_.points().front().y / 32) * 16;
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
tilemap_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
|
||||
tilemap_canvas_.DrawContextMenu();
|
||||
tilemap_canvas_.DrawBitmapTable(sheets_);
|
||||
tilemap_canvas_.DrawGrid();
|
||||
tilemap_canvas_.DrawOverlay();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawTitleScreenEditor() {
|
||||
TAB_ITEM("Title Screen")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawNamingScreenEditor() {
|
||||
TAB_ITEM("Naming Screen")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
void ScreenEditor::DrawOverworldMapEditor() {
|
||||
TAB_ITEM("Overworld Map")
|
||||
END_TAB_ITEM()
|
||||
}
|
||||
|
||||
void ScreenEditor::DrawToolset() {
|
||||
static bool show_bg1 = true;
|
||||
static bool show_bg2 = true;
|
||||
static bool show_bg3 = true;
|
||||
|
||||
static bool drawing_bg1 = true;
|
||||
static bool drawing_bg2 = false;
|
||||
static bool drawing_bg3 = false;
|
||||
|
||||
ImGui::Checkbox("Show BG1", &show_bg1);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Show BG2", &show_bg2);
|
||||
|
||||
ImGui::Checkbox("Draw BG1", &drawing_bg1);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Draw BG2", &drawing_bg2);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Draw BG3", &drawing_bg3);
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -5,52 +5,70 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/constants.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/color.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/inventory.h"
|
||||
#include "gui/canvas.h"
|
||||
#include "gui/color.h"
|
||||
#include "gui/icons.h"
|
||||
#include "app/zelda3/screen/dungeon_map.h"
|
||||
#include "app/zelda3/screen/inventory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace editor {
|
||||
|
||||
using MosaicArray = std::array<int, core::kNumOverworldMaps>;
|
||||
static int overworldCustomMosaicASM = 0x1301D0;
|
||||
|
||||
class ScreenEditor {
|
||||
class ScreenEditor : public SharedROM {
|
||||
public:
|
||||
ScreenEditor();
|
||||
void SetupROM(ROM &rom) {
|
||||
rom_ = rom;
|
||||
inventory_.SetupROM(rom_);
|
||||
}
|
||||
void Update();
|
||||
|
||||
absl::Status SaveDungeonMaps();
|
||||
|
||||
private:
|
||||
void DrawMosaicEditor();
|
||||
void DrawTitleScreenEditor();
|
||||
void DrawNamingScreenEditor();
|
||||
void DrawOverworldMapEditor();
|
||||
void DrawDungeonMapsEditor();
|
||||
void DrawInventoryMenuEditor();
|
||||
|
||||
void DrawInventoryMenuEditor();
|
||||
void DrawToolset();
|
||||
void DrawInventoryToolset();
|
||||
void DrawWorldGrid(int world, int h = 8, int w = 8);
|
||||
|
||||
char mosaic_tiles_[core::kNumOverworldMaps];
|
||||
absl::Status LoadDungeonMaps();
|
||||
absl::Status LoadDungeonMapTile16();
|
||||
void DrawDungeonMapsTabs();
|
||||
void DrawDungeonMapsEditor();
|
||||
|
||||
std::vector<zelda3::DungeonMap> dungeon_maps_;
|
||||
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
|
||||
|
||||
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
|
||||
|
||||
bool dungeon_maps_loaded_ = false;
|
||||
|
||||
int selected_tile16_ = 0;
|
||||
int selected_dungeon = 0;
|
||||
uint8_t selected_room = 0;
|
||||
uint8_t boss_room = 0;
|
||||
int floor_number = 1;
|
||||
|
||||
bool copy_button_pressed = false;
|
||||
bool paste_button_pressed = false;
|
||||
|
||||
ROM rom_;
|
||||
Bytes all_gfx_;
|
||||
zelda3::Inventory inventory_;
|
||||
gfx::SNESPalette palette_;
|
||||
gfx::SnesPalette palette_;
|
||||
gui::Canvas screen_canvas_;
|
||||
gui::Canvas tilesheet_canvas_;
|
||||
gui::Canvas tilemap_canvas_;
|
||||
|
||||
gfx::BitmapTable sheets_;
|
||||
|
||||
gfx::Tilesheet tile16_sheet_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
||||
69
src/app/editor/sprite_editor.cc
Normal file
69
src/app/editor/sprite_editor.cc
Normal file
@@ -0,0 +1,69 @@
|
||||
#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
|
||||
29
src/app/editor/sprite_editor.h
Normal file
29
src/app/editor/sprite_editor.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#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 : public SharedROM {
|
||||
public:
|
||||
absl::Status Update();
|
||||
|
||||
private:
|
||||
void DrawEditorTable();
|
||||
void DrawSpriteCanvas();
|
||||
void DrawCurrentSheets();
|
||||
|
||||
uint8_t current_sheets_[8];
|
||||
bool sheets_loaded_ = false;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||
139
src/app/emu/audio/apu.cc
Normal file
139
src/app/emu/audio/apu.cc
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "app/emu/audio/apu.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#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 {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
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); });
|
||||
}
|
||||
|
||||
void APU::Reset() {
|
||||
clock_.ResetAccumulatedTime();
|
||||
spc700_.Reset();
|
||||
dsp_.Reset();
|
||||
}
|
||||
|
||||
void APU::Update() {
|
||||
auto cycles_to_run = clock_.GetCycleCount();
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
ProcessSamples();
|
||||
}
|
||||
|
||||
void APU::Notify(uint32_t address, uint8_t data) {
|
||||
if (address < 0x2140 || address > 0x2143) {
|
||||
return;
|
||||
}
|
||||
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();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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::FetchSampleForVoice(uint8_t voice_num) {
|
||||
uint16_t address = CalculateAddressForVoice(voice_num);
|
||||
return aram_.read(address);
|
||||
}
|
||||
|
||||
uint16_t APU::CalculateAddressForVoice(uint8_t voice_num) {
|
||||
// TODO: Calculate the address for the specified voice
|
||||
return voice_num;
|
||||
}
|
||||
|
||||
int16_t APU::GetNextSample() {
|
||||
if (!audio_samples_.empty()) {
|
||||
int16_t sample = audio_samples_.front();
|
||||
audio_samples_.erase(audio_samples_.begin());
|
||||
return sample;
|
||||
}
|
||||
return 0; // TODO: Return the last sample instead of 0.
|
||||
}
|
||||
|
||||
const std::vector<int16_t>& APU::GetAudioSamples() const {
|
||||
return audio_samples_;
|
||||
}
|
||||
|
||||
void APU::UpdateChannelSettings() {
|
||||
// TODO: Implement this method to update the channel settings.
|
||||
}
|
||||
|
||||
int16_t APU::GenerateSample(int channel) {
|
||||
// TODO: Implement this method to generate a sample for the specified channel.
|
||||
}
|
||||
|
||||
void APU::ApplyEnvelope(int channel) {
|
||||
// TODO: Implement this method to apply an envelope to the specified channel.
|
||||
}
|
||||
|
||||
uint8_t APU::ReadDspMemory(uint16_t address) {
|
||||
return dsp_.ReadGlobalReg(address);
|
||||
}
|
||||
|
||||
void APU::WriteDspMemory(uint16_t address, uint8_t value) {
|
||||
dsp_.WriteGlobalReg(address, value);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
137
src/app/emu/audio/apu.h
Normal file
137
src/app/emu/audio/apu.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#ifndef YAZE_APP_EMU_APU_H_
|
||||
#define YAZE_APP_EMU_APU_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#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 {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
/**
|
||||
*
|
||||
* 64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700.
|
||||
* Some regions of this space are overlaid with special hardware functions.
|
||||
*
|
||||
* Range Note
|
||||
* $0000-00EF Zero Page RAM
|
||||
* $00F0-00FF Sound CPU Registers
|
||||
* $0100-01FF Stack Page RAM
|
||||
* $0200-FFBF RAM
|
||||
* $FFC0-FFFF IPL ROM or RAM
|
||||
*
|
||||
* The region at $FFC0-FFFF will normally read from the 64-byte IPL ROM, but the
|
||||
* underlying RAM can always be written to, and the high bit of the Control
|
||||
* register $F1 can be cleared to unmap the IPL ROM and allow read access to
|
||||
* this RAM.
|
||||
*
|
||||
*/
|
||||
|
||||
const int kApuClockSpeed = 1024000; // 1.024 MHz
|
||||
const int apuSampleRate = 32000; // 32 KHz
|
||||
const int apuClocksPerSample = 64; // 64 clocks per sample
|
||||
|
||||
class APU : public Observer {
|
||||
public:
|
||||
APU(MemoryImpl &memory, AudioRam &aram, Clock &clock)
|
||||
: aram_(aram), clock_(clock), 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();
|
||||
|
||||
// Called upon a reset
|
||||
void Initialize() {
|
||||
spc700_.Reset();
|
||||
dsp_.Reset();
|
||||
SignalReady();
|
||||
}
|
||||
|
||||
// Set Port 0 = $AA and Port 1 = $BB
|
||||
void SignalReady() {
|
||||
memory_.WriteByte(0x2140, READY_SIGNAL_0);
|
||||
memory_.WriteByte(0x2141, READY_SIGNAL_1);
|
||||
}
|
||||
|
||||
void WriteToPort(uint8_t portNum, uint8_t value) {
|
||||
ports_[portNum] = value;
|
||||
switch (portNum) {
|
||||
case 0:
|
||||
memory_.WriteByte(0x2140, value);
|
||||
break;
|
||||
case 1:
|
||||
memory_.WriteByte(0x2141, value);
|
||||
break;
|
||||
case 2:
|
||||
memory_.WriteByte(0x2142, value);
|
||||
break;
|
||||
case 3:
|
||||
memory_.WriteByte(0x2143, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); }
|
||||
|
||||
// Method to fetch a sample from AudioRam
|
||||
uint8_t FetchSampleFromRam(uint16_t address) const {
|
||||
return aram_.read(address);
|
||||
}
|
||||
|
||||
// Method to push a processed sample to the audio buffer
|
||||
void PushToAudioBuffer(int16_t sample) { audio_samples_.push_back(sample); }
|
||||
|
||||
// Returns the audio samples for the current frame
|
||||
const std::vector<int16_t> &GetAudioSamples() const;
|
||||
|
||||
private:
|
||||
// Constants for communication
|
||||
static const uint8_t READY_SIGNAL_0 = 0xAA;
|
||||
static const uint8_t READY_SIGNAL_1 = 0xBB;
|
||||
static const uint8_t BEGIN_SIGNAL = 0xCC;
|
||||
|
||||
// Port buffers (equivalent to $2140 to $2143 for the main CPU)
|
||||
uint8_t ports_[4] = {0};
|
||||
|
||||
// Updates internal state based on APU register settings
|
||||
void UpdateChannelSettings();
|
||||
|
||||
// Generates a sample for an audio channel
|
||||
int16_t GenerateSample(int channel);
|
||||
|
||||
// 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_;
|
||||
|
||||
DigitalSignalProcessor dsp_;
|
||||
Spc700 spc700_{aram_};
|
||||
std::vector<int16_t> audio_samples_;
|
||||
|
||||
std::function<void()> ready_callback_;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif
|
||||
281
src/app/emu/audio/dsp.cc
Normal file
281
src/app/emu/audio/dsp.cc
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "app/emu/audio/dsp.h"
|
||||
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void DigitalSignalProcessor::Reset() {}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value) {
|
||||
voice %= kNumVoices;
|
||||
switch (reg % kNumVoiceRegs) {
|
||||
case 0:
|
||||
voices_[voice].vol_left = static_cast<int8_t>(value);
|
||||
break;
|
||||
case 1:
|
||||
voices_[voice].vol_right = static_cast<int8_t>(value);
|
||||
break;
|
||||
case 2:
|
||||
voices_[voice].pitch_low = value;
|
||||
break;
|
||||
case 3:
|
||||
voices_[voice].pitch_high = value;
|
||||
break;
|
||||
case 4:
|
||||
voices_[voice].source_number = value;
|
||||
break;
|
||||
case 5:
|
||||
voices_[voice].adsr1 = value;
|
||||
break;
|
||||
case 6:
|
||||
voices_[voice].adsr2 = value;
|
||||
break;
|
||||
case 7:
|
||||
voices_[voice].gain = value;
|
||||
break;
|
||||
// Note: envx and outx are read-only, so they don't have cases here
|
||||
}
|
||||
}
|
||||
|
||||
// Set the callbacks
|
||||
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) { sample_fetcher_ = fetcher; }
|
||||
|
||||
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) { sample_pusher_ = pusher; }
|
||||
|
||||
int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) {
|
||||
Voice const& voice = voices_[voice_num];
|
||||
uint16_t sample_address = voice.source_number;
|
||||
|
||||
// Use the callback to fetch the sample
|
||||
int16_t sample = static_cast<int16_t>(sample_fetcher_(sample_address) << 8);
|
||||
return sample;
|
||||
}
|
||||
|
||||
int16_t DigitalSignalProcessor::ProcessSample(uint8_t voice_num, int16_t sample) {
|
||||
Voice const& voice = voices_[voice_num];
|
||||
|
||||
// Adjust the pitch (for simplicity, we're just adjusting the sample value)
|
||||
sample += voice.pitch_low + (voice.pitch_high << 8);
|
||||
|
||||
// Apply volume (separate for left and right for stereo sound)
|
||||
int16_t left_sample = (sample * voice.vol_left) / 255;
|
||||
int16_t right_sample = (sample * voice.vol_right) / 255;
|
||||
|
||||
// Combine stereo samples into a single 16-bit value
|
||||
return (left_sample + right_sample) / 2;
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::MixSamples() {
|
||||
int16_t mixed_sample = 0;
|
||||
|
||||
for (uint8_t i = 0; i < kNumVoices; i++) {
|
||||
int16_t decoded_sample = DecodeSample(i);
|
||||
int16_t processed_sample = ProcessSample(i, decoded_sample);
|
||||
mixed_sample += processed_sample;
|
||||
}
|
||||
|
||||
// Clamp the mixed sample to 16-bit range
|
||||
if (mixed_sample > 32767) {
|
||||
mixed_sample = 32767;
|
||||
} else if (mixed_sample < -32768) {
|
||||
mixed_sample = -32768;
|
||||
}
|
||||
|
||||
// Use the callback to push the mixed sample
|
||||
sample_pusher_(mixed_sample);
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) {
|
||||
uint8_t adsr1 = ReadVoiceReg(voice, 0x05);
|
||||
uint8_t adsr2 = ReadVoiceReg(voice, 0x06);
|
||||
uint8_t gain = ReadVoiceReg(voice, 0x07);
|
||||
|
||||
uint8_t enableADSR = (adsr1 & 0x80) >> 7;
|
||||
|
||||
if (enableADSR) {
|
||||
// Handle ADSR envelope
|
||||
Voice& voice_obj = voices_[voice];
|
||||
switch (voice_obj.state) {
|
||||
case VoiceState::ATTACK:
|
||||
// Update amplitude based on attack rate
|
||||
voice_obj.current_amplitude += AttackRate(adsr1);
|
||||
if (voice_obj.current_amplitude >= ENVELOPE_MAX) {
|
||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
||||
voice_obj.state = VoiceState::DECAY;
|
||||
}
|
||||
break;
|
||||
case VoiceState::DECAY:
|
||||
// Update amplitude based on decay rate
|
||||
voice_obj.current_amplitude -= DecayRate(adsr2);
|
||||
if (voice_obj.current_amplitude <= voice_obj.decay_level) {
|
||||
voice_obj.current_amplitude = voice_obj.decay_level;
|
||||
voice_obj.state = VoiceState::SUSTAIN;
|
||||
}
|
||||
break;
|
||||
case VoiceState::SUSTAIN:
|
||||
// Keep amplitude at the calculated decay level
|
||||
voice_obj.current_amplitude = voice_obj.decay_level;
|
||||
break;
|
||||
case VoiceState::RELEASE:
|
||||
// Update amplitude based on release rate
|
||||
voice_obj.current_amplitude -= ReleaseRate(adsr2);
|
||||
if (voice_obj.current_amplitude <= 0) {
|
||||
voice_obj.current_amplitude = 0;
|
||||
voice_obj.state = VoiceState::OFF;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Handle Gain envelope
|
||||
// Extract mode from the gain byte
|
||||
uint8_t mode = (gain & 0xE0) >> 5;
|
||||
uint8_t rate = gain & 0x1F;
|
||||
|
||||
Voice& voice_obj = voices_[voice];
|
||||
|
||||
switch (mode) {
|
||||
case 0: // Direct Designation
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
voice_obj.current_amplitude =
|
||||
rate << 3; // Multiplying by 8 to scale to 0-255
|
||||
break;
|
||||
|
||||
case 6: // Increase Mode (Linear)
|
||||
voice_obj.current_amplitude += gainTimings[0][rate];
|
||||
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
|
||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
||||
}
|
||||
break;
|
||||
|
||||
case 7: // Increase Mode (Bent Line)
|
||||
// Hypothetical behavior: Increase linearly at first, then increase
|
||||
// more slowly You'll likely need to adjust this based on your
|
||||
// specific requirements
|
||||
if (voice_obj.current_amplitude < (ENVELOPE_MAX / 2)) {
|
||||
voice_obj.current_amplitude += gainTimings[1][rate];
|
||||
} else {
|
||||
voice_obj.current_amplitude += gainTimings[1][rate] / 2;
|
||||
}
|
||||
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
|
||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // Decrease Mode (Linear)
|
||||
if (voice_obj.current_amplitude < gainTimings[2][rate]) {
|
||||
voice_obj.current_amplitude = 0;
|
||||
} else {
|
||||
voice_obj.current_amplitude -= gainTimings[2][rate];
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // Decrease Mode (Exponential)
|
||||
voice_obj.current_amplitude -=
|
||||
(voice_obj.current_amplitude * gainTimings[3][rate]) / ENVELOPE_MAX;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Default behavior can be handled here if necessary
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::update_voice_state(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
switch (voice.state) {
|
||||
case VoiceState::OFF:
|
||||
// Reset current amplitude
|
||||
voice.current_amplitude = 0;
|
||||
break;
|
||||
|
||||
case VoiceState::ATTACK:
|
||||
// Increase the current amplitude at a rate defined by the ATTACK
|
||||
// setting
|
||||
voice.current_amplitude += AttackRate(voice.adsr1);
|
||||
if (voice.current_amplitude >= ENVELOPE_MAX) {
|
||||
voice.current_amplitude = ENVELOPE_MAX;
|
||||
voice.state = VoiceState::DECAY;
|
||||
voice.decay_level = CalculateDecayLevel(voice.adsr2);
|
||||
}
|
||||
break;
|
||||
|
||||
case VoiceState::DECAY:
|
||||
// Decrease the current amplitude at a rate defined by the DECAY setting
|
||||
voice.current_amplitude -= DecayRate(voice.adsr2);
|
||||
if (voice.current_amplitude <= voice.decay_level) {
|
||||
voice.current_amplitude = voice.decay_level;
|
||||
voice.state = VoiceState::SUSTAIN;
|
||||
}
|
||||
break;
|
||||
|
||||
case VoiceState::SUSTAIN:
|
||||
// Keep the current amplitude at the decay level
|
||||
break;
|
||||
|
||||
case VoiceState::RELEASE:
|
||||
// Decrease the current amplitude at a rate defined by the RELEASE
|
||||
// setting
|
||||
voice.current_amplitude -= ReleaseRate(voice.adsr2);
|
||||
if (voice.current_amplitude == 0) {
|
||||
voice.state = VoiceState::OFF;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::process_envelope(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
|
||||
// Update the voice state first (based on keys, etc.)
|
||||
update_voice_state(voice_num);
|
||||
|
||||
// Calculate the envelope value based on the current amplitude
|
||||
voice.envx = calculate_envelope_value(voice.current_amplitude);
|
||||
|
||||
// Apply the envelope value to the audio output
|
||||
apply_envelope_to_output(voice_num);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
315
src/app/emu/audio/dsp.h
Normal file
315
src/app/emu/audio/dsp.h
Normal file
@@ -0,0 +1,315 @@
|
||||
#ifndef YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||
#define YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
using SampleFetcher = std::function<uint8_t(uint16_t)>;
|
||||
using SamplePusher = std::function<void(int16_t)>;
|
||||
|
||||
/**
|
||||
*
|
||||
* The S-DSP is a digital signal processor generating the sound data.
|
||||
*
|
||||
* A DSP register can be selected with $F2, after which it can be read or
|
||||
* written at $F3. Often it is useful to load the register address into A, and
|
||||
* the value to send in Y, so that MOV $F2, YA can be used to do both in one
|
||||
* 16-bit instruction.
|
||||
*
|
||||
* The DSP register address space only has 7 bits. The high bit of $F2, if set,
|
||||
* will make the selected register read-only via $F3.
|
||||
*
|
||||
* When initializing the DSP registers for the first time, take care not to
|
||||
* accidentally enable echo writeback via FLG, because it will immediately begin
|
||||
* overwriting values in RAM.
|
||||
*
|
||||
* Voices
|
||||
* There are 8 voices, numbered 0 to 7.
|
||||
* Each voice X has 10 registers in the range $X0-$X9.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
class DigitalSignalProcessor {
|
||||
private:
|
||||
static const size_t kNumVoices = 8;
|
||||
static const size_t kNumVoiceRegs = 10;
|
||||
static const size_t kNumGlobalRegs = 15;
|
||||
|
||||
enum class VoiceState { OFF, ATTACK, DECAY, SUSTAIN, RELEASE };
|
||||
|
||||
struct Voice {
|
||||
int8_t vol_left; // x0
|
||||
int8_t vol_right; // x1
|
||||
uint8_t pitch_low; // x2
|
||||
uint8_t pitch_high; // x3
|
||||
uint8_t source_number; // x4
|
||||
uint8_t adsr1; // x5
|
||||
uint8_t adsr2; // x6
|
||||
uint8_t gain; // x7
|
||||
uint8_t envx; // x8 (read-only)
|
||||
int8_t outx; // x9 (read-only)
|
||||
|
||||
VoiceState state = VoiceState::OFF;
|
||||
uint16_t current_amplitude = 0; // Current amplitude value used for ADSR
|
||||
uint16_t decay_level; // Calculated decay level based on ADSR settings
|
||||
};
|
||||
Voice voices_[8];
|
||||
|
||||
// Global DSP registers
|
||||
uint8_t mvol_left; // 0C
|
||||
uint8_t mvol_right; // 0D
|
||||
uint8_t evol_left; // 0E
|
||||
uint8_t evol_right; // 0F
|
||||
uint8_t kon; // 10
|
||||
uint8_t koff; // 11
|
||||
uint8_t flags; // 12
|
||||
uint8_t endx; // 13 (read-only)
|
||||
|
||||
// Global registers
|
||||
std::vector<uint8_t> globalRegs = std::vector<uint8_t>(kNumGlobalRegs, 0x00);
|
||||
|
||||
static const uint16_t ENVELOPE_MAX = 2047; // $7FF
|
||||
|
||||
// Attack times in ms
|
||||
const std::vector<uint32_t> attackTimes = {
|
||||
4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 0};
|
||||
|
||||
// Decay times in ms
|
||||
const std::vector<uint32_t> decayTimes = {1200, 740, 440, 290,
|
||||
180, 110, 74, 37};
|
||||
|
||||
// Release times in ms
|
||||
const std::vector<uint32_t> releaseTimes = {
|
||||
// "Infinite" is represented by a large value, e.g., UINT32_MAX
|
||||
UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
|
||||
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
|
||||
1200, 880, 740, 590, 440, 370, 290, 220,
|
||||
180, 150, 110, 92, 74, 55, 37, 18};
|
||||
|
||||
// Gain timings for decrease linear, decrease exponential, etc.
|
||||
// Organized by mode: [Linear Increase, Bentline Increase, Linear Decrease,
|
||||
// Exponential Decrease]
|
||||
const std::vector<std::vector<uint32_t>> gainTimings = {
|
||||
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
|
||||
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
|
||||
24, 20, 16, 12, 10, 8, 6, 4, 2},
|
||||
{UINT32_MAX, 5400, 4600, 3500, 2600, 2300, 1800, 1300, 1100, 900,
|
||||
670, 560, 450, 340, 280, 220, 170, 140, 110, 84,
|
||||
70, 56, 42, 35, 28, 21, 18, 14, 11, 7,
|
||||
/*3.5=*/3},
|
||||
// Repeating the Linear Increase timings for Linear Decrease, since they
|
||||
// are the same.
|
||||
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
|
||||
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
|
||||
24, 20, 16, 12, 10, 8, 6, 4, 2},
|
||||
{UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
|
||||
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
|
||||
1200, 880, 740, 590, 440, 370, 290, 220,
|
||||
180, 150, 110, 92, 55, 37, 18}};
|
||||
|
||||
// DSP Period Table
|
||||
const std::vector<std::vector<uint16_t>> DigitalSignalProcessorPeriodTable = {
|
||||
// ... Your DSP period table here ...
|
||||
};
|
||||
|
||||
// DSP Period Offset
|
||||
const std::vector<uint16_t> DigitalSignalProcessorPeriodOffset = {
|
||||
// ... Your DSP period offsets here ...
|
||||
};
|
||||
|
||||
uint8_t calculate_envelope_value(uint16_t amplitude) const {
|
||||
// Convert the 16-bit amplitude to an 8-bit envelope value
|
||||
return amplitude >> 8;
|
||||
}
|
||||
|
||||
void apply_envelope_to_output(uint8_t voice_num) {
|
||||
Voice& voice = voices_[voice_num];
|
||||
|
||||
// Scale the OUTX by the envelope value
|
||||
// This might be a linear scaling, or more complex operations can be used
|
||||
voice.outx = (voice.outx * voice.envx) / 255;
|
||||
}
|
||||
|
||||
SampleFetcher sample_fetcher_;
|
||||
SamplePusher sample_pusher_;
|
||||
|
||||
public:
|
||||
DigitalSignalProcessor() = default;
|
||||
|
||||
void Reset();
|
||||
|
||||
void SetSampleFetcher(std::function<uint8_t(uint16_t)> fetcher);
|
||||
void SetSamplePusher(std::function<void(int16_t)> pusher);
|
||||
|
||||
// Read a byte from a voice register
|
||||
uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const;
|
||||
|
||||
// Write a byte to a voice register
|
||||
void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value);
|
||||
|
||||
// Read a byte from a global register
|
||||
uint8_t ReadGlobalReg(uint8_t reg) const {
|
||||
return globalRegs[reg % kNumGlobalRegs];
|
||||
}
|
||||
|
||||
// Write a byte to a global register
|
||||
void WriteGlobalReg(uint8_t reg, uint8_t value) {
|
||||
globalRegs[reg % kNumGlobalRegs] = value;
|
||||
}
|
||||
|
||||
int16_t DecodeSample(uint8_t voice_num);
|
||||
int16_t ProcessSample(uint8_t voice_num, int16_t sample);
|
||||
void MixSamples();
|
||||
|
||||
// Trigger a voice to start playing
|
||||
void trigger_voice(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
voice.state = VoiceState::ATTACK;
|
||||
// Initialize other state management variables if needed
|
||||
}
|
||||
|
||||
// Release a voice (e.g., note release in ADSR)
|
||||
void release_voice(uint8_t voice_num) {
|
||||
if (voice_num >= kNumVoices) return;
|
||||
|
||||
Voice& voice = voices_[voice_num];
|
||||
if (voice.state != VoiceState::OFF) {
|
||||
voice.state = VoiceState::RELEASE;
|
||||
}
|
||||
// Update other state management variables if needed
|
||||
}
|
||||
|
||||
// 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);
|
||||
};
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||
94
src/app/emu/audio/internal/addressing.cc
Normal file
94
src/app/emu/audio/internal/addressing.cc
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "app/emu/audio/spc700.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
// Immediate
|
||||
uint8_t Spc700::imm() {
|
||||
PC++;
|
||||
return read(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;
|
||||
}
|
||||
|
||||
// Direct page indexed by X
|
||||
uint8_t Spc700::dp_plus_x() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset + X);
|
||||
}
|
||||
|
||||
// Direct page indexed by Y
|
||||
uint8_t Spc700::dp_plus_y() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset + Y);
|
||||
}
|
||||
|
||||
// Indexed indirect (add index before 16-bit lookup).
|
||||
uint16_t Spc700::dp_plus_x_indirect() {
|
||||
PC++;
|
||||
uint16_t addr = read_16(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);
|
||||
return offset + Y;
|
||||
}
|
||||
|
||||
uint16_t Spc700::abs() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
return addr;
|
||||
}
|
||||
|
||||
int8_t Spc700::rel() {
|
||||
PC++;
|
||||
return static_cast<int8_t>(read(PC));
|
||||
}
|
||||
|
||||
uint8_t Spc700::i() { return read((PSW.P << 8) + X); }
|
||||
|
||||
uint8_t Spc700::i_postinc() {
|
||||
uint8_t value = read((PSW.P << 8) + X);
|
||||
X++;
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t Spc700::addr_plus_i() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
return read(addr) + X;
|
||||
}
|
||||
|
||||
uint16_t Spc700::addr_plus_i_indexed() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
addr += X;
|
||||
return read(addr) | (read(addr + 1) << 8);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
361
src/app/emu/audio/internal/instructions.cc
Normal file
361
src/app/emu/audio/internal/instructions.cc
Normal file
@@ -0,0 +1,361 @@
|
||||
#include "app/emu/audio/spc700.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void Spc700::MOV(uint8_t& dest, uint8_t operand) {
|
||||
dest = operand;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
|
||||
write(address, operand);
|
||||
PSW.Z = (operand == 0);
|
||||
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);
|
||||
PSW.C = (result > 0xFF);
|
||||
PSW.Z = ((result & 0xFF) == 0);
|
||||
PSW.N = (result & 0x80);
|
||||
PSW.H = ((A ^ operand ^ result) & 0x10);
|
||||
dest = result & 0xFF;
|
||||
}
|
||||
|
||||
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);
|
||||
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::OR(uint8_t& dest, uint8_t operand) {
|
||||
dest |= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::EOR(uint8_t& dest, uint8_t operand) {
|
||||
dest ^= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 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::LSR(uint8_t& operand) {
|
||||
PSW.C = (operand & 0x01);
|
||||
operand >>= 1;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 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::XCN(uint8_t operand, bool isImmediate) {
|
||||
uint8_t value = isImmediate ? imm() : operand;
|
||||
value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);
|
||||
PSW.Z = (value == 0);
|
||||
PSW.N = (value & 0x80);
|
||||
// operand = value;
|
||||
}
|
||||
|
||||
void Spc700::INC(uint8_t& operand) {
|
||||
operand++;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::DEC(uint8_t& operand) {
|
||||
operand--;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::MOVW(uint16_t& dest, uint16_t operand) {
|
||||
dest = operand;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::INCW(uint16_t& operand) {
|
||||
operand++;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::DECW(uint16_t& operand) {
|
||||
operand--;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::ADDW(uint16_t& dest, uint16_t operand) {
|
||||
uint32_t result = dest + operand;
|
||||
PSW.C = (result > 0xFFFF);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000);
|
||||
dest = result & 0xFFFF;
|
||||
}
|
||||
|
||||
void Spc700::SUBW(uint16_t& dest, uint16_t operand) {
|
||||
uint32_t result = dest - operand;
|
||||
PSW.C = (result < 0x10000);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000);
|
||||
dest = result & 0xFFFF;
|
||||
}
|
||||
|
||||
void Spc700::CMPW(uint16_t operand) {
|
||||
uint32_t result = YA - operand;
|
||||
PSW.C = (result < 0x10000);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::MUL(uint8_t operand) {
|
||||
uint16_t result = A * operand;
|
||||
YA = result;
|
||||
PSW.Z = (result == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
}
|
||||
|
||||
void Spc700::DIV(uint8_t operand) {
|
||||
if (operand == 0) {
|
||||
// Handle divide by zero error
|
||||
return;
|
||||
}
|
||||
uint8_t quotient = A / operand;
|
||||
uint8_t remainder = A % operand;
|
||||
A = quotient;
|
||||
Y = remainder;
|
||||
PSW.Z = (quotient == 0);
|
||||
PSW.N = (quotient & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::BRA(int8_t offset) { PC += offset; }
|
||||
|
||||
void Spc700::BEQ(int8_t offset) {
|
||||
if (PSW.Z) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BNE(int8_t offset) {
|
||||
if (!PSW.Z) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BCS(int8_t offset) {
|
||||
if (PSW.C) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BCC(int8_t offset) {
|
||||
if (!PSW.C) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BVS(int8_t offset) {
|
||||
if (PSW.V) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BVC(int8_t offset) {
|
||||
if (!PSW.V) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BMI(int8_t offset) {
|
||||
if (PSW.N) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BPL(int8_t offset) {
|
||||
if (!PSW.N) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BBS(uint8_t bit, uint8_t operand) {
|
||||
if (operand & (1 << bit)) {
|
||||
PC += rel();
|
||||
}
|
||||
}
|
||||
|
||||
void Spc700::BBC(uint8_t bit, uint8_t operand) {
|
||||
if (!(operand & (1 << bit))) {
|
||||
PC += rel();
|
||||
}
|
||||
}
|
||||
|
||||
// CBNE DBNZ
|
||||
// JMP
|
||||
void Spc700::JMP(uint16_t address) { PC = address; }
|
||||
|
||||
void Spc700::CALL(uint16_t address) {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC = address;
|
||||
}
|
||||
|
||||
void Spc700::PCALL(uint8_t offset) {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC += offset;
|
||||
}
|
||||
|
||||
void Spc700::TCALL(uint8_t offset) {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC = 0xFFDE + offset;
|
||||
}
|
||||
|
||||
void Spc700::BRK() {
|
||||
uint16_t return_address = PC + 2;
|
||||
write(SP, return_address & 0xFF);
|
||||
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||
SP -= 2;
|
||||
PC = 0xFFDE;
|
||||
}
|
||||
|
||||
void Spc700::RET() {
|
||||
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
||||
SP += 2;
|
||||
PC = return_address;
|
||||
}
|
||||
|
||||
void Spc700::RETI() {
|
||||
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
||||
SP += 2;
|
||||
PC = return_address;
|
||||
PSW.I = 1;
|
||||
}
|
||||
|
||||
void Spc700::PUSH(uint8_t operand) {
|
||||
write(SP, operand);
|
||||
SP--;
|
||||
}
|
||||
|
||||
void Spc700::POP(uint8_t& operand) {
|
||||
SP++;
|
||||
operand = read(SP);
|
||||
}
|
||||
|
||||
void Spc700::SET1(uint8_t bit, uint8_t& operand) { operand |= (1 << bit); }
|
||||
|
||||
void Spc700::CLR1(uint8_t bit, uint8_t& operand) { operand &= ~(1 << bit); }
|
||||
|
||||
void Spc700::TSET1(uint8_t bit, uint8_t& operand) {
|
||||
PSW.C = (operand & (1 << bit));
|
||||
operand |= (1 << bit);
|
||||
}
|
||||
|
||||
void Spc700::TCLR1(uint8_t bit, uint8_t& operand) {
|
||||
PSW.C = (operand & (1 << bit));
|
||||
operand &= ~(1 << bit);
|
||||
}
|
||||
|
||||
void Spc700::AND1(uint8_t bit, uint8_t& operand) {
|
||||
operand &= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::OR1(uint8_t bit, uint8_t& operand) {
|
||||
operand |= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::EOR1(uint8_t bit, uint8_t& operand) {
|
||||
operand ^= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::NOT1(uint8_t bit, uint8_t& operand) {
|
||||
operand ^= (1 << bit);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void Spc700::MOV1(uint8_t bit, uint8_t& operand) {
|
||||
PSW.C = (operand & (1 << bit));
|
||||
operand |= (1 << bit);
|
||||
}
|
||||
|
||||
void Spc700::CLRC() { PSW.C = 0; }
|
||||
|
||||
void Spc700::SETC() { PSW.C = 1; }
|
||||
|
||||
void Spc700::NOTC() { PSW.C = !PSW.C; }
|
||||
|
||||
void Spc700::CLRV() { PSW.V = 0; }
|
||||
|
||||
void Spc700::CLRP() { PSW.P = 0; }
|
||||
|
||||
void Spc700::SETP() { PSW.P = 1; }
|
||||
|
||||
void Spc700::EI() { PSW.I = 1; }
|
||||
|
||||
void Spc700::DI() { PSW.I = 0; }
|
||||
|
||||
void Spc700::NOP() { PC++; }
|
||||
|
||||
void Spc700::SLEEP() {}
|
||||
|
||||
void Spc700::STOP() {}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
263
src/app/emu/audio/internal/opcodes.h
Normal file
263
src/app/emu/audio/internal/opcodes.h
Normal file
@@ -0,0 +1,263 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
const std::unordered_map<uint8_t, std::string> spc_opcode_map = {
|
||||
{0x00, "NOP"},
|
||||
{0x01, "TCALL0"},
|
||||
{0x02, "SET1 direct.0"},
|
||||
{0x03, "BBS direct.0,rel"},
|
||||
{0x04, "OR A,direct"},
|
||||
{0x05, "OR A,abs"},
|
||||
{0x06, "OR A,(X)"},
|
||||
{0x07, "OR A,(direct+X)"},
|
||||
{0x08, "OR A,#imm"},
|
||||
{0x09, "OR direct,imm"},
|
||||
{0x0A, "OR1 C,membit"},
|
||||
{0x0B, "ASL direct"},
|
||||
{0x0C, "ASL abs"},
|
||||
{0x0D, "PUSH PSW"},
|
||||
{0x0E, "TSET1 abs"},
|
||||
{0x0F, "BRK"},
|
||||
{0x10, "BPL rel"},
|
||||
{0x11, "TCALL1"},
|
||||
{0x12, "CLR1 direct.0"},
|
||||
{0x13, "BBC direct.0,rel"},
|
||||
{0x14, "OR A,direct+X"},
|
||||
{0x15, "OR A,abs+X"},
|
||||
{0x16, "OR A,abs+Y"},
|
||||
{0x17, "OR A,(direct)+Y"},
|
||||
{0x18, "OR direct,direct"},
|
||||
{0x19, "OR (X),(Y)"},
|
||||
{0x1A, "DECW direct"},
|
||||
{0x1B, "ASL direct+X"},
|
||||
{0x1C, "ASL A"},
|
||||
{0x1D, "DEC X"},
|
||||
{0x1E, "CMP X,abs"},
|
||||
{0x1F, "JMP (abs+X)"},
|
||||
{0x20, "CLRP"},
|
||||
{0x21, "TCALL2"},
|
||||
{0x22, "SET1 direct.1"},
|
||||
{0x23, "BBS direct.1,rel"},
|
||||
{0x24, "AND A,direct"},
|
||||
{0x25, "AND A,abs"},
|
||||
{0x26, "AND A,(X)"},
|
||||
{0x27, "AND A,(direct+X)"},
|
||||
{0x28, "AND A,#imm"},
|
||||
{0x29, "AND direct,imm"},
|
||||
{0x2A, "OR1 C,/membit"},
|
||||
{0x2B, "ROL direct"},
|
||||
{0x2C, "ROL abs"},
|
||||
{0x2D, "PUSH A"},
|
||||
{0x2E, "CBNE direct,rel"},
|
||||
{0x2F, "BRA rel"},
|
||||
{0x30, "BMI rel"},
|
||||
{0x31, "TCALL3"},
|
||||
{0x32, "CLR1 direct.1"},
|
||||
{0x33, "BBC direct.1,rel"},
|
||||
{0x34, "AND A,direct+X"},
|
||||
{0x35, "AND A,abs+X"},
|
||||
{0x36, "AND A,abs+Y"},
|
||||
{0x37, "AND A,(direct)+Y"},
|
||||
{0x38, "AND direct,direct"},
|
||||
{0x39, "AND (X),(Y)"},
|
||||
{0x3A, "INCW direct"},
|
||||
{0x3B, "ROL direct+X"},
|
||||
{0x3C, "ROL A"},
|
||||
{0x3D, "INC X"},
|
||||
{0x3E, "CMP X,direct"},
|
||||
{0x3F, "CALL abs"},
|
||||
{0x40, "SETP"},
|
||||
{0x41, "TCALL4"},
|
||||
{0x42, "SET1 direct.2"},
|
||||
{0x43, "BBS direct.2,rel"},
|
||||
{0x44, "EOR A,direct"},
|
||||
{0x45, "EOR A,abs"},
|
||||
{0x46, "EOR A,(X)"},
|
||||
{0x47, "EOR A,(direct+X)"},
|
||||
{0x48, "EOR A,#imm"},
|
||||
{0x49, "EOR direct,imm"},
|
||||
{0x4A, "AND1 C,membit"},
|
||||
{0x4B, "LSR direct"},
|
||||
{0x4C, "LSR abs"},
|
||||
{0x4D, "PUSH X"},
|
||||
{0x4E, "TCLR1 abs"},
|
||||
{0x4F, "PCALL addr"},
|
||||
{0x50, "BVC rel"},
|
||||
{0x51, "TCALL5"},
|
||||
{0x52, "CLR1 direct.2"},
|
||||
{0x53, "BBC direct.2,rel"},
|
||||
{0x54, "EOR A,direct+X"},
|
||||
{0x55, "EOR A,abs+X"},
|
||||
{0x56, "EOR A,abs+Y"},
|
||||
{0x57, "EOR A,(direct)+Y"},
|
||||
{0x58, "EOR direct,direct"},
|
||||
{0x59, "EOR (X),(Y)"},
|
||||
{0x5A, "CMPW YA,direct"},
|
||||
{0x5B, "LSR direct+X"},
|
||||
{0x5C, "LSR A"},
|
||||
{0x5D, "MOV X,A"},
|
||||
{0x5E, "CMP Y,abs"},
|
||||
{0x5F, "JMP abs"},
|
||||
{0x60, "CLRC"},
|
||||
{0x61, "TCALL6"},
|
||||
{0x62, "SET1 direct.3"},
|
||||
{0x63, "BBS direct.3,rel"},
|
||||
{0x64, "CMP A,direct"},
|
||||
{0x65, "CMP A,abs"},
|
||||
{0x66, "CMP A,(X)"},
|
||||
{0x67, "CMP A,(direct+X)"},
|
||||
{0x68, "CMP A,#imm"},
|
||||
{0x69, "CMP direct,imm"},
|
||||
{0x6A, "AND1 C,/membit"},
|
||||
{0x6B, "ROR direct"},
|
||||
{0x6C, "ROR abs"},
|
||||
{0x6D, "PUSH Y"},
|
||||
{0x6E, "DBNZ direct,rel"},
|
||||
{0x6F, "RET"},
|
||||
{0x70, "BVS rel"},
|
||||
{0x71, "TCALL7"},
|
||||
{0x72, "CLR1 direct.3"},
|
||||
{0x73, "BBC direct.3,rel"},
|
||||
{0x74, "CMP A,direct+X"},
|
||||
{0x75, "CMP A,abs+X"},
|
||||
{0x76, "CMP A,abs+Y"},
|
||||
{0x77, "CMP A,(direct)+Y"},
|
||||
{0x78, "CMP direct,direct"},
|
||||
{0x79, "CMP (X),(Y)"},
|
||||
{0x7A, "ADDW YA,direct"},
|
||||
{0x7B, "ROR direct+X"},
|
||||
{0x7C, "ROR A"},
|
||||
{0x7D, "MOV A,X"},
|
||||
{0x7E, "CMP Y,direct"},
|
||||
{0x7F, "RETI"},
|
||||
{0x80, "SETC"},
|
||||
{0x81, "TCALL8"},
|
||||
{0x82, "SET1 direct.4"},
|
||||
{0x83, "BBS direct.4,rel"},
|
||||
{0x84, "ADC A,direct"},
|
||||
{0x85, "ADC A,abs"},
|
||||
{0x86, "ADC A,(X)"},
|
||||
{0x87, "ADC A,(direct+X)"},
|
||||
{0x88, "ADC A,#imm"},
|
||||
{0x89, "ADC direct,imm"},
|
||||
{0x8A, "EOR1 C,membit"},
|
||||
{0x8B, "DEC direct"},
|
||||
{0x8C, "DEC abs"},
|
||||
{0x8D, "MOV Y,#imm"},
|
||||
{0x8E, "POP PSW"},
|
||||
{0x8F, "MOV direct,#imm"},
|
||||
{0x90, "BCC rel"},
|
||||
{0x91, "TCALL9"},
|
||||
{0x92, "CLR1 direct.4"},
|
||||
{0x93, "BBC direct.4,rel"},
|
||||
{0x94, "ADC A,direct+X"},
|
||||
{0x95, "ADC A,abs+X"},
|
||||
{0x96, "ADC A,abs+Y"},
|
||||
{0x97, "ADC A,(direct)+Y"},
|
||||
{0x98, "ADC direct,direct"},
|
||||
{0x99, "ADC (X),(Y)"},
|
||||
{0x9A, "SUBW YA,direct"},
|
||||
{0x9B, "DEC direct+X"},
|
||||
{0x9C, "DEC A"},
|
||||
{0x9D, "MOV X,SP"},
|
||||
{0x9E, "DIV YA,X"},
|
||||
{0x9F, "XCN A"},
|
||||
{0xA0, "EI"},
|
||||
{0xA1, "TCALL10"},
|
||||
{0xA2, "SET1 direct.5"},
|
||||
{0xA3, "BBS direct.5,rel"},
|
||||
{0xA4, "SBC A,direct"},
|
||||
{0xA5, "SBC A,abs"},
|
||||
{0xA6, "SBC A,(X)"},
|
||||
{0xA7, "SBC A,(direct+X)"},
|
||||
{0xA8, "SBC A,#imm"},
|
||||
{0xA9, "SBC direct,imm"},
|
||||
{0xAA, "MOV1 C,membit"},
|
||||
{0xAB, "INC direct"},
|
||||
{0xAC, "INC abs"},
|
||||
{0xAD, "CMP Y,#imm"},
|
||||
{0xAE, "POP A"},
|
||||
{0xAF, "MOV (X)+,A"},
|
||||
{0xB0, "BCS rel"},
|
||||
{0xB1, "TCALL11"},
|
||||
{0xB2, "CLR1 direct.5"},
|
||||
{0xB3, "BBC direct.5,rel"},
|
||||
{0xB4, "SBC A,direct+X"},
|
||||
{0xB5, "SBC A,abs+X"},
|
||||
{0xB6, "SBC A,abs+Y"},
|
||||
{0xB7, "SBC A,(direct)+Y"},
|
||||
{0xB8, "SBC direct,direct"},
|
||||
{0xB9, "SBC (X),(Y)"},
|
||||
{0xBA, "MOVW YA,direct"},
|
||||
{0xBB, "INC direct+X"},
|
||||
{0xBC, "INC A"},
|
||||
{0xBD, "MOV SP,X"},
|
||||
{0xBE, "DAS"},
|
||||
{0xBF, "MOV A,(X)+"},
|
||||
{0xC0, "DI"},
|
||||
{0xC1, "TCALL12"},
|
||||
{0xC2, "SET1 direct.6"},
|
||||
{0xC3, "BBS direct.6,rel"},
|
||||
{0xC4, "MOV direct,A"},
|
||||
{0xC5, "MOV abs,A"},
|
||||
{0xC6, "MOV (X),A"},
|
||||
{0xC7, "MOV (direct+X),A"},
|
||||
{0xC8, "CMP X,#imm"},
|
||||
{0xC9, "MOV abs,X"},
|
||||
{0xCA, "MOV1 membit,C"},
|
||||
{0xCB, "MOV direct,Y"},
|
||||
{0xCC, "MOV abs,Y"},
|
||||
{0xCD, "MOV X,#imm"},
|
||||
{0xCE, "POP X"},
|
||||
{0xCF, "MUL YA"},
|
||||
{0xD0, "BNE rel"},
|
||||
{0xD1, "TCALL13"},
|
||||
{0xD2, "CLR1 direct.6"},
|
||||
{0xD3, "BBC direct.6,rel"},
|
||||
{0xD4, "MOV direct+X,A"},
|
||||
{0xD5, "MOV abs+X,A"},
|
||||
{0xD6, "MOV abs+Y,A"},
|
||||
{0xD7, "MOV (direct)+Y,A"},
|
||||
{0xD8, "MOV direct,X"},
|
||||
{0xD9, "MOV direct+Y,X"},
|
||||
{0xDA, "MOVW direct,YA"},
|
||||
{0xDB, "MOV direct+X,Y"},
|
||||
{0xDC, "DEC Y"},
|
||||
{0xDD, "MOV A,Y"},
|
||||
{0xDE, "CBNE direct+X,rel"},
|
||||
{0xDF, "DAA"},
|
||||
{0xE0, "CLRV"},
|
||||
{0xE1, "TCALL14"},
|
||||
{0xE2, "SET1 direct.7"},
|
||||
{0xE3, "BBS direct.7,rel"},
|
||||
{0xE4, "MOV A,direct"},
|
||||
{0xE5, "MOV A,abs"},
|
||||
{0xE6, "MOV A,(X)"},
|
||||
{0xE7, "MOV A,(direct+X)"},
|
||||
{0xE8, "MOV A,#imm"},
|
||||
{0xE9, "MOV X,abs"},
|
||||
{0xEA, "NOT1 membit"},
|
||||
{0xEB, "MOV Y,direct"},
|
||||
{0xEC, "MOV Y,abs"},
|
||||
{0xED, "NOTC"},
|
||||
{0xEE, "POP Y"},
|
||||
{0xEF, "SLEEP"},
|
||||
{0xF0, "BEQ rel"},
|
||||
{0xF1, "TCALL15"},
|
||||
{0xF2, "CLR1 direct.7"},
|
||||
{0xF3, "BBC direct.7,rel"},
|
||||
{0xF4, "MOV A,direct+X"},
|
||||
{0xF5, "MOV A,abs+X"},
|
||||
{0xF6, "MOV A,abs+Y"},
|
||||
{0xF7, "MOV A,(direct)+Y"},
|
||||
{0xF8, "MOV X,direct"},
|
||||
{0xF9, "MOV X,direct+Y"},
|
||||
{0xFA, "MOV direct,S"},
|
||||
{0xFB, "MOV Y,direct+X"},
|
||||
{0xFC, "INC Y"},
|
||||
{0xFD, "MOV Y,A"},
|
||||
{0xFE, "DBNZ Y,rel"},
|
||||
{0xFF, "STOP"}};
|
||||
957
src/app/emu/audio/spc700.cc
Normal file
957
src/app/emu/audio/spc700.cc
Normal file
@@ -0,0 +1,957 @@
|
||||
#include "app/emu/audio/spc700.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/audio/internal/opcodes.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void Spc700::Reset() {
|
||||
PC = 0;
|
||||
A = 0;
|
||||
X = 0;
|
||||
Y = 0;
|
||||
SP = 0xFF;
|
||||
PSW = ByteToFlags(0x00);
|
||||
aram_.reset();
|
||||
}
|
||||
|
||||
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::ExecuteInstructions(uint8_t opcode) {
|
||||
uint16_t initialPC = PC;
|
||||
switch (opcode) {
|
||||
// 8-bit Move Memory to Register
|
||||
case 0xE8: // MOV A, #imm
|
||||
{
|
||||
MOV(A, imm());
|
||||
break;
|
||||
}
|
||||
case 0xE6: // MOV A, (X)
|
||||
{
|
||||
MOV(A, X);
|
||||
break;
|
||||
}
|
||||
case 0xBF: // MOV A, (X)+
|
||||
{
|
||||
MOV(A, X);
|
||||
X++;
|
||||
break;
|
||||
}
|
||||
case 0xE4: // MOV A, dp
|
||||
{
|
||||
MOV(A, dp());
|
||||
break;
|
||||
}
|
||||
case 0xF4: // MOV A, dp+X
|
||||
{
|
||||
MOV(A, dp_plus_x());
|
||||
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());
|
||||
break;
|
||||
}
|
||||
|
||||
// 8-bit move register to memory
|
||||
case 0xC6: // MOV (X), A
|
||||
{
|
||||
MOV_ADDR(X, A);
|
||||
break;
|
||||
}
|
||||
case 0xAF: // MOV (X)+, A
|
||||
{
|
||||
MOV_ADDR(X, A);
|
||||
break;
|
||||
}
|
||||
case 0xC4: // MOV dp, A
|
||||
{
|
||||
MOV_ADDR(get_dp_addr(), A);
|
||||
break;
|
||||
}
|
||||
case 0xD4: // MOV dp+X, A
|
||||
{
|
||||
MOV_ADDR(get_dp_addr() + X, A);
|
||||
break;
|
||||
}
|
||||
case 0xC5: // MOV !abs, A
|
||||
{
|
||||
MOV_ADDR(abs(), A);
|
||||
break;
|
||||
}
|
||||
case 0xD5: // MOV !abs+X, A
|
||||
{
|
||||
MOV_ADDR(abs() + X, A);
|
||||
break;
|
||||
}
|
||||
case 0xD6: // MOV !abs+Y, A
|
||||
{
|
||||
MOV_ADDR(abs() + Y, A);
|
||||
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
|
||||
ASL(dp());
|
||||
break;
|
||||
case 0x1B: // ASL dp+X
|
||||
ASL(dp_plus_x());
|
||||
break;
|
||||
case 0x0C: // ASL !abs
|
||||
ASL(abs());
|
||||
break;
|
||||
case 0x5C: // LSR A
|
||||
LSR(A);
|
||||
break;
|
||||
case 0x4B: // LSR dp
|
||||
LSR(mutable_dp());
|
||||
break;
|
||||
case 0x5B: // LSR dp+X
|
||||
LSR(mutable_read((PSW.P << 8) + dp_plus_x()));
|
||||
break;
|
||||
case 0x4C: // LSR !abs
|
||||
LSR(mutable_read(abs()));
|
||||
break;
|
||||
|
||||
case 0x3C: // ROL A
|
||||
ROL(A);
|
||||
break;
|
||||
case 0x2B: // ROL dp
|
||||
ROL(dp());
|
||||
break;
|
||||
case 0x3B: // ROL dp+X
|
||||
ROL(dp_plus_x());
|
||||
break;
|
||||
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());
|
||||
break;
|
||||
}
|
||||
case 0x4F: // PCALL up
|
||||
{
|
||||
PCALL(imm());
|
||||
break;
|
||||
}
|
||||
case 0x6F: // RET
|
||||
{
|
||||
RET();
|
||||
break;
|
||||
}
|
||||
case 0x7F: // RETI
|
||||
{
|
||||
RETI();
|
||||
break;
|
||||
}
|
||||
|
||||
// . stack
|
||||
case 0x2D: // PUSH A
|
||||
{
|
||||
PUSH(A);
|
||||
break;
|
||||
}
|
||||
case 0x4D: // PUSH X
|
||||
{
|
||||
PUSH(X);
|
||||
break;
|
||||
}
|
||||
case 0x6D: // PUSH Y
|
||||
{
|
||||
PUSH(Y);
|
||||
break;
|
||||
}
|
||||
case 0x0D: // PUSH PSW
|
||||
{
|
||||
PUSH(FlagsToByte(PSW));
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xAE: // POP A
|
||||
{
|
||||
POP(A);
|
||||
break;
|
||||
}
|
||||
case 0xCE: // POP X
|
||||
{
|
||||
POP(X);
|
||||
break;
|
||||
}
|
||||
case 0xEE: // POP Y
|
||||
{
|
||||
POP(Y);
|
||||
break;
|
||||
}
|
||||
case 0x8E: // POP PSW
|
||||
{
|
||||
uint8_t flags_byte;
|
||||
POP(flags_byte);
|
||||
PSW = ByteToFlags(flags_byte);
|
||||
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();
|
||||
break;
|
||||
}
|
||||
case 0xEF: // SLEEP
|
||||
{
|
||||
SLEEP();
|
||||
break;
|
||||
}
|
||||
case 0x0F: // STOP
|
||||
{
|
||||
STOP();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
std::cout << "Unknown opcode: " << std::hex << opcode << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
LogInstruction(initialPC, opcode);
|
||||
}
|
||||
|
||||
void Spc700::LogInstruction(uint16_t initial_pc, uint8_t opcode) {
|
||||
std::string mnemonic = spc_opcode_map.at(opcode);
|
||||
|
||||
std::stringstream log_entry_stream;
|
||||
log_entry_stream << "\033[1;36m$" << std::hex << std::setw(4)
|
||||
<< std::setfill('0') << initial_pc << "\033[0m";
|
||||
log_entry_stream << " \033[1;32m" << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << static_cast<int>(opcode) << "\033[0m"
|
||||
<< " \033[1;35m" << std::setw(18) << std::left
|
||||
<< std::setfill(' ') << mnemonic << "\033[0m";
|
||||
|
||||
log_entry_stream << " \033[1;33mA: " << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << std::right << static_cast<int>(A)
|
||||
<< "\033[0m";
|
||||
log_entry_stream << " \033[1;33mX: " << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << std::right << static_cast<int>(X)
|
||||
<< "\033[0m";
|
||||
log_entry_stream << " \033[1;33mY: " << std::hex << std::setw(2)
|
||||
<< std::setfill('0') << std::right << static_cast<int>(Y)
|
||||
<< "\033[0m";
|
||||
std::string log_entry = log_entry_stream.str();
|
||||
|
||||
std::cerr << log_entry << std::endl;
|
||||
|
||||
// Append the log entry to the log
|
||||
log_.push_back(log_entry);
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
267
src/app/emu/audio/spc700.h
Normal file
267
src/app/emu/audio/spc700.h
Normal file
@@ -0,0 +1,267 @@
|
||||
#ifndef YAZE_APP_EMU_SPC700_H
|
||||
#define YAZE_APP_EMU_SPC700_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class AudioRam {
|
||||
public:
|
||||
virtual ~AudioRam() = default;
|
||||
virtual void reset() = 0;
|
||||
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;
|
||||
};
|
||||
|
||||
class AudioRamImpl : public AudioRam {
|
||||
static const int ARAM_SIZE = 0x10000;
|
||||
std::vector<uint8_t> ram = std::vector<uint8_t>(ARAM_SIZE, 0);
|
||||
|
||||
public:
|
||||
AudioRamImpl() = default;
|
||||
void reset() override { ram = std::vector<uint8_t>(ARAM_SIZE, 0); }
|
||||
|
||||
uint8_t read(uint16_t address) const override {
|
||||
return ram[address % ARAM_SIZE];
|
||||
}
|
||||
|
||||
uint8_t& mutable_read(uint16_t address) override {
|
||||
return ram.at(address % ARAM_SIZE);
|
||||
}
|
||||
|
||||
void write(uint16_t address, uint8_t value) override {
|
||||
ram[address % ARAM_SIZE] = value;
|
||||
}
|
||||
};
|
||||
|
||||
class Spc700 {
|
||||
private:
|
||||
AudioRam& aram_;
|
||||
std::vector<std::string> log_;
|
||||
|
||||
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,
|
||||
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};
|
||||
|
||||
public:
|
||||
explicit Spc700(AudioRam& aram) : aram_(aram) {}
|
||||
|
||||
// Registers
|
||||
uint8_t A = 0x00; // 8-bit accumulator
|
||||
uint8_t X = 0x00; // 8-bit index
|
||||
uint8_t Y = 0x00; // 8-bit index
|
||||
uint16_t YA = 0x00; // 16-bit pair of A (lsb) and Y (msb)
|
||||
uint16_t PC = 0xFFC0; // program counter
|
||||
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
|
||||
};
|
||||
Flags PSW; // Processor status word
|
||||
|
||||
uint8_t FlagsToByte(Flags flags) {
|
||||
return (flags.N << 7) | (flags.V << 6) | (flags.P << 5) | (flags.B << 4) |
|
||||
(flags.H << 3) | (flags.I << 2) | (flags.Z << 1) | (flags.C);
|
||||
}
|
||||
|
||||
Flags ByteToFlags(uint8_t byte) {
|
||||
Flags flags;
|
||||
flags.N = (byte & 0x80) >> 7;
|
||||
flags.V = (byte & 0x40) >> 6;
|
||||
flags.P = (byte & 0x20) >> 5;
|
||||
flags.B = (byte & 0x10) >> 4;
|
||||
flags.H = (byte & 0x08) >> 3;
|
||||
flags.I = (byte & 0x04) >> 2;
|
||||
flags.Z = (byte & 0x02) >> 1;
|
||||
flags.C = (byte & 0x01);
|
||||
return flags;
|
||||
}
|
||||
|
||||
void Reset();
|
||||
|
||||
void BootIplRom();
|
||||
|
||||
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& 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);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t& mutable_read_16(uint16_t address) {
|
||||
if (address < 0xFFC0) {
|
||||
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address));
|
||||
} else {
|
||||
// NOTE: Mutable access to IPL ROM is not allowed
|
||||
return *reinterpret_cast<uint16_t*>(&aram_.mutable_read(address));
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t 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];
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// Addressing modes
|
||||
|
||||
// Immediate
|
||||
uint8_t imm();
|
||||
|
||||
// Direct page
|
||||
uint8_t dp();
|
||||
uint8_t& mutable_dp();
|
||||
|
||||
uint8_t get_dp_addr();
|
||||
|
||||
// Direct page indexed by X
|
||||
uint8_t dp_plus_x();
|
||||
|
||||
// Direct page indexed by Y
|
||||
uint8_t dp_plus_y();
|
||||
|
||||
// Indexed indirect (add index before 16-bit lookup).
|
||||
uint16_t dp_plus_x_indirect();
|
||||
|
||||
// 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();
|
||||
|
||||
uint16_t addr_plus_i();
|
||||
|
||||
uint16_t addr_plus_i_indexed();
|
||||
|
||||
// ==========================================================================
|
||||
// Instructions
|
||||
|
||||
void MOV(uint8_t& dest, uint8_t operand);
|
||||
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 XCN(uint8_t operand, bool isImmediate = false);
|
||||
void INC(uint8_t& operand);
|
||||
void DEC(uint8_t& operand);
|
||||
void MOVW(uint16_t& dest, uint16_t operand);
|
||||
void INCW(uint16_t& operand);
|
||||
void DECW(uint16_t& operand);
|
||||
void ADDW(uint16_t& dest, uint16_t operand);
|
||||
void SUBW(uint16_t& dest, uint16_t operand);
|
||||
void CMPW(uint16_t operand);
|
||||
void MUL(uint8_t operand);
|
||||
void DIV(uint8_t operand);
|
||||
void BRA(int8_t offset);
|
||||
void BEQ(int8_t offset);
|
||||
void BNE(int8_t offset);
|
||||
void BCS(int8_t offset);
|
||||
void BCC(int8_t offset);
|
||||
void BVS(int8_t offset);
|
||||
void BVC(int8_t offset);
|
||||
void BMI(int8_t offset);
|
||||
void BPL(int8_t offset);
|
||||
void BBS(uint8_t bit, uint8_t operand);
|
||||
void BBC(uint8_t bit, uint8_t operand);
|
||||
void JMP(uint16_t address);
|
||||
void CALL(uint16_t address);
|
||||
void PCALL(uint8_t offset);
|
||||
void TCALL(uint8_t offset);
|
||||
void BRK();
|
||||
void RET();
|
||||
void RETI();
|
||||
void PUSH(uint8_t operand);
|
||||
void POP(uint8_t& operand);
|
||||
void SET1(uint8_t bit, uint8_t& operand);
|
||||
void CLR1(uint8_t bit, uint8_t& operand);
|
||||
void TSET1(uint8_t bit, uint8_t& operand);
|
||||
void TCLR1(uint8_t bit, uint8_t& operand);
|
||||
void AND1(uint8_t bit, uint8_t& operand);
|
||||
void OR1(uint8_t bit, uint8_t& operand);
|
||||
void EOR1(uint8_t bit, uint8_t& operand);
|
||||
void NOT1(uint8_t bit, uint8_t& operand);
|
||||
void MOV1(uint8_t bit, uint8_t& operand);
|
||||
void CLRC();
|
||||
void SETC();
|
||||
void NOTC();
|
||||
void CLRV();
|
||||
void CLRP();
|
||||
void SETP();
|
||||
void EI();
|
||||
void DI();
|
||||
void NOP();
|
||||
void SLEEP();
|
||||
void STOP();
|
||||
|
||||
// CBNE DBNZ
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
#endif // YAZE_APP_EMU_SPC700_H
|
||||
63
src/app/emu/cpu/clock.h
Normal file
63
src/app/emu/cpu/clock.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef YAZE_APP_EMU_CLOCK_H_
|
||||
#define YAZE_APP_EMU_CLOCK_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class Clock {
|
||||
public:
|
||||
virtual ~Clock() = default;
|
||||
virtual void UpdateClock(double delta) = 0;
|
||||
virtual unsigned long long GetCycleCount() const = 0;
|
||||
virtual void ResetAccumulatedTime() = 0;
|
||||
virtual void SetFrequency(float new_frequency) = 0;
|
||||
virtual float GetFrequency() const = 0;
|
||||
};
|
||||
|
||||
class ClockImpl : public Clock {
|
||||
public:
|
||||
ClockImpl() = default;
|
||||
virtual ~ClockImpl() = default;
|
||||
|
||||
void UpdateCycleCount(double deltaTime) {
|
||||
accumulatedTime += deltaTime;
|
||||
double cycleTime = 1.0 / frequency;
|
||||
|
||||
while (accumulatedTime >= cycleTime) {
|
||||
Cycle();
|
||||
accumulatedTime -= cycleTime;
|
||||
}
|
||||
}
|
||||
|
||||
void Cycle() {
|
||||
cycle++;
|
||||
cycleCount++;
|
||||
}
|
||||
|
||||
void UpdateClock(double delta) override {
|
||||
UpdateCycleCount(delta);
|
||||
ResetAccumulatedTime();
|
||||
}
|
||||
|
||||
void ResetAccumulatedTime() override { accumulatedTime = 0.0; }
|
||||
unsigned long long GetCycleCount() const override { return cycleCount; }
|
||||
float GetFrequency() const override { return frequency; }
|
||||
void SetFrequency(float new_frequency) override {
|
||||
this->frequency = new_frequency;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t cycle = 0; // Current cycle
|
||||
float frequency = 0.0; // Frequency of the clock in Hz
|
||||
unsigned long long cycleCount = 0; // Total number of cycles executed
|
||||
double accumulatedTime = 0.0; // Accumulated time since the last cycle update
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_CLOCK_H_
|
||||
1997
src/app/emu/cpu/cpu.cc
Normal file
1997
src/app/emu/cpu/cpu.cc
Normal file
File diff suppressed because it is too large
Load Diff
727
src/app/emu/cpu/cpu.h
Normal file
727
src/app/emu/cpu/cpu.h
Normal file
@@ -0,0 +1,727 @@
|
||||
#ifndef YAZE_APP_EMU_CPU_H_
|
||||
#define YAZE_APP_EMU_CPU_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/common.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/internal/opcodes.h"
|
||||
#include "app/emu/debug/log.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class InstructionEntry {
|
||||
public:
|
||||
// Constructor
|
||||
InstructionEntry(uint32_t addr, uint8_t op, const std::string& ops,
|
||||
const std::string& instr)
|
||||
: address(addr), opcode(op), operands(ops), instruction(instr) {}
|
||||
|
||||
// Getters for the class members
|
||||
uint32_t GetAddress() const { return address; }
|
||||
uint8_t GetOpcode() const { return opcode; }
|
||||
const std::string& GetOperands() const { return operands; }
|
||||
const std::string& GetInstruction() const { return instruction; }
|
||||
|
||||
uint32_t address; // Memory address of the instruction
|
||||
uint8_t opcode; // Opcode of the instruction
|
||||
std::string operands; // Operand(s) of the instruction, if any
|
||||
std::string instruction; // Human-readable instruction text
|
||||
};
|
||||
|
||||
const int kCpuClockSpeed = 21477272; // 21.477272 MHz
|
||||
|
||||
class CPU : public Memory, public Loggable, public core::ExperimentFlags {
|
||||
public:
|
||||
explicit CPU(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {}
|
||||
enum class UpdateMode { Run, Step, Pause };
|
||||
|
||||
void Init(bool verbose = false) { clock.SetFrequency(kCpuClockSpeed); }
|
||||
|
||||
void Update(UpdateMode mode = UpdateMode::Run, int stepCount = 1);
|
||||
|
||||
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_; }
|
||||
|
||||
std::vector<uint32_t> breakpoints_;
|
||||
std::vector<InstructionEntry> instruction_log_;
|
||||
|
||||
// ======================================================
|
||||
// Interrupt Vectors
|
||||
// Emulation mode, e = 1 Native mode, e = 0
|
||||
//
|
||||
// 0xFFFE,FF - IRQ/BRK 0xFFEE,EF - IRQ
|
||||
// 0xFFFC,FD - RESET
|
||||
// 0xFFFA,FB - NMI 0xFFEA,EB - NMI
|
||||
// 0xFFF8,F9 - ABORT 0xFFE8,E9 - ABORT
|
||||
// 0xFFE6,E7 - BRK
|
||||
// 0xFFF4,F5 - COP 0xFFE4,E5 - COP
|
||||
void HandleInterrupts();
|
||||
|
||||
// ======================================================
|
||||
// Registers
|
||||
|
||||
uint16_t A = 0; // Accumulator
|
||||
uint16_t X = 0; // X index register
|
||||
uint16_t Y = 0; // Y index register
|
||||
uint16_t D = 0; // Direct Page register
|
||||
uint8_t DB = 0; // Data Bank register
|
||||
uint8_t PB = 0; // Program Bank register
|
||||
uint16_t PC = 0; // Program Counter
|
||||
uint8_t E = 1; // Emulation mode flag
|
||||
uint8_t status = 0b00110000; // Processor Status (P)
|
||||
|
||||
// Mnemonic Value Binary Description
|
||||
// N #$80 10000000 Negative
|
||||
// V #$40 01000000 Overflow
|
||||
// M #$20 00100000 Accumulator size (0 = 16-bit, 1 = 8-bit)
|
||||
// X #$10 00010000 Index size (0 = 16-bit, 1 = 8-bit)
|
||||
// D #$08 00001000 Decimal
|
||||
// I #$04 00000100 IRQ disable
|
||||
// Z #$02 00000010 Zero
|
||||
// C #$01 00000001 Carry
|
||||
// E 6502 emulation mode
|
||||
// B #$10 00010000 Break (emulation mode only)
|
||||
|
||||
// Setting flags in the status register
|
||||
bool m() { return GetAccumulatorSize() ? 1 : 0; }
|
||||
int GetAccumulatorSize() const { return status & 0x20; }
|
||||
int GetIndexSize() const { return status & 0x10; }
|
||||
void set_16_bit_mode() {
|
||||
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); }
|
||||
|
||||
// Set individual flags
|
||||
void SetNegativeFlag(bool set) { SetFlag(0x80, set); }
|
||||
void SetOverflowFlag(bool set) { SetFlag(0x40, set); }
|
||||
void SetBreakFlag(bool set) { SetFlag(0x10, set); }
|
||||
void SetDecimalFlag(bool set) { SetFlag(0x08, set); }
|
||||
void SetInterruptFlag(bool set) { SetFlag(0x04, set); }
|
||||
void SetZeroFlag(bool set) { SetFlag(0x02, set); }
|
||||
void SetCarryFlag(bool set) { SetFlag(0x01, set); }
|
||||
|
||||
// Get individual flags
|
||||
bool GetNegativeFlag() const { return GetFlag(0x80); }
|
||||
bool GetOverflowFlag() const { return GetFlag(0x40); }
|
||||
bool GetBreakFlag() const { return GetFlag(0x10); }
|
||||
bool GetDecimalFlag() const { return GetFlag(0x08); }
|
||||
bool GetInterruptFlag() const { return GetFlag(0x04); }
|
||||
bool GetZeroFlag() const { return GetFlag(0x02); }
|
||||
bool GetCarryFlag() const { return GetFlag(0x01); }
|
||||
|
||||
enum class AccessType { Control, Data };
|
||||
|
||||
// ==========================================================================
|
||||
// Addressing Modes
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Data Bank Register if locating data
|
||||
// Program Bank Register if transferring control
|
||||
// High: Second operand byte
|
||||
// Low: First operand byte
|
||||
//
|
||||
// LDA addr
|
||||
uint32_t Absolute(AccessType access_type = AccessType::Data);
|
||||
|
||||
// Effective Address:
|
||||
// The Data Bank Register is concatened with the 16-bit operand
|
||||
// the 24-bit result is added to the X Index Register
|
||||
// based on the emulation mode (16:X=0, 8:X=1)
|
||||
//
|
||||
// LDA addr, X
|
||||
uint32_t AbsoluteIndexedX();
|
||||
|
||||
// Effective Address:
|
||||
// The Data Bank Register is concatened with the 16-bit operand
|
||||
// the 24-bit result is added to the Y Index Register
|
||||
// based on the emulation mode (16:Y=0, 8:Y=1)
|
||||
//
|
||||
// LDA addr, Y
|
||||
uint32_t AbsoluteIndexedY();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Program Bank Register (PBR)
|
||||
// High/low: The Indirect Address
|
||||
// Indirect Address: Located in the Program Bank at the sum of
|
||||
// the operand double byte and X based on the
|
||||
// emulation mode
|
||||
// JMP (addr, X)
|
||||
uint16_t AbsoluteIndexedIndirect();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Program Bank Register (PBR)
|
||||
// High/low: The Indirect Address
|
||||
// Indirect Address: Located in Bank Zero, at the operand double byte
|
||||
//
|
||||
// JMP (addr)
|
||||
uint16_t AbsoluteIndirect();
|
||||
|
||||
// Effective Address:
|
||||
// Bank/High/Low: The 24-bit Indirect Address
|
||||
// Indirect Address: Located in Bank Zero, at the operand double byte
|
||||
//
|
||||
// JMP [addr]
|
||||
uint32_t AbsoluteIndirectLong();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Third operand byte
|
||||
// High: Second operand byte
|
||||
// Low: First operand byte
|
||||
//
|
||||
// LDA long
|
||||
uint32_t AbsoluteLong();
|
||||
|
||||
// Effective Address:
|
||||
// The 24-bit operand is added to X based on the emulation mode
|
||||
//
|
||||
// LDA long, X
|
||||
uint32_t AbsoluteLongIndexedX();
|
||||
|
||||
// Source Effective Address:
|
||||
// Bank: Second operand byte
|
||||
// High/Low: The 16-bit value in X, if X is 8-bit high byte is 0
|
||||
//
|
||||
// Destination Effective Address:
|
||||
// Bank: First operand byte
|
||||
// High/Low: The 16-bit value in Y, if Y is 8-bit high byte is 0
|
||||
//
|
||||
// Length:
|
||||
// The number of bytes to be moved: 16-bit value in Acculumator C plus 1.
|
||||
//
|
||||
// MVN src, dst
|
||||
void BlockMove(uint16_t source, uint16_t dest, uint16_t length);
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Zero
|
||||
// High/low: Direct Page Register plus operand byte
|
||||
//
|
||||
// LDA dp
|
||||
uint16_t DirectPage();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Zero
|
||||
// High/low: Direct Page Register plus operand byte plus X
|
||||
// based on the emulation mode
|
||||
//
|
||||
// LDA dp, X
|
||||
uint16_t DirectPageIndexedX();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Zero
|
||||
// High/low: Direct Page Register plus operand byte plus Y
|
||||
// based on the emulation mode
|
||||
// LDA dp, Y
|
||||
uint16_t DirectPageIndexedY();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Data bank register
|
||||
// High/low: The indirect address
|
||||
// Indirect Address: Located in the direct page at the sum of the direct page
|
||||
// register, the operand byte, and X based on the emulation mode in bank zero.
|
||||
//
|
||||
// LDA (dp, X)
|
||||
uint16_t DirectPageIndexedIndirectX();
|
||||
|
||||
// Effective Address:
|
||||
// Bank: Data bank register
|
||||
// High/low: The 16-bit indirect address
|
||||
// Indirect Address: The operand byte plus the direct page register in bank
|
||||
// zero.
|
||||
//
|
||||
// LDA (dp)
|
||||
uint16_t DirectPageIndirect();
|
||||
|
||||
// Effective Address:
|
||||
// Bank/High/Low: The 24-bit indirect address
|
||||
// Indirect address: The operand byte plus the direct page
|
||||
// register in bank zero.
|
||||
//
|
||||
// LDA [dp]
|
||||
uint32_t DirectPageIndirectLong();
|
||||
|
||||
// Effective Address:
|
||||
// Found by concatenating the data bank to the double-byte
|
||||
// indirect address, then adding Y based on the emulation mode.
|
||||
//
|
||||
// Indirect Address: Located in the Direct Page at the sum of the direct page
|
||||
// register and the operand byte, in bank zero.
|
||||
//
|
||||
// LDA (dp), Y
|
||||
uint16_t DirectPageIndirectIndexedY();
|
||||
|
||||
// Effective Address:
|
||||
// Found by adding to the triple-byte indirect address Y based on the
|
||||
// emulation mode. Indrect Address: Located in the Direct Page at the sum
|
||||
// of the direct page register and the operand byte in bank zero.
|
||||
// Indirect Address:
|
||||
// Located in the Direct Page at the sum of the direct page register and
|
||||
// the operand byte in bank zero.
|
||||
//
|
||||
// LDA (dp), Y
|
||||
uint32_t DirectPageIndirectLongIndexedY();
|
||||
|
||||
// 8-bit data: Data Operand Byte
|
||||
// 16-bit data 65816 native mode m or x = 0
|
||||
// Data High: Second Operand Byte
|
||||
// Data Low: First Operand Byte
|
||||
//
|
||||
// LDA #const
|
||||
uint16_t Immediate(bool index_size = false);
|
||||
|
||||
uint16_t StackRelative();
|
||||
|
||||
// Effective Address:
|
||||
// The Data Bank Register is concatenated to the Indirect Address;
|
||||
// the 24-bit result is added to Y (16 bits if x = 0; else 8 bits)
|
||||
// Indirect Address:
|
||||
// Located at the 16-bit sum of the 8-bit operand and the 16-bit stack
|
||||
// pointer
|
||||
//
|
||||
// LDA (sr, S), Y
|
||||
uint32_t StackRelativeIndirectIndexedY();
|
||||
|
||||
// Memory access routines
|
||||
uint8_t ReadByte(uint32_t address) const override {
|
||||
return memory.ReadByte(address);
|
||||
}
|
||||
uint16_t ReadWord(uint32_t address) const override {
|
||||
return memory.ReadWord(address);
|
||||
}
|
||||
uint32_t ReadWordLong(uint32_t address) const override {
|
||||
return memory.ReadWordLong(address);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ReadByteVector(uint32_t address,
|
||||
uint16_t size) const override {
|
||||
return memory.ReadByteVector(address, size);
|
||||
}
|
||||
|
||||
void WriteByte(uint32_t address, uint8_t value) override {
|
||||
memory.WriteByte(address, value);
|
||||
}
|
||||
|
||||
void WriteWord(uint32_t address, uint16_t value) override {
|
||||
memory.WriteWord(address, value);
|
||||
}
|
||||
void WriteLong(uint32_t address, uint32_t value) override {
|
||||
memory.WriteLong(address, value);
|
||||
}
|
||||
|
||||
uint8_t FetchByte() {
|
||||
uint32_t address = (PB << 16) | PC + 1;
|
||||
uint8_t byte = memory.ReadByte(address);
|
||||
return byte;
|
||||
}
|
||||
|
||||
uint16_t FetchWord() {
|
||||
uint32_t address = (PB << 16) | PC + 1;
|
||||
uint16_t value = memory.ReadWord(address);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t FetchLong() {
|
||||
uint32_t value = memory.ReadWordLong((PB << 16) | PC + 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
int8_t FetchSignedByte() { return static_cast<int8_t>(FetchByte()); }
|
||||
|
||||
int16_t FetchSignedWord() {
|
||||
auto offset = static_cast<int16_t>(FetchWord());
|
||||
return offset;
|
||||
}
|
||||
|
||||
uint8_t FetchByteDirectPage(uint8_t operand) {
|
||||
uint16_t distance = D * 0x100;
|
||||
|
||||
// Calculate the effective address in the Direct Page
|
||||
uint16_t effectiveAddress = operand + distance;
|
||||
|
||||
// Fetch the byte from memory
|
||||
uint8_t fetchedByte = memory.ReadByte(effectiveAddress);
|
||||
|
||||
next_pc_ = PC + 1;
|
||||
|
||||
return fetchedByte;
|
||||
}
|
||||
|
||||
uint16_t ReadByteOrWord(uint32_t address) {
|
||||
if (GetAccumulatorSize()) {
|
||||
// 8-bit mode
|
||||
return memory.ReadByte(address) & 0xFF;
|
||||
} else {
|
||||
// 16-bit mode
|
||||
return memory.ReadWord(address);
|
||||
}
|
||||
}
|
||||
|
||||
void PushByte(uint8_t value) override { memory.PushByte(value); }
|
||||
void PushWord(uint16_t value) override { memory.PushWord(value); }
|
||||
uint8_t PopByte() override { return memory.PopByte(); }
|
||||
uint16_t PopWord() override { return memory.PopWord(); }
|
||||
void PushLong(uint32_t value) override { memory.PushLong(value); }
|
||||
uint32_t PopLong() override { return memory.PopLong(); }
|
||||
|
||||
// ======================================================
|
||||
// Instructions
|
||||
|
||||
// ADC: Add with carry
|
||||
void ADC(uint16_t operand);
|
||||
|
||||
// AND: Logical AND
|
||||
void AND(uint32_t address, bool immediate = false);
|
||||
void ANDAbsoluteLong(uint32_t address);
|
||||
|
||||
// ASL: Arithmetic shift left
|
||||
void ASL(uint16_t address);
|
||||
|
||||
// BCC: Branch if carry clear
|
||||
void BCC(int8_t offset);
|
||||
|
||||
// BCS: Branch if carry set
|
||||
void BCS(int8_t offset);
|
||||
|
||||
// BEQ: Branch if equal
|
||||
void BEQ(int8_t offset);
|
||||
|
||||
// BIT: Bit test
|
||||
void BIT(uint16_t address);
|
||||
|
||||
// BMI: Branch if minus
|
||||
void BMI(int8_t offset);
|
||||
|
||||
// BNE: Branch if not equal
|
||||
void BNE(int8_t offset);
|
||||
|
||||
// BPL: Branch if plus
|
||||
void BPL(int8_t offset);
|
||||
|
||||
// BRA: Branch always
|
||||
void BRA(int8_t offset);
|
||||
|
||||
// BRK: Force interrupt
|
||||
void BRK();
|
||||
|
||||
// BRL: Branch always long
|
||||
void BRL(int16_t offset);
|
||||
|
||||
// BVC: Branch if overflow clear
|
||||
void BVC(int8_t offset);
|
||||
|
||||
// BVS: Branch if overflow set
|
||||
void BVS(int8_t offset);
|
||||
|
||||
// CLC: Clear carry flag
|
||||
void CLC();
|
||||
|
||||
// CLD: Clear decimal mode
|
||||
void CLD();
|
||||
|
||||
// CLI: Clear interrupt disable bit
|
||||
void CLI();
|
||||
|
||||
// CLV: Clear overflow flag
|
||||
void CLV();
|
||||
|
||||
// CMP: Compare
|
||||
void CMP(uint32_t address, bool immediate = false);
|
||||
|
||||
// COP: Coprocessor enable
|
||||
void COP();
|
||||
|
||||
// CPX: Compare X register
|
||||
void CPX(uint32_t address, bool immediate = false);
|
||||
|
||||
// CPY: Compare Y register
|
||||
void CPY(uint32_t address, bool immediate = false);
|
||||
|
||||
// DEC: Decrement memory
|
||||
void DEC(uint32_t address, bool accumulator = false);
|
||||
|
||||
// DEX: Decrement X register
|
||||
void DEX();
|
||||
|
||||
// DEY: Decrement Y register
|
||||
void DEY();
|
||||
|
||||
// EOR: Exclusive OR
|
||||
void EOR(uint32_t address, bool immediate = false);
|
||||
|
||||
// INC: Increment memory
|
||||
void INC(uint32_t address, bool accumulator = false);
|
||||
|
||||
// INX: Increment X register
|
||||
void INX();
|
||||
|
||||
// INY: Increment Y register
|
||||
void INY();
|
||||
|
||||
// JMP: Jump
|
||||
void JMP(uint16_t address);
|
||||
|
||||
// JML: Jump long
|
||||
void JML(uint32_t address);
|
||||
|
||||
// JSR: Jump to subroutine
|
||||
void JSR(uint16_t address);
|
||||
|
||||
// JSL: Jump to subroutine long
|
||||
void JSL(uint32_t address);
|
||||
|
||||
// LDA: Load accumulator
|
||||
void LDA(uint16_t address, bool immediate = false, bool direct_page = false,
|
||||
bool data_bank = false);
|
||||
|
||||
// LDX: Load X register
|
||||
void LDX(uint16_t address, bool immediate = false);
|
||||
|
||||
// LDY: Load Y register
|
||||
void LDY(uint16_t address, bool immediate = false);
|
||||
|
||||
// LSR: Logical shift right
|
||||
void LSR(uint16_t address, bool accumulator = false);
|
||||
|
||||
// MVN: Block move next
|
||||
void MVN(uint16_t source, uint16_t dest, uint16_t length);
|
||||
|
||||
// MVP: Block move previous
|
||||
void MVP(uint16_t source, uint16_t dest, uint16_t length);
|
||||
|
||||
// NOP: No operation
|
||||
void NOP();
|
||||
|
||||
// ORA: Logical inclusive OR
|
||||
void ORA(uint16_t address, bool immediate = false);
|
||||
|
||||
// PEA: Push effective absolute address
|
||||
void PEA();
|
||||
|
||||
// PEI: Push effective indirect address
|
||||
void PEI();
|
||||
|
||||
// PER: Push effective relative address
|
||||
void PER();
|
||||
|
||||
// PHA: Push accumulator
|
||||
void PHA();
|
||||
|
||||
// PHB: Push data bank register
|
||||
void PHB();
|
||||
|
||||
// PHD: Push direct page register
|
||||
void PHD();
|
||||
|
||||
// PHK: Push program bank register
|
||||
void PHK();
|
||||
|
||||
// PHP: Push processor status (flags)
|
||||
void PHP();
|
||||
|
||||
// PHX: Push X register
|
||||
void PHX();
|
||||
|
||||
// PHY: Push Y register
|
||||
void PHY();
|
||||
|
||||
// PLA: Pull accumulator
|
||||
void PLA();
|
||||
|
||||
// PLB: Pull data bank register
|
||||
void PLB();
|
||||
|
||||
// PLD: Pull direct page register
|
||||
void PLD();
|
||||
|
||||
// PLP: Pull processor status (flags)
|
||||
void PLP();
|
||||
|
||||
// PLX: Pull X register
|
||||
void PLX();
|
||||
|
||||
// PLY: Pull Y register
|
||||
void PLY();
|
||||
|
||||
// REP: Reset processor status bits
|
||||
void REP();
|
||||
|
||||
// ROL: Rotate left
|
||||
void ROL(uint32_t address, bool accumulator = false);
|
||||
|
||||
// ROR: Rotate right
|
||||
void ROR(uint32_t address, bool accumulator = false);
|
||||
|
||||
// RTI: Return from interrupt
|
||||
void RTI();
|
||||
|
||||
// RTL: Return from subroutine long
|
||||
void RTL();
|
||||
|
||||
// RTS: Return from subroutine
|
||||
void RTS();
|
||||
|
||||
// SBC: Subtract with carry
|
||||
void SBC(uint32_t operand, bool immediate = false);
|
||||
|
||||
// SEC: Set carry flag
|
||||
void SEC();
|
||||
|
||||
// SED: Set decimal mode
|
||||
void SED();
|
||||
|
||||
// SEI: Set interrupt disable status
|
||||
void SEI();
|
||||
|
||||
// SEP: Set processor status bits
|
||||
void SEP();
|
||||
|
||||
// STA: Store accumulator
|
||||
void STA(uint32_t address);
|
||||
|
||||
// STP: Stop the processor
|
||||
void STP();
|
||||
|
||||
// STX: Store X register
|
||||
void STX(uint16_t address);
|
||||
|
||||
// STY: Store Y register
|
||||
void STY(uint16_t address);
|
||||
|
||||
// STZ: Store zero
|
||||
void STZ(uint16_t address);
|
||||
|
||||
// TAX: Transfer accumulator to X
|
||||
void TAX();
|
||||
|
||||
// TAY: Transfer accumulator to Y
|
||||
void TAY();
|
||||
|
||||
// TCD: Transfer 16-bit accumulator to direct page register
|
||||
void TCD();
|
||||
|
||||
// TCS: Transfer 16-bit accumulator to stack pointer
|
||||
void TCS();
|
||||
|
||||
// TDC: Transfer direct page register to 16-bit accumulator
|
||||
void TDC();
|
||||
|
||||
// TRB: Test and reset bits
|
||||
void TRB(uint16_t address);
|
||||
|
||||
// TSB: Test and set bits
|
||||
void TSB(uint16_t address);
|
||||
|
||||
// TSC: Transfer stack pointer to 16-bit accumulator
|
||||
void TSC();
|
||||
|
||||
// TSX: Transfer stack pointer to X
|
||||
void TSX();
|
||||
|
||||
// TXA: Transfer X to accumulator
|
||||
void TXA();
|
||||
|
||||
// TXS: Transfer X to stack pointer
|
||||
void TXS();
|
||||
|
||||
// TXY: Transfer X to Y
|
||||
void TXY();
|
||||
|
||||
// TYA: Transfer Y to accumulator
|
||||
void TYA();
|
||||
|
||||
// TYX: Transfer Y to X
|
||||
void TYX();
|
||||
|
||||
// WAI: Wait for interrupt
|
||||
void WAI();
|
||||
|
||||
// WDM: Reserved for future expansion
|
||||
void WDM();
|
||||
|
||||
// XBA: Exchange B and A
|
||||
void XBA();
|
||||
|
||||
// XCE: Exchange carry and emulation bits
|
||||
void XCE();
|
||||
|
||||
private:
|
||||
void compare(uint16_t register_value, uint16_t memory_value) {
|
||||
uint16_t result;
|
||||
if (GetIndexSize()) {
|
||||
// 8-bit mode
|
||||
uint8_t result8 = static_cast<uint8_t>(register_value) -
|
||||
static_cast<uint8_t>(memory_value);
|
||||
result = result8;
|
||||
SetNegativeFlag(result & 0x80); // Negative flag for 8-bit
|
||||
} else {
|
||||
// 16-bit mode
|
||||
result = register_value - memory_value;
|
||||
SetNegativeFlag(result & 0x8000); // Negative flag for 16-bit
|
||||
}
|
||||
SetZeroFlag(result == 0); // Zero flag
|
||||
SetCarryFlag(register_value >= memory_value); // Carry flag
|
||||
}
|
||||
|
||||
void SetFlag(uint8_t mask, bool set) {
|
||||
if (set) {
|
||||
status |= mask; // Set the bit
|
||||
} else {
|
||||
status &= ~mask; // Clear the bit
|
||||
}
|
||||
}
|
||||
|
||||
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_;
|
||||
|
||||
Memory& memory;
|
||||
Clock& clock;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_CPU_H_
|
||||
123
src/app/emu/cpu/internal/addressing.cc
Normal file
123
src/app/emu/cpu/internal/addressing.cc
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPage() {
|
||||
uint8_t dp = FetchByte();
|
||||
return D + dp;
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPageIndexedX() {
|
||||
uint8_t operand = FetchByte();
|
||||
uint16_t x_by_mode = GetAccumulatorSize() ? X : X & 0xFF;
|
||||
return D + operand + x_by_mode;
|
||||
}
|
||||
|
||||
uint16_t CPU::DirectPageIndexedY() {
|
||||
uint8_t operand = FetchByte();
|
||||
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::DirectPageIndirectLong() {
|
||||
uint8_t dp = FetchByte();
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t CPU::DirectPageIndirectLongIndexedY() {
|
||||
uint8_t operand = FetchByte();
|
||||
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;
|
||||
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();
|
||||
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
|
||||
820
src/app/emu/cpu/internal/instructions.cc
Normal file
820
src/app/emu/cpu/internal/instructions.cc
Normal file
@@ -0,0 +1,820 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
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<uint16_t>(A & 0xFF) +
|
||||
static_cast<uint16_t>(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);
|
||||
} else {
|
||||
uint32_t result =
|
||||
static_cast<uint32_t>(A) + static_cast<uint32_t>(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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
} else {
|
||||
uint8_t memory_value = memory.ReadByte(value);
|
||||
result = A - memory_value;
|
||||
}
|
||||
SetZeroFlag(result == 0);
|
||||
SetNegativeFlag(result & 0x80);
|
||||
SetCarryFlag(A >= (value & 0xFF));
|
||||
} else { // 16-bit
|
||||
uint16_t result;
|
||||
if (isImmediate) {
|
||||
result = A - (value & 0xFFFF);
|
||||
} else {
|
||||
uint16_t memory_value = memory.ReadWord(value);
|
||||
result = A - memory_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);
|
||||
} else {
|
||||
uint16_t value = memory.ReadWord(address);
|
||||
value--;
|
||||
memory.WriteWord(address, value);
|
||||
SetZeroFlag(value == 0);
|
||||
SetNegativeFlag(value & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::DEX() {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
X = static_cast<uint8_t>(X - 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
} else { // 16-bit
|
||||
X = static_cast<uint16_t>(X - 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::DEY() {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
Y = static_cast<uint8_t>(Y - 1);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x80);
|
||||
} else { // 16-bit
|
||||
Y = static_cast<uint16_t>(Y - 1);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::EOR(uint32_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::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<uint8_t>(X + 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x80);
|
||||
} else { // 16-bit
|
||||
X = static_cast<uint16_t>(X + 1);
|
||||
SetZeroFlag(X == 0);
|
||||
SetNegativeFlag(X & 0x8000);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::INY() {
|
||||
if (GetIndexSize()) { // 8-bit
|
||||
Y = static_cast<uint8_t>(Y + 1);
|
||||
SetZeroFlag(Y == 0);
|
||||
SetNegativeFlag(Y & 0x80);
|
||||
} else { // 16-bit
|
||||
Y = static_cast<uint16_t>(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<uint16_t>(address & 0xFFFF);
|
||||
// Set the PBR to the upper 8 bits of the address
|
||||
PB = static_cast<uint8_t>((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<uint8_t>(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<uint8_t>(X));
|
||||
} else {
|
||||
memory.PushWord(X);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::PHY() {
|
||||
if (GetIndexSize()) {
|
||||
memory.PushByte(static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(X));
|
||||
} else {
|
||||
memory.WriteWord(address, X);
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::STY(uint16_t address) {
|
||||
if (GetIndexSize()) {
|
||||
memory.WriteByte(address, static_cast<uint8_t>(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
|
||||
61
src/app/emu/cpu/internal/opcodes.h
Normal file
61
src/app/emu/cpu/internal/opcodes.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
const std::unordered_map<uint8_t, std::string> opcode_to_mnemonic = {
|
||||
{0x00, "BRK"}, {0x01, "ORA"}, {0x02, "COP"}, {0x03, "ORA"}, {0x04, "TSB"},
|
||||
{0x05, "ORA"}, {0x06, "ASL"}, {0x07, "ORA"}, {0x08, "PHP"}, {0x09, "ORA"},
|
||||
{0x0A, "ASL"}, {0x0B, "PHD"}, {0x0C, "TSB"}, {0x0D, "ORA"}, {0x0E, "ASL"},
|
||||
{0x0F, "ORA"}, {0x10, "BPL"}, {0x11, "ORA"}, {0x12, "ORA"}, {0x13, "ORA"},
|
||||
{0x14, "TRB"}, {0x15, "ORA"}, {0x16, "ASL"}, {0x17, "ORA"}, {0x18, "CLC"},
|
||||
{0x19, "ORA"}, {0x1A, "INC"}, {0x1B, "TCS"}, {0x1C, "TRB"}, {0x1D, "ORA"},
|
||||
{0x1E, "ASL"}, {0x1F, "ORA"}, {0x20, "JSR"}, {0x21, "AND"}, {0x22, "JSL"},
|
||||
{0x23, "AND"}, {0x24, "BIT"}, {0x25, "AND"}, {0x26, "ROL"}, {0x27, "AND"},
|
||||
{0x28, "PLP"}, {0x29, "AND"}, {0x2A, "ROL"}, {0x2B, "PLD"}, {0x2C, "BIT"},
|
||||
{0x2D, "AND"}, {0x2E, "ROL"}, {0x2F, "AND"}, {0x30, "BMI"}, {0x31, "AND"},
|
||||
{0x32, "AND"}, {0x33, "AND"}, {0x34, "BIT"}, {0x35, "AND"}, {0x36, "ROL"},
|
||||
{0x37, "AND"}, {0x38, "SEC"}, {0x39, "AND"}, {0x3A, "DEC"}, {0x3B, "TSC"},
|
||||
{0x3C, "BIT"}, {0x3D, "AND"}, {0x3E, "ROL"}, {0x3F, "AND"}, {0x40, "RTI"},
|
||||
{0x41, "EOR"}, {0x42, "WDM"}, {0x43, "EOR"}, {0x44, "MVP"}, {0x45, "EOR"},
|
||||
{0x46, "LSR"}, {0x47, "EOR"}, {0x48, "PHA"}, {0x49, "EOR"}, {0x4A, "LSR"},
|
||||
{0x4B, "PHK"}, {0x4C, "JMP"}, {0x4D, "EOR"}, {0x4E, "LSR"}, {0x4F, "EOR"},
|
||||
{0x50, "BVC"}, {0x51, "EOR"}, {0x52, "EOR"}, {0x53, "EOR"}, {0x54, "MVN"},
|
||||
{0x55, "EOR"}, {0x56, "LSR"}, {0x57, "EOR"}, {0x58, "CLI"}, {0x59, "EOR"},
|
||||
{0x5A, "PHY"}, {0x5B, "TCD"}, {0x5C, "JMP"}, {0x5D, "EOR"}, {0x5E, "LSR"},
|
||||
{0x5F, "EOR"}, {0x60, "RTS"}, {0x61, "ADC"}, {0x62, "PER"}, {0x63, "ADC"},
|
||||
{0x64, "STZ"}, {0x65, "ADC"}, {0x66, "ROR"}, {0x67, "ADC"}, {0x68, "PLA"},
|
||||
{0x69, "ADC"}, {0x6A, "ROR"}, {0x6B, "RTL"}, {0x6C, "JMP"}, {0x6D, "ADC"},
|
||||
{0x6E, "ROR"}, {0x6F, "ADC"}, {0x70, "BVS"}, {0x71, "ADC"}, {0x72, "ADC"},
|
||||
{0x73, "ADC"}, {0x74, "STZ"}, {0x75, "ADC"}, {0x76, "ROR"}, {0x77, "ADC"},
|
||||
{0x78, "SEI"}, {0x79, "ADC"}, {0x7A, "PLY"}, {0x7B, "TDC"}, {0x7C, "JMP"},
|
||||
{0x7D, "ADC"}, {0x7E, "ROR"}, {0x7F, "ADC"}, {0x80, "BRA"}, {0x81, "STA"},
|
||||
{0x82, "BRL"}, {0x83, "STA"}, {0x84, "STY"}, {0x85, "STA"}, {0x86, "STX"},
|
||||
{0x87, "STA"}, {0x88, "DEY"}, {0x89, "BIT"}, {0x8A, "TXA"}, {0x8B, "PHB"},
|
||||
{0x8C, "STY"}, {0x8D, "STA"}, {0x8E, "STX"}, {0x8F, "STA"}, {0x90, "BCC"},
|
||||
{0x91, "STA"}, {0x92, "STA"}, {0x93, "STA"}, {0x94, "STY"}, {0x95, "STA"},
|
||||
{0x96, "STX"}, {0x97, "STA"}, {0x98, "TYA"}, {0x99, "STA"}, {0x9A, "TXS"},
|
||||
{0x9B, "TXY"}, {0x9C, "STZ"}, {0x9D, "STA"}, {0x9E, "STZ"}, {0x9F, "STA"},
|
||||
{0xA0, "LDY"}, {0xA1, "LDA"}, {0xA2, "LDX"}, {0xA3, "LDA"}, {0xA4, "LDY"},
|
||||
{0xA5, "LDA"}, {0xA6, "LDX"}, {0xA7, "LDA"}, {0xA8, "TAY"}, {0xA9, "LDA"},
|
||||
{0xAA, "TAX"}, {0xAB, "PLB"}, {0xAC, "LDY"}, {0xAD, "LDA"}, {0xAE, "LDX"},
|
||||
{0xAF, "LDA"}, {0xB0, "BCS"}, {0xB1, "LDA"}, {0xB2, "LDA"}, {0xB3, "LDA"},
|
||||
{0xB4, "LDY"}, {0xB5, "LDA"}, {0xB6, "LDX"}, {0xB7, "LDA"}, {0xB8, "CLV"},
|
||||
{0xB9, "LDA"}, {0xBA, "TSX"}, {0xBB, "TYX"}, {0xBC, "LDY"}, {0xBD, "LDA"},
|
||||
{0xBE, "LDX"}, {0xBF, "LDA"}, {0xC0, "CPY"}, {0xC1, "CMP"}, {0xC2, "REP"},
|
||||
{0xC3, "CMP"}, {0xC4, "CPY"}, {0xC5, "CMP"}, {0xC6, "DEC"}, {0xC7, "CMP"},
|
||||
{0xC8, "INY"}, {0xC9, "CMP"}, {0xCA, "DEX"}, {0xCB, "WAI"}, {0xCC, "CPY"},
|
||||
{0xCD, "CMP"}, {0xCE, "DEC"}, {0xCF, "CMP"}, {0xD0, "BNE"}, {0xD1, "CMP"},
|
||||
{0xD2, "CMP"}, {0xD3, "CMP"}, {0xD4, "PEI"}, {0xD5, "CMP"}, {0xD6, "DEC"},
|
||||
{0xD7, "CMP"}, {0xD8, "CLD"}, {0xD9, "CMP"}, {0xDA, "PHX"}, {0xDB, "STP"},
|
||||
{0xDC, "JMP"}, {0xDD, "CMP"}, {0xDE, "DEC"}, {0xDF, "CMP"}, {0xE0, "CPX"},
|
||||
{0xE1, "SBC"}, {0xE2, "SEP"}, {0xE3, "SBC"}, {0xE4, "CPX"}, {0xE5, "SBC"},
|
||||
{0xE6, "INC"}, {0xE7, "SBC"}, {0xE8, "INX"}, {0xE9, "SBC"}, {0xEA, "NOP"},
|
||||
{0xEB, "XBA"}, {0xEC, "CPX"}, {0xED, "SBC"}, {0xEE, "INC"}, {0xEF, "SBC"},
|
||||
{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"}
|
||||
|
||||
};
|
||||
118
src/app/emu/debug/asm_parser.h
Normal file
118
src/app/emu/debug/asm_parser.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/cpu/internal/opcodes.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class AsmParser {
|
||||
public:
|
||||
std::vector<uint8_t> Parse(const std::string& instruction) {
|
||||
std::smatch match;
|
||||
if (!std::regex_match(instruction, match, instruction_regex_)) {
|
||||
throw std::runtime_error("Invalid instruction format: " + instruction);
|
||||
}
|
||||
|
||||
std::string mnemonic = match[1];
|
||||
std::string addressing_mode = match[2];
|
||||
std::string operand = match[3];
|
||||
|
||||
std::string lookup_string = mnemonic.substr(0, 3);
|
||||
|
||||
auto opcode_entry = mnemonic_to_opcode_.find(mnemonic);
|
||||
if (opcode_entry == mnemonic_to_opcode_.end()) {
|
||||
throw std::runtime_error(
|
||||
"Unknown mnemonic or addressing mode: " + mnemonic + addressing_mode);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bytes = {opcode_entry->second};
|
||||
// AppendOperandBytes(bytes, operand, addressing_mode);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void CreateInternalOpcodeMap() {
|
||||
for (const auto& opcode_entry : opcode_to_mnemonic) {
|
||||
std::string name = opcode_entry.second;
|
||||
uint8_t opcode = opcode_entry.first;
|
||||
mnemonic_to_opcode_[name] = opcode;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void AppendOperandBytes(std::vector<uint8_t>& bytes,
|
||||
const std::string& operand,
|
||||
const std::string& addressing_mode) {
|
||||
if (addressing_mode == ".b") {
|
||||
bytes.push_back(static_cast<uint8_t>(std::stoi(operand, nullptr, 16)));
|
||||
} else if (addressing_mode == ".w") {
|
||||
uint16_t word_operand =
|
||||
static_cast<uint16_t>(std::stoi(operand, nullptr, 16));
|
||||
bytes.push_back(static_cast<uint8_t>(word_operand & 0xFF));
|
||||
bytes.push_back(static_cast<uint8_t>((word_operand >> 8) & 0xFF));
|
||||
} else if (addressing_mode == ".l") {
|
||||
uint32_t long_operand =
|
||||
static_cast<uint32_t>(std::stoul(operand, nullptr, 16));
|
||||
bytes.push_back(static_cast<uint8_t>(long_operand & 0xFF));
|
||||
bytes.push_back(static_cast<uint8_t>((long_operand >> 8) & 0xFF));
|
||||
bytes.push_back(static_cast<uint8_t>((long_operand >> 16) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
enum class AddressingMode {
|
||||
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] == '#') {
|
||||
return AddressingMode::kImmediate;
|
||||
} else {
|
||||
return AddressingMode::kImplied;
|
||||
}
|
||||
}
|
||||
|
||||
const std::regex instruction_regex_{R"((\w+)\s*(\.\w)?\s*(\$\w+|\#\w+|\w+))"};
|
||||
std::unordered_map<std::string, uint8_t> mnemonic_to_opcode_;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
56
src/app/emu/debug/debugger.h
Normal file
56
src/app/emu/debug/debugger.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef YAZE_APP_EMU_DEBUG_DEBUGGER_H_
|
||||
#define YAZE_APP_EMU_DEBUG_DEBUGGER_H_
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/video/ppu.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class Debugger {
|
||||
public:
|
||||
Debugger() = default;
|
||||
// Attach the debugger to the emulator
|
||||
// Debugger(CPU &cpu, PPU &ppu, APU &apu);
|
||||
|
||||
// Set a breakpoint
|
||||
void SetBreakpoint(uint16_t address);
|
||||
|
||||
// Remove a breakpoint
|
||||
void RemoveBreakpoint(uint16_t address);
|
||||
|
||||
// Step through the code
|
||||
void Step();
|
||||
|
||||
// Inspect memory
|
||||
uint8_t InspectMemory(uint16_t address);
|
||||
|
||||
// Modify memory
|
||||
void ModifyMemory(uint16_t address, uint8_t value);
|
||||
|
||||
// Inspect registers
|
||||
uint8_t InspectRegister(uint8_t reg);
|
||||
|
||||
// Modify registers
|
||||
void ModifyRegister(uint8_t reg, uint8_t value);
|
||||
|
||||
// Handle other debugger tasks
|
||||
// ...
|
||||
|
||||
private:
|
||||
// References to the emulator's components
|
||||
// CPU &cpu;
|
||||
// PPU &ppu;
|
||||
// APU &apu;
|
||||
|
||||
// Breakpoints, watchpoints, etc.
|
||||
// ...
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_DBG_H_
|
||||
43
src/app/emu/debug/log.h
Normal file
43
src/app/emu/debug/log.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef YAZE_APP_EMU_LOG_H_
|
||||
#define YAZE_APP_EMU_LOG_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
// Logger.h
|
||||
class Logger {
|
||||
public:
|
||||
static Logger& GetInstance() {
|
||||
static Logger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Log(const std::string& message) const {
|
||||
// Write log messages to a file or console
|
||||
std::cout << message << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
Logger() = default;
|
||||
Logger(const Logger&) = delete;
|
||||
Logger& operator=(const Logger&) = delete;
|
||||
};
|
||||
|
||||
// Loggable.h
|
||||
class Loggable {
|
||||
protected:
|
||||
Logger& logger_ = Logger::GetInstance();
|
||||
|
||||
virtual ~Loggable() = default;
|
||||
virtual void LogMessage(const std::string& message) { logger_.Log(message); }
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_LOG_H_
|
||||
387
src/app/emu/emulator.cc
Normal file
387
src/app/emu/emulator.cc
Normal file
@@ -0,0 +1,387 @@
|
||||
#include "app/emu/emulator.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui_memory_editor.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/core/constants.h"
|
||||
#include "app/emu/snes.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using ImGui::NextColumn;
|
||||
using ImGui::SameLine;
|
||||
using ImGui::Separator;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::Text;
|
||||
|
||||
void Emulator::Run() {
|
||||
if (!snes_.running() && rom()->is_loaded()) {
|
||||
snes_.SetupMemory(*rom());
|
||||
snes_.Init(*rom());
|
||||
}
|
||||
|
||||
RenderNavBar();
|
||||
|
||||
if (running_) {
|
||||
HandleEvents();
|
||||
if (!step_) {
|
||||
snes_.Run();
|
||||
}
|
||||
}
|
||||
|
||||
RenderEmulator();
|
||||
}
|
||||
|
||||
void Emulator::RenderNavBar() {
|
||||
MENU_BAR()
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
MENU_ITEM("Input") {}
|
||||
MENU_ITEM("Audio") {}
|
||||
MENU_ITEM("Video") {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
END_MENU_BAR()
|
||||
|
||||
if (ImGui::Button(ICON_MD_PLAY_ARROW)) {
|
||||
loading_ = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Start Emulation");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_PAUSE)) {
|
||||
snes_.SetCpuMode(1);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Pause Emulation");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_SKIP_NEXT)) {
|
||||
// Step through Code logic
|
||||
snes_.StepRun();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Step Through Code");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_REFRESH)) {
|
||||
// Reset Emulator logic
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Reset Emulator");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_STOP)) {
|
||||
// Stop Emulation logic
|
||||
running_ = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Stop Emulation");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_SAVE)) {
|
||||
// Save State logic
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Save State");
|
||||
}
|
||||
SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_MD_SYSTEM_UPDATE_ALT)) {
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Load State");
|
||||
}
|
||||
|
||||
// Additional elements
|
||||
SameLine();
|
||||
if (ImGui::Button(ICON_MD_SETTINGS)) {
|
||||
// Settings logic
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Settings");
|
||||
}
|
||||
|
||||
SameLine();
|
||||
if (ImGui::Button(ICON_MD_INFO)) {
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("About Debugger");
|
||||
}
|
||||
// About Debugger logic
|
||||
}
|
||||
static bool show_memory_viewer = false;
|
||||
|
||||
SameLine();
|
||||
if (ImGui::Button(ICON_MD_MEMORY)) {
|
||||
show_memory_viewer = !show_memory_viewer;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Memory Viewer");
|
||||
}
|
||||
|
||||
if (show_memory_viewer) {
|
||||
ImGui::Begin("Memory Viewer", &show_memory_viewer);
|
||||
RenderMemoryViewer();
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::HandleEvents() {
|
||||
// Handle user input events
|
||||
// ...
|
||||
}
|
||||
|
||||
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() {
|
||||
Text("Breakpoints");
|
||||
Separator();
|
||||
static char breakpoint_input[10] = "";
|
||||
static int current_memory_mode = 0;
|
||||
|
||||
static bool read_mode = false;
|
||||
static bool write_mode = false;
|
||||
static bool execute_mode = false;
|
||||
|
||||
if (ImGui::Combo("##TypeOfMemory", ¤t_memory_mode, "PRG\0RAM\0")) {
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Read", &read_mode);
|
||||
SameLine();
|
||||
ImGui::Checkbox("Write", &write_mode);
|
||||
SameLine();
|
||||
ImGui::Checkbox("Execute", &execute_mode);
|
||||
|
||||
// Breakpoint input fields and buttons
|
||||
if (ImGui::InputText("##BreakpointInput", breakpoint_input, 10,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
int breakpoint = std::stoi(breakpoint_input, nullptr, 16);
|
||||
snes_.cpu().SetBreakpoint(breakpoint);
|
||||
memset(breakpoint_input, 0, sizeof(breakpoint_input));
|
||||
}
|
||||
SameLine();
|
||||
if (ImGui::Button("Add")) {
|
||||
int breakpoint = std::stoi(breakpoint_input, nullptr, 16);
|
||||
snes_.cpu().SetBreakpoint(breakpoint);
|
||||
memset(breakpoint_input, 0, sizeof(breakpoint_input));
|
||||
}
|
||||
SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
snes_.cpu().ClearBreakpoints();
|
||||
}
|
||||
Separator();
|
||||
auto breakpoints = snes_.cpu().GetBreakpoints();
|
||||
if (!breakpoints.empty()) {
|
||||
Text("Breakpoints:");
|
||||
ImGui::BeginChild("BreakpointsList", ImVec2(0, 100), true);
|
||||
for (auto breakpoint : breakpoints) {
|
||||
if (ImGui::Selectable(absl::StrFormat("0x%04X", breakpoint).c_str())) {
|
||||
// Jump to breakpoint
|
||||
// snes_.Cpu().JumpToBreakpoint(breakpoint);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
Separator();
|
||||
gui::InputHexByte("PB", &manual_pb_, 1, 25.f);
|
||||
gui::InputHexWord("PC", &manual_pc_, 25.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 mem_edit;
|
||||
if (ImGui::Button("RAM")) {
|
||||
mem_edit.GotoAddrAndHighlight(0x7E0000, 0x7E0001);
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("MemoryViewerTable", 2,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) {
|
||||
ImGui::TableSetupColumn("Bookmarks");
|
||||
ImGui::TableSetupColumn("Memory");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
TableNextColumn();
|
||||
if (ImGui::CollapsingHeader("Bookmarks", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
// Input for adding a new bookmark
|
||||
static char nameBuf[256];
|
||||
static uint64_t uint64StringBuf;
|
||||
ImGui::InputText("Name", nameBuf, IM_ARRAYSIZE(nameBuf));
|
||||
gui::InputHex("Address", &uint64StringBuf);
|
||||
if (ImGui::Button("Add Bookmark")) {
|
||||
bookmarks.push_back({nameBuf, uint64StringBuf});
|
||||
memset(nameBuf, 0, sizeof(nameBuf));
|
||||
uint64StringBuf = 0;
|
||||
}
|
||||
|
||||
// Tree view of bookmarks
|
||||
for (const auto& bookmark : bookmarks) {
|
||||
if (ImGui::TreeNode(bookmark.name.c_str(), ICON_MD_STAR)) {
|
||||
auto bookmark_string = absl::StrFormat(
|
||||
"%s: 0x%08X", bookmark.name.c_str(), bookmark.value);
|
||||
if (ImGui::Selectable(bookmark_string.c_str())) {
|
||||
mem_edit.GotoAddrAndHighlight(static_cast<ImU64>(bookmark.value),
|
||||
1);
|
||||
}
|
||||
SameLine();
|
||||
if (ImGui::Button("Delete")) {
|
||||
// Logic to delete the bookmark
|
||||
bookmarks.erase(std::remove_if(bookmarks.begin(), bookmarks.end(),
|
||||
[&](const Bookmark& b) {
|
||||
return b.name == bookmark.name &&
|
||||
b.value == bookmark.value;
|
||||
}),
|
||||
bookmarks.end());
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
mem_edit.DrawContents((void*)snes_.Memory()->data(),
|
||||
snes_.Memory()->size());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::RenderCpuInstructionLog(
|
||||
const std::vector<InstructionEntry>& instructionLog) {
|
||||
if (ImGui::CollapsingHeader("CPU Instruction Log")) {
|
||||
// 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);
|
||||
|
||||
// Instruction list
|
||||
ImGui::BeginChild("InstructionList", ImVec2(0, 0),
|
||||
ImGuiChildFlags_None);
|
||||
for (const auto& entry : instructionLog) {
|
||||
if (ShouldDisplay(entry, filterBuf, showAllOpcodes)) {
|
||||
if (ImGui::Selectable(
|
||||
absl::StrFormat("%06X: %s %s", entry.address,
|
||||
opcode_to_mnemonic.at(entry.opcode),
|
||||
entry.operands)
|
||||
.c_str())) {
|
||||
// Logic to handle click (e.g., jump to address, set breakpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
53
src/app/emu/emulator.h
Normal file
53
src/app/emu/emulator.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef YAZE_APP_CORE_EMULATOR_H
|
||||
#define YAZE_APP_CORE_EMULATOR_H
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/snes.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class Emulator : public SharedROM {
|
||||
public:
|
||||
void Run();
|
||||
|
||||
private:
|
||||
void RenderNavBar();
|
||||
void HandleEvents();
|
||||
|
||||
void RenderEmulator();
|
||||
void RenderSnesPpu();
|
||||
void RenderBreakpointList();
|
||||
void RenderCpuState(CPU& cpu);
|
||||
void RenderMemoryViewer();
|
||||
|
||||
struct Bookmark {
|
||||
std::string name;
|
||||
uint64_t value;
|
||||
};
|
||||
std::vector<Bookmark> bookmarks;
|
||||
|
||||
void RenderCpuInstructionLog(
|
||||
const std::vector<InstructionEntry>& instructionLog);
|
||||
|
||||
SNES snes_;
|
||||
uint16_t manual_pc_ = 0;
|
||||
uint8_t manual_pb_ = 0;
|
||||
|
||||
bool power_ = false;
|
||||
bool loading_ = false;
|
||||
bool running_ = false;
|
||||
bool step_ = true;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_CORE_EMULATOR_H
|
||||
75
src/app/emu/memory/dma.cc
Normal file
75
src/app/emu/memory/dma.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "app/emu/memory/dma.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void DMA::StartDMATransfer(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, DASn)
|
||||
// ...
|
||||
|
||||
// Determine the transfer direction based on the DMAPn register
|
||||
bool fromMemory = (ch.DMAPn & 0x80) != 0;
|
||||
|
||||
// Determine the transfer size based on the DMAPn register
|
||||
bool transferTwoBytes = (ch.DMAPn & 0x40) != 0;
|
||||
|
||||
// Perform the DMA transfer based on the channel parameters
|
||||
std::cout << "Starting DMA transfer for channel " << i << std::endl;
|
||||
|
||||
for (uint16_t j = 0; j < ch.DASn; ++j) {
|
||||
// Read a byte or two bytes from memory based on the transfer size
|
||||
// ...
|
||||
|
||||
// Write the data to the B-bus address (BBADn) if transferring from
|
||||
// memory
|
||||
// ...
|
||||
|
||||
// Update the A1Tn register based on the transfer direction
|
||||
if (fromMemory) {
|
||||
ch.A1Tn += transferTwoBytes ? 2 : 1;
|
||||
} else {
|
||||
ch.A1Tn -= transferTwoBytes ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the channel registers after the transfer (e.g., A1Tn, DASn)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
MDMAEN = channelMask; // Set the MDMAEN register to the channel mask
|
||||
}
|
||||
|
||||
void DMA::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
|
||||
// ...
|
||||
}
|
||||
}
|
||||
HDMAEN = channelMask; // Set the HDMAEN register to the channel mask
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
59
src/app/emu/memory/dma.h
Normal file
59
src/app/emu/memory/dma.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef YAZE_APP_EMU_MEMORY_DMA_H
|
||||
#define YAZE_APP_EMU_MEMORY_DMA_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
// Direct Memory Address
|
||||
class DMA {
|
||||
public:
|
||||
DMA() {
|
||||
// Initialize DMA and HDMA channels
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
channels[i].DMAPn = 0;
|
||||
channels[i].BBADn = 0;
|
||||
channels[i].UNUSEDn = 0;
|
||||
channels[i].A1Tn = 0xFFFFFF;
|
||||
channels[i].DASn = 0xFFFF;
|
||||
channels[i].A2An = 0xFFFF;
|
||||
channels[i].NLTRn = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
// DMA Transfer Modes
|
||||
enum class DMA_TRANSFER_TYPE {
|
||||
OAM,
|
||||
PPUDATA,
|
||||
CGDATA,
|
||||
FILL_VRAM,
|
||||
CLEAR_VRAM,
|
||||
RESET_VRAM
|
||||
};
|
||||
|
||||
// Functions for handling DMA and HDMA transfers
|
||||
void StartDMATransfer(uint8_t channels);
|
||||
void EnableHDMATransfers(uint8_t channels);
|
||||
|
||||
// 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];
|
||||
|
||||
uint8_t MDMAEN = 0; // Start DMA transfer
|
||||
uint8_t HDMAEN = 0; // Enable HDMA transfers
|
||||
};
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_MEMORY_DMA_H
|
||||
82
src/app/emu/memory/memory.cc
Normal file
82
src/app/emu/memory/memory.cc
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/debug/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void DrawSnesMemoryMapping(const MemoryImpl& memory) {
|
||||
// Using those as a base value to create width/height that are factor of the
|
||||
// size of our font
|
||||
const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
|
||||
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||
const char* column_names[] = {
|
||||
"Offset", "0x00", "0x01", "0x02", "0x03", "0x04", "0x05", "0x06", "0x07",
|
||||
"0x08", "0x09", "0x0A", "0x0B", "0x0C", "0x0D", "0x0E", "0x0F", "0x10",
|
||||
"0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "0x17", "0x18", "0x19",
|
||||
"0x1A", "0x1B", "0x1C", "0x1D", "0x1E", "0x1F"};
|
||||
const int columns_count = IM_ARRAYSIZE(column_names);
|
||||
const int rows_count = 16;
|
||||
|
||||
static ImGuiTableFlags table_flags =
|
||||
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX |
|
||||
ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable |
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_HighlightHoveredColumn;
|
||||
static bool bools[columns_count * rows_count] = {};
|
||||
static int frozen_cols = 1;
|
||||
static int frozen_rows = 2;
|
||||
ImGui::CheckboxFlags("_ScrollX", &table_flags, ImGuiTableFlags_ScrollX);
|
||||
ImGui::CheckboxFlags("_ScrollY", &table_flags, ImGuiTableFlags_ScrollY);
|
||||
ImGui::CheckboxFlags("_NoBordersInBody", &table_flags,
|
||||
ImGuiTableFlags_NoBordersInBody);
|
||||
ImGui::CheckboxFlags("_HighlightHoveredColumn", &table_flags,
|
||||
ImGuiTableFlags_HighlightHoveredColumn);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::SliderInt("Frozen columns", &frozen_cols, 0, 2);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2);
|
||||
|
||||
if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags,
|
||||
ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) {
|
||||
ImGui::TableSetupColumn(
|
||||
column_names[0],
|
||||
ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder);
|
||||
for (int n = 1; n < columns_count; n++)
|
||||
ImGui::TableSetupColumn(column_names[n],
|
||||
ImGuiTableColumnFlags_AngledHeader |
|
||||
ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupScrollFreeze(frozen_cols, frozen_rows);
|
||||
|
||||
ImGui::TableAngledHeadersRow();
|
||||
ImGui::TableHeadersRow();
|
||||
for (int row = 0; row < rows_count; row++) {
|
||||
ImGui::PushID(row);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text("Offset 0x%04X", row);
|
||||
for (int column = 1; column < columns_count; column++)
|
||||
if (ImGui::TableSetColumnIndex(column)) {
|
||||
ImGui::PushID(column);
|
||||
ImGui::Checkbox("", &bools[row * columns_count + column]);
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
403
src/app/emu/memory/memory.h
Normal file
403
src/app/emu/memory/memory.h
Normal file
@@ -0,0 +1,403 @@
|
||||
#ifndef MEM_H
|
||||
#define MEM_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/debug/log.h"
|
||||
|
||||
// LoROM (Mode 20):
|
||||
|
||||
// Banks Offset Purpose
|
||||
// 00-3F 0000-1FFF LowRAM (shadowed from 7E)
|
||||
// 2000-2FFF PPU1, APU
|
||||
// 3000-3FFF SFX, DSP, etc.
|
||||
// 4000-41FF Controller
|
||||
// 4200-5FFF PPU2, DMA, etc.
|
||||
// 6000-7FFF Expansion RAM (reserved)
|
||||
// 8000-FFFF 32k ROM Chunk
|
||||
// 40-7C 0000-7FFF 32k ROM Chunk
|
||||
// 8000-FFFF 32k ROM Chunk
|
||||
// 7D 0000-FFFF SRAM
|
||||
// 7E 0000-1FFF LowRAM
|
||||
// 2000-FFFF System RAM
|
||||
// 7F 0000-FFFF System RAM
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
enum ROMSpeed { SLOW_ROM = 0x00, FAST_ROM = 0x07 };
|
||||
|
||||
enum BankSize { LOW_ROM = 0x00, HI_ROM = 0x01 };
|
||||
|
||||
enum ROMType {
|
||||
ROM_DEFAULT = 0x00,
|
||||
ROM_RAM = 0x01,
|
||||
ROM_SRAM = 0x02,
|
||||
ROM_DSP1 = 0x03,
|
||||
ROM_DSP1_RAM = 0x04,
|
||||
ROM_DSP1_SRAM = 0x05,
|
||||
FX = 0x06
|
||||
};
|
||||
|
||||
enum ROMSize {
|
||||
SIZE_2_MBIT = 0x08,
|
||||
SIZE_4_MBIT = 0x09,
|
||||
SIZE_8_MBIT = 0x0A,
|
||||
SIZE_16_MBIT = 0x0B,
|
||||
SIZE_32_MBIT = 0x0C
|
||||
};
|
||||
|
||||
enum SRAMSize {
|
||||
NO_SRAM = 0x00,
|
||||
SRAM_16_KBIT = 0x01,
|
||||
SRAM_32_KBIT = 0x02,
|
||||
SRAM_64_KBIT = 0x03
|
||||
};
|
||||
|
||||
enum CountryCode {
|
||||
JAPAN = 0x00,
|
||||
USA = 0x01,
|
||||
EUROPE_OCEANIA_ASIA = 0x02,
|
||||
// ... and other countries
|
||||
};
|
||||
|
||||
enum License {
|
||||
INVALID = 0,
|
||||
NINTENDO = 1,
|
||||
ZAMUSE = 5,
|
||||
CAPCOM = 8,
|
||||
// ... and other licenses
|
||||
};
|
||||
|
||||
class ROMInfo {
|
||||
public:
|
||||
std::string title;
|
||||
ROMSpeed romSpeed;
|
||||
BankSize bankSize;
|
||||
ROMType romType;
|
||||
ROMSize romSize;
|
||||
SRAMSize sramSize;
|
||||
CountryCode countryCode;
|
||||
License license;
|
||||
uint8_t version;
|
||||
uint16_t checksumComplement;
|
||||
uint16_t checksum;
|
||||
uint16_t nmiVblVector;
|
||||
uint16_t resetVector;
|
||||
};
|
||||
|
||||
class Observer {
|
||||
public:
|
||||
virtual ~Observer() = default;
|
||||
virtual void Notify(uint32_t address, uint8_t data) = 0;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// memory.h
|
||||
class Memory {
|
||||
public:
|
||||
virtual ~Memory() = default;
|
||||
virtual uint8_t ReadByte(uint32_t address) const = 0;
|
||||
virtual uint16_t ReadWord(uint32_t address) const = 0;
|
||||
virtual uint32_t ReadWordLong(uint32_t address) const = 0;
|
||||
virtual std::vector<uint8_t> ReadByteVector(uint32_t address,
|
||||
uint16_t length) const = 0;
|
||||
|
||||
virtual void WriteByte(uint32_t address, uint8_t value) = 0;
|
||||
virtual void WriteWord(uint32_t address, uint16_t value) = 0;
|
||||
virtual void WriteLong(uint32_t address, uint32_t value) = 0;
|
||||
|
||||
virtual void PushByte(uint8_t value) = 0;
|
||||
virtual uint8_t PopByte() = 0;
|
||||
virtual void PushWord(uint16_t value) = 0;
|
||||
virtual uint16_t PopWord() = 0;
|
||||
virtual void PushLong(uint32_t value) = 0;
|
||||
virtual uint32_t PopLong() = 0;
|
||||
|
||||
virtual uint16_t SP() const = 0;
|
||||
virtual void SetSP(uint16_t value) = 0;
|
||||
|
||||
virtual void ClearMemory() = 0;
|
||||
|
||||
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 };
|
||||
|
||||
class MemoryImpl : public Memory, public Loggable {
|
||||
public:
|
||||
void Initialize(const std::vector<uint8_t>& 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;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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<uint16_t>(memory_.at(mapped_address)) |
|
||||
(static_cast<uint16_t>(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<uint32_t>(memory_.at(mapped_address)) |
|
||||
(static_cast<uint32_t>(memory_.at(mapped_address + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(mapped_address + 2)) << 16);
|
||||
}
|
||||
std::vector<uint8_t> ReadByteVector(uint32_t address,
|
||||
uint16_t length) const override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
NotifyObservers(mapped_address, /*data=*/0);
|
||||
return std::vector<uint8_t>(memory_.begin() + mapped_address,
|
||||
memory_.begin() + mapped_address + length);
|
||||
}
|
||||
|
||||
void WriteByte(uint32_t address, uint8_t value) override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
memory_[mapped_address] = value;
|
||||
}
|
||||
void WriteWord(uint32_t address, uint16_t value) override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
memory_.at(mapped_address) = value & 0xFF;
|
||||
memory_.at(mapped_address + 1) = (value >> 8) & 0xFF;
|
||||
}
|
||||
void WriteLong(uint32_t address, uint32_t value) override {
|
||||
uint32_t mapped_address = GetMappedAddress(address);
|
||||
memory_.at(mapped_address) = value & 0xFF;
|
||||
memory_.at(mapped_address + 1) = (value >> 8) & 0xFF;
|
||||
memory_.at(mapped_address + 2) = (value >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
// Stack operations
|
||||
void PushByte(uint8_t value) override {
|
||||
if (SP_ > 0x0100) {
|
||||
memory_.at(SP_--) = value;
|
||||
} else {
|
||||
// Handle stack underflow
|
||||
std::cout << "Stack underflow!" << std::endl;
|
||||
throw std::runtime_error("Stack underflow!");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PopByte() override {
|
||||
if (SP_ < 0x1FF) {
|
||||
return memory_.at(++SP_);
|
||||
} else {
|
||||
// Handle stack overflow
|
||||
std::cout << "Stack overflow!" << std::endl;
|
||||
throw std::runtime_error("Stack overflow!");
|
||||
}
|
||||
}
|
||||
|
||||
void PushWord(uint16_t value) override {
|
||||
PushByte(value >> 8);
|
||||
PushByte(value & 0xFF);
|
||||
}
|
||||
|
||||
uint16_t PopWord() override {
|
||||
uint8_t low = PopByte();
|
||||
uint8_t high = PopByte();
|
||||
return (static_cast<uint16_t>(high) << 8) | low;
|
||||
}
|
||||
|
||||
void PushLong(uint32_t value) override {
|
||||
PushByte(value >> 16);
|
||||
PushByte(value >> 8);
|
||||
PushByte(value & 0xFF);
|
||||
}
|
||||
|
||||
uint32_t PopLong() override {
|
||||
uint8_t low = PopByte();
|
||||
uint8_t mid = PopByte();
|
||||
uint8_t high = PopByte();
|
||||
return (static_cast<uint32_t>(high) << 16) |
|
||||
(static_cast<uint32_t>(mid) << 8) | low;
|
||||
}
|
||||
|
||||
void AddObserver(Observer* observer) { observers_.push_back(observer); }
|
||||
|
||||
// Stack Pointer access.
|
||||
uint16_t SP() const override { return SP_; }
|
||||
void SetSP(uint16_t value) override { SP_ = value; }
|
||||
void ClearMemory() override { std::fill(memory_.begin(), memory_.end(), 0); }
|
||||
|
||||
uint8_t at(int i) const override { return memory_[i]; }
|
||||
uint8_t operator[](int i) const override {
|
||||
if (i > memory_.size()) {
|
||||
std::cout << i << " out of bounds \n";
|
||||
return memory_[0];
|
||||
}
|
||||
return memory_[i];
|
||||
}
|
||||
|
||||
auto size() const { return memory_.size(); }
|
||||
auto begin() const { return memory_.begin(); }
|
||||
auto end() const { return memory_.end(); }
|
||||
auto data() const { return memory_.data(); }
|
||||
|
||||
// Define memory regions
|
||||
std::vector<uint8_t> rom_;
|
||||
std::vector<uint8_t> ram_;
|
||||
std::vector<uint8_t> vram_;
|
||||
std::vector<uint8_t> 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);
|
||||
}
|
||||
}
|
||||
|
||||
bool verbose_ = false;
|
||||
|
||||
std::vector<Observer*> observers_;
|
||||
|
||||
// Memory (64KB)
|
||||
std::vector<uint8_t> memory_;
|
||||
|
||||
// Stack Pointer
|
||||
uint16_t SP_ = 0x01FF;
|
||||
|
||||
MemoryMapping mapping_ = MemoryMapping::SNES_LOROM;
|
||||
};
|
||||
|
||||
void DrawSnesMemoryMapping(const MemoryImpl& memory);
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // MEM_H
|
||||
189
src/app/emu/memory/mock_memory.h
Normal file
189
src/app/emu/memory/mock_memory.h
Normal file
@@ -0,0 +1,189 @@
|
||||
#ifndef YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
#define YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
using yaze::app::emu::Clock;
|
||||
using yaze::app::emu::CPU;
|
||||
using yaze::app::emu::Memory;
|
||||
|
||||
class MockClock : public Clock {
|
||||
public:
|
||||
MOCK_METHOD(void, UpdateClock, (double delta), (override));
|
||||
MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override));
|
||||
MOCK_METHOD(void, ResetAccumulatedTime, (), (override));
|
||||
MOCK_METHOD(void, SetFrequency, (float new_frequency), (override));
|
||||
MOCK_METHOD(float, GetFrequency, (), (const, override));
|
||||
};
|
||||
|
||||
// 0x1000000 is 16 MB, simplifying the memory layout for testing
|
||||
// 2 MB is = 0x200000
|
||||
|
||||
class MockMemory : public Memory {
|
||||
public:
|
||||
MOCK_CONST_METHOD1(ReadByte, uint8_t(uint32_t address));
|
||||
MOCK_CONST_METHOD1(ReadWord, uint16_t(uint32_t address));
|
||||
MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint32_t address));
|
||||
MOCK_METHOD(std::vector<uint8_t>, ReadByteVector,
|
||||
(uint32_t address, uint16_t length), (const, override));
|
||||
|
||||
MOCK_METHOD2(WriteByte, void(uint32_t address, uint8_t value));
|
||||
MOCK_METHOD2(WriteWord, void(uint32_t address, uint16_t value));
|
||||
MOCK_METHOD2(WriteLong, void(uint32_t address, uint32_t value));
|
||||
|
||||
MOCK_METHOD1(PushByte, void(uint8_t value));
|
||||
MOCK_METHOD0(PopByte, uint8_t());
|
||||
MOCK_METHOD1(PushWord, void(uint16_t value));
|
||||
MOCK_METHOD0(PopWord, uint16_t());
|
||||
MOCK_METHOD1(PushLong, void(uint32_t value));
|
||||
MOCK_METHOD0(PopLong, uint32_t());
|
||||
|
||||
MOCK_CONST_METHOD0(SP, uint16_t());
|
||||
MOCK_METHOD1(SetSP, void(uint16_t value));
|
||||
|
||||
MOCK_METHOD1(SetMemory, void(const std::vector<uint8_t>& data));
|
||||
MOCK_METHOD1(LoadData, void(const std::vector<uint8_t>& data));
|
||||
|
||||
MOCK_METHOD0(ClearMemory, void());
|
||||
|
||||
MOCK_CONST_METHOD1(at, uint8_t(int i));
|
||||
uint8_t operator[](int i) const override { return memory_[i]; }
|
||||
|
||||
void SetMemoryContents(const std::vector<uint8_t>& data) {
|
||||
if (data.size() > memory_.size()) {
|
||||
memory_.resize(data.size());
|
||||
}
|
||||
std::copy(data.begin(), data.end(), memory_.begin());
|
||||
}
|
||||
|
||||
void SetMemoryContents(const std::vector<uint16_t>& data) {
|
||||
if (data.size() > memory_.size()) {
|
||||
memory_.resize(data.size());
|
||||
}
|
||||
int i = 0;
|
||||
for (const auto& each : data) {
|
||||
memory_[i] = each & 0xFF;
|
||||
memory_[i + 1] = (each >> 8) & 0xFF;
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void InsertMemory(const uint64_t address, const std::vector<uint8_t>& data) {
|
||||
if (address > memory_.size()) {
|
||||
memory_.resize(address + data.size());
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (const auto& each : data) {
|
||||
memory_[address + i] = each;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize(const std::vector<uint8_t>& romData) {
|
||||
// 16 MB, simplifying the memory layout for testing
|
||||
memory_.resize(0x1000000);
|
||||
|
||||
// Clear memory
|
||||
std::fill(memory_.begin(), memory_.end(), 0);
|
||||
|
||||
// Load ROM data into mock memory
|
||||
size_t romSize = romData.size();
|
||||
size_t romAddress = 0;
|
||||
const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB
|
||||
for (size_t bank = 0x00; bank <= 0xBF; bank += 0x80) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Init() {
|
||||
ON_CALL(*this, ReadByte(::testing::_))
|
||||
.WillByDefault(
|
||||
[this](uint32_t address) { return memory_.at(address); });
|
||||
ON_CALL(*this, ReadWord(::testing::_))
|
||||
.WillByDefault([this](uint32_t address) {
|
||||
return static_cast<uint16_t>(memory_.at(address)) |
|
||||
(static_cast<uint16_t>(memory_.at(address + 1)) << 8);
|
||||
});
|
||||
ON_CALL(*this, ReadWordLong(::testing::_))
|
||||
.WillByDefault([this](uint32_t address) {
|
||||
return static_cast<uint32_t>(memory_.at(address)) |
|
||||
(static_cast<uint32_t>(memory_.at(address + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(address + 2)) << 16);
|
||||
});
|
||||
ON_CALL(*this, ReadByteVector(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint16_t length) {
|
||||
std::vector<uint8_t> data;
|
||||
for (int i = 0; i < length; i++) {
|
||||
data.push_back(memory_.at(address + i));
|
||||
}
|
||||
return data;
|
||||
});
|
||||
ON_CALL(*this, WriteByte(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint8_t value) {
|
||||
memory_[address] = value;
|
||||
});
|
||||
ON_CALL(*this, WriteWord(::testing::_, ::testing::_))
|
||||
.WillByDefault([this](uint32_t address, uint16_t value) {
|
||||
memory_[address] = value & 0xFF;
|
||||
memory_[address + 1] = (value >> 8) & 0xFF;
|
||||
});
|
||||
ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) {
|
||||
memory_.at(SP_--) = value;
|
||||
});
|
||||
ON_CALL(*this, PopByte()).WillByDefault([this]() {
|
||||
uint8_t value = memory_.at(SP_);
|
||||
this->SetSP(SP_ + 1);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, PushWord(::testing::_))
|
||||
.WillByDefault([this](uint16_t value) {
|
||||
memory_.at(SP_) = value & 0xFF;
|
||||
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
|
||||
this->SetSP(SP_ - 2);
|
||||
});
|
||||
ON_CALL(*this, PopWord()).WillByDefault([this]() {
|
||||
uint16_t value = static_cast<uint16_t>(memory_.at(SP_)) |
|
||||
(static_cast<uint16_t>(memory_.at(SP_ + 1)) << 8);
|
||||
this->SetSP(SP_ + 2);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, PushLong(::testing::_))
|
||||
.WillByDefault([this](uint32_t value) {
|
||||
memory_.at(SP_) = value & 0xFF;
|
||||
memory_.at(SP_ + 1) = (value >> 8) & 0xFF;
|
||||
memory_.at(SP_ + 2) = (value >> 16) & 0xFF;
|
||||
});
|
||||
ON_CALL(*this, PopLong()).WillByDefault([this]() {
|
||||
uint32_t value = static_cast<uint32_t>(memory_.at(SP_)) |
|
||||
(static_cast<uint32_t>(memory_.at(SP_ + 1)) << 8) |
|
||||
(static_cast<uint32_t>(memory_.at(SP_ + 2)) << 16);
|
||||
this->SetSP(SP_ + 3);
|
||||
return value;
|
||||
});
|
||||
ON_CALL(*this, SP()).WillByDefault([this]() { return SP_; });
|
||||
ON_CALL(*this, SetSP(::testing::_)).WillByDefault([this](uint16_t value) {
|
||||
SP_ = value;
|
||||
});
|
||||
ON_CALL(*this, ClearMemory()).WillByDefault([this]() {
|
||||
memory_.resize(64000, 0x00);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<uint8_t> memory_;
|
||||
uint16_t SP_ = 0x01FF;
|
||||
};
|
||||
|
||||
#endif // YAZE_TEST_MOCK_MOCK_MEMORY_H
|
||||
376
src/app/emu/snes.cc
Normal file
376
src/app/emu/snes.cc
Normal file
@@ -0,0 +1,376 @@
|
||||
#include "app/emu/snes.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/debug/debugger.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/video/ppu.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
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 audio_callback(void* userdata, uint8_t* stream, int len) {
|
||||
auto* apu = static_cast<APU*>(userdata);
|
||||
auto* buffer = reinterpret_cast<int16_t*>(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
|
||||
}
|
||||
}
|
||||
|
||||
} // 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
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
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<double>(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::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::HandleInput() {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::SaveState(const std::string& path) {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::LoadState(const std::string& path) {
|
||||
// ...
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
120
src/app/emu/snes.h
Normal file
120
src/app/emu/snes.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef YAZE_APP_EMU_SNES_H
|
||||
#define YAZE_APP_EMU_SNES_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#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"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class SNES : public DMA {
|
||||
public:
|
||||
SNES() = default;
|
||||
~SNES() = default;
|
||||
|
||||
ROMInfo ReadRomHeader(uint32_t offset);
|
||||
|
||||
// Initialization
|
||||
void Init(ROM& rom);
|
||||
|
||||
// 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();
|
||||
|
||||
// Controller input handling
|
||||
void HandleInput();
|
||||
|
||||
// Save/Load game state
|
||||
void SaveState(const std::string& path);
|
||||
void LoadState(const std::string& path);
|
||||
|
||||
bool running() const { return running_; }
|
||||
|
||||
auto cpu() -> CPU& { return cpu_; }
|
||||
auto ppu() -> Ppu& { return ppu_; }
|
||||
auto Memory() -> MemoryImpl* { return &memory_; }
|
||||
|
||||
void SetCpuMode(int mode) { cpu_mode_ = mode; }
|
||||
CPU::UpdateMode GetCpuMode() const {
|
||||
return static_cast<CPU::UpdateMode>(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);
|
||||
}
|
||||
|
||||
private:
|
||||
void WriteToRegister(uint16_t address, uint8_t value) {
|
||||
memory_.WriteByte(address, value);
|
||||
}
|
||||
|
||||
// Components of the SNES
|
||||
MemoryImpl memory_;
|
||||
ClockImpl clock_;
|
||||
AudioRamImpl audio_ram_;
|
||||
|
||||
CPU cpu_{memory_, clock_};
|
||||
Ppu ppu_{memory_, clock_};
|
||||
APU apu_{memory_, audio_ram_, clock_};
|
||||
|
||||
// Helper classes
|
||||
ROMInfo rom_info_;
|
||||
Debugger debugger;
|
||||
|
||||
// Currently loaded ROM
|
||||
std::vector<uint8_t> rom_data;
|
||||
|
||||
// Byte flag to indicate if the VBlank routine should be executed or not
|
||||
std::atomic<bool> v_blank_flag_;
|
||||
|
||||
// 32-bit counter to track the number of NMI interrupts
|
||||
std::atomic<uint32_t> frame_counter_;
|
||||
|
||||
// Other private member variables
|
||||
bool running_ = false;
|
||||
int scanline;
|
||||
int cpu_mode_ = 0;
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_SNES_H
|
||||
435
src/app/emu/video/ppu.cc
Normal file
435
src/app/emu/video/ppu.cc
Normal file
@@ -0,0 +1,435 @@
|
||||
#include "app/emu/video/ppu.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/memory/memory.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
using namespace PpuRegisters;
|
||||
|
||||
void Ppu::Update() {
|
||||
auto cycles_to_run = clock_.GetCycleCount();
|
||||
|
||||
UpdateInternalState(cycles_to_run);
|
||||
|
||||
// Render however many scanlines we're supposed to.
|
||||
if (current_scanline_ < visibleScanlines) {
|
||||
// Render the current scanline
|
||||
RenderScanline();
|
||||
|
||||
// Increment the current scanline
|
||||
current_scanline_++;
|
||||
}
|
||||
}
|
||||
|
||||
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::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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<BackgroundMode>(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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
case 1:
|
||||
// Render the first background layer
|
||||
bg1_tilemap_info = BGSC(memory_.ReadByte(BG1SC));
|
||||
bg1_chr_data = BGNBA(memory_.ReadByte(BG12NBA));
|
||||
break;
|
||||
case 2:
|
||||
// Render the second background layer
|
||||
bg2_tilemap_info = BGSC(memory_.ReadByte(BG2SC));
|
||||
bg2_chr_data = BGNBA(memory_.ReadByte(BG12NBA));
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void Ppu::RenderSprites() {}
|
||||
|
||||
void Ppu::UpdatePaletteData() {}
|
||||
|
||||
void Ppu::ApplyEffects() {}
|
||||
|
||||
void Ppu::ComposeLayers() {}
|
||||
|
||||
void Ppu::DisplayFrameBuffer() {
|
||||
if (!screen_->is_active()) {
|
||||
screen_->Create(256, 240, 24, frame_buffer_);
|
||||
rom()->RenderBitmap(screen_.get());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
393
src/app/emu/video/ppu.h
Normal file
393
src/app/emu/video/ppu.h
Normal file
@@ -0,0 +1,393 @@
|
||||
#ifndef YAZE_APP_EMU_PPU_H
|
||||
#define YAZE_APP_EMU_PPU_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/video/ppu_registers.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
using namespace yaze::app::emu::PpuRegisters;
|
||||
|
||||
class PpuInterface {
|
||||
public:
|
||||
virtual ~PpuInterface() = default;
|
||||
|
||||
// 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<uint8_t>& GetFrameBuffer() const = 0;
|
||||
virtual std::shared_ptr<gfx::Bitmap> 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
|
||||
enum class BackgroundMode {
|
||||
Mode0, // 4 layers, each 2bpp (4 colors)
|
||||
Mode1, // 2 layers, 4bpp (16 colors), 1 layer, 2bpp (4 colors)
|
||||
Mode2, // 2 layers, 4bpp (16 colors), 1 layer for offset-per-tile
|
||||
Mode3, // 1 layer, 8bpp (256 colors), 1 layer, 4bpp (16 colors)
|
||||
Mode4, // 1 layer, 8bpp (256 colors), 1 layer, 2bpp (4 colors)
|
||||
// 1 layer for offset-per-tile
|
||||
Mode5, // 1 layer, 4bpp (16 colors), 1 layer, 2bpp (4 colors) hi-res
|
||||
Mode6, // 1 layer, 4bpp (16 colors), 1 layer for offset-per-tile, hi-res
|
||||
Mode7, // 1 layer, 8bpp (256 colors), rotation/scaling
|
||||
};
|
||||
|
||||
// Enum representing sprite sizes
|
||||
enum class SpriteSize { Size8x8, Size16x16, Size32x32, Size64x64 };
|
||||
|
||||
// Struct representing a sprite's attributes
|
||||
struct SpriteAttributes {
|
||||
uint8_t x; // X position of the sprite
|
||||
uint8_t y; // Y position of the sprite
|
||||
uint16_t tile; // Tile number for the sprite
|
||||
uint8_t palette; // Palette number for the sprite
|
||||
uint8_t priority; // Priority for the sprite
|
||||
bool hFlip; // Horizontal flip flag
|
||||
bool vFlip; // Vertical flip flag
|
||||
};
|
||||
|
||||
// Struct representing a tilemap entry
|
||||
struct TilemapEntry {
|
||||
uint16_t tileNumber; // Tile number for the tile
|
||||
uint8_t palette; // Palette number for the tile
|
||||
uint8_t priority; // Priority for the tile
|
||||
bool hFlip; // Horizontal flip flag
|
||||
bool vFlip; // Vertical flip flag
|
||||
};
|
||||
|
||||
// Struct representing a tilemap
|
||||
struct Tilemap {
|
||||
std::vector<TilemapEntry> entries; // Entries for the tilemap
|
||||
};
|
||||
|
||||
// Struct representing a color
|
||||
struct Color {
|
||||
uint8_t r; // Red component
|
||||
uint8_t g; // Green component
|
||||
uint8_t b; // Blue component
|
||||
};
|
||||
|
||||
// Registers
|
||||
struct OAMSize {
|
||||
uint8_t base_selection : 3;
|
||||
uint8_t name_selection : 2;
|
||||
uint8_t object_size : 3;
|
||||
};
|
||||
|
||||
struct OAMAddress {
|
||||
uint8_t oam_address_low : 8;
|
||||
uint8_t oam_address_msb : 1;
|
||||
uint8_t oam_priority_rotation : 1;
|
||||
uint8_t unused : 6;
|
||||
};
|
||||
|
||||
struct TileMapLocation {
|
||||
uint8_t SC_size : 2;
|
||||
uint8_t tile_map_address : 5;
|
||||
uint8_t unused : 1;
|
||||
};
|
||||
|
||||
struct CharacterLocation {
|
||||
uint8_t BG1_address : 4;
|
||||
uint8_t BG2_address : 4;
|
||||
uint8_t BG3_address : 4;
|
||||
uint8_t BG4_address : 4;
|
||||
};
|
||||
|
||||
struct VideoPortControl {
|
||||
uint8_t increment_rate : 2;
|
||||
uint8_t full_graphic : 2;
|
||||
uint8_t increment_mode : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct ScreenDisplay {
|
||||
uint8_t brightness : 4;
|
||||
uint8_t disable_screen : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct ScreenMode {
|
||||
uint8_t general_screen_mode : 3;
|
||||
uint8_t priority : 1;
|
||||
uint8_t BG1_tile_size : 1;
|
||||
uint8_t BG2_tile_size : 1;
|
||||
uint8_t BG3_tile_size : 1;
|
||||
uint8_t BG4_tile_size : 1;
|
||||
};
|
||||
|
||||
struct ScrollRegister {
|
||||
uint8_t offset : 8;
|
||||
uint8_t mode7_bits : 3;
|
||||
uint8_t unused : 5;
|
||||
};
|
||||
|
||||
struct MainSubScreenDesignation {
|
||||
uint8_t BG1_enable : 1;
|
||||
uint8_t BG2_enable : 1;
|
||||
uint8_t BG3_enable : 1;
|
||||
uint8_t BG4_enable : 1;
|
||||
uint8_t sprites_enable : 1;
|
||||
uint8_t unused : 3;
|
||||
};
|
||||
|
||||
struct WindowMaskSettings {
|
||||
uint8_t BG1_clip_in_out : 1;
|
||||
uint8_t BG1_enable : 1;
|
||||
uint8_t BG2_clip_in_out : 1;
|
||||
uint8_t BG2_enable : 1;
|
||||
uint8_t BG3_clip_in_out : 1;
|
||||
uint8_t BG3_enable : 1;
|
||||
uint8_t BG4_clip_in_out : 1;
|
||||
uint8_t BG4_enable : 1;
|
||||
};
|
||||
|
||||
struct WindowMaskSettings2 {
|
||||
uint8_t sprites_clip_in_out : 1;
|
||||
uint8_t sprites_enable : 1;
|
||||
uint8_t color_windows_clip_in_out : 1;
|
||||
uint8_t color_windows_enable : 1;
|
||||
uint8_t unused : 4;
|
||||
};
|
||||
|
||||
struct WindowPosition {
|
||||
uint8_t position : 8;
|
||||
};
|
||||
|
||||
struct MaskLogicSettings {
|
||||
uint8_t BG1_mask_logic : 2;
|
||||
uint8_t BG2_mask_logic : 2;
|
||||
uint8_t BG3_mask_logic : 2;
|
||||
uint8_t BG4_mask_logic : 2;
|
||||
};
|
||||
|
||||
// Counter/IRQ/NMI Registers
|
||||
struct CounterIrqNmiRegisters {
|
||||
uint8_t softwareLatchHvCounter; // Register $2137
|
||||
uint16_t horizontalScanLocation; // Register $213C
|
||||
uint16_t verticalScanLocation; // Register $213D
|
||||
uint8_t counterEnable; // Register $4200
|
||||
uint16_t horizontalIrqTrigger; // Register $4207/$4208
|
||||
uint16_t verticalIrqTrigger; // Register $4209/$420A
|
||||
uint8_t nmiRegister; // Register $4210
|
||||
uint8_t irqRegister; // Register $4211
|
||||
uint8_t statusRegisterIrq; // Register $4212
|
||||
};
|
||||
|
||||
// Joypad Registers
|
||||
struct JoypadRegisters {
|
||||
uint16_t joypadData[4]; // Register $4218 to $421F
|
||||
uint8_t oldStyleJoypadRegisters[2]; // Registers $4016/$4217
|
||||
};
|
||||
|
||||
// DMA Registers
|
||||
struct DmaRegisters {
|
||||
uint8_t startDmaTransfer; // Register $420B
|
||||
uint8_t enableHDmaTransfer; // Register $420C
|
||||
uint8_t dmacontrol_register_ister[8]; // Register $43?0
|
||||
uint8_t dmaDestinationAddress[8]; // Register $43?1
|
||||
uint32_t dmaSourceAddress[8]; // Register $43?2/$43?3/$43?4
|
||||
uint16_t bytesToTransfer[8]; // Register $43?5/$43?6/$43?7
|
||||
uint16_t hdmaCountPointer[8]; // Register $43?8/$43?9
|
||||
uint8_t scanlinesLeft[8]; // Register $43?A
|
||||
};
|
||||
|
||||
// WRAM access Registers
|
||||
struct WramAccessRegisters {
|
||||
uint8_t dataByte; // Register $2180
|
||||
uint32_t address; // Register $2181/$2182/$2183
|
||||
};
|
||||
|
||||
struct Tile {
|
||||
uint16_t index; // Index of the tile in VRAM
|
||||
uint8_t palette; // Palette number used for this tile
|
||||
bool flip_x; // Horizontal flip flag
|
||||
bool flip_y; // Vertical flip flag
|
||||
uint8_t priority; // Priority of this tile
|
||||
};
|
||||
|
||||
struct BackgroundLayer {
|
||||
enum class Size { SIZE_32x32, SIZE_64x32, SIZE_32x64, SIZE_64x64 };
|
||||
|
||||
enum class ColorDepth { BPP_2, BPP_4, BPP_8 };
|
||||
|
||||
Size size; // Size of the background layer
|
||||
ColorDepth color_depth; // Color depth of the background layer
|
||||
std::vector<Tile> tilemap; // Tilemap data
|
||||
std::vector<uint8_t> tile_data; // Tile data in VRAM
|
||||
uint16_t tilemap_base_address; // Base address of the tilemap in VRAM
|
||||
uint16_t tile_data_base_address; // Base address of the tile data in VRAM
|
||||
uint8_t scroll_x; // Horizontal scroll offset
|
||||
uint8_t scroll_y; // Vertical scroll offset
|
||||
bool enabled; // Whether the background layer is enabled
|
||||
};
|
||||
|
||||
const int kPpuClockSpeed = 5369318; // 5.369318 MHz
|
||||
|
||||
class Ppu : public Observer, public SharedROM {
|
||||
public:
|
||||
// Initializes the PPU with the necessary resources and dependencies
|
||||
Ppu(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<gfx::Bitmap>(256, 240, 8, 0x100);
|
||||
screen_->set_active(false);
|
||||
}
|
||||
|
||||
// Resets the PPU to its initial state
|
||||
void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); }
|
||||
|
||||
// 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 Notify(uint32_t address, uint8_t data) override;
|
||||
|
||||
// Returns the pixel data for the current frame
|
||||
const std::vector<uint8_t>& GetFrameBuffer() const { return frame_buffer_; }
|
||||
|
||||
auto GetScreen() const { return screen_; }
|
||||
|
||||
private:
|
||||
// Updates internal state based on PPU register settings
|
||||
void UpdateModeSettings();
|
||||
|
||||
// Internal methods to handle PPU rendering and operations
|
||||
void UpdateTileData();
|
||||
|
||||
// Fetches the tile map data from memory and stores it in an internal buffer
|
||||
void UpdateTileMapData();
|
||||
|
||||
// Renders a background layer
|
||||
void RenderBackground(int layer);
|
||||
|
||||
// Renders sprites (also known as objects)
|
||||
void RenderSprites();
|
||||
|
||||
// Fetches the palette data from CGRAM and stores it in an internal buffer
|
||||
void UpdatePaletteData();
|
||||
|
||||
// Applies effects to the layers based on the current mode and register
|
||||
void ApplyEffects();
|
||||
|
||||
// Combines the layers into a single image and stores it in the frame buffer
|
||||
void ComposeLayers();
|
||||
|
||||
// Sends the frame buffer to the display hardware (e.g., SDL2)
|
||||
void DisplayFrameBuffer();
|
||||
|
||||
// ===========================================================
|
||||
// Member variables to store internal PPU state and resources
|
||||
Memory& memory_;
|
||||
Clock& clock_;
|
||||
|
||||
// PPU registers
|
||||
OAMSize oam_size_;
|
||||
OAMAddress oam_address_;
|
||||
Mosaic mosaic_;
|
||||
std::array<BGSC, 4> bgsc_;
|
||||
std::array<BGNBA, 4> bgnba_;
|
||||
std::array<BGHOFS, 4> bghofs_;
|
||||
std::array<BGVOFS, 4> 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<BackgroundLayer, 4> bg_layers_;
|
||||
std::vector<SpriteAttributes> sprites_;
|
||||
std::vector<uint8_t> tile_data_;
|
||||
std::vector<uint8_t> frame_buffer_;
|
||||
std::shared_ptr<gfx::Bitmap> 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 emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EMU_PPU_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user