Compare commits
3 Commits
origin-202
...
pre-0.2.2-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
546093360f | ||
|
|
d94b7a3e81 | ||
|
|
e7470bdfac |
50
.github/workflows/cmake.yml
vendored
Normal file
50
.github/workflows/cmake.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: CMake
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'test/**'
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'test/**'
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||||
|
BUILD_TYPE: Debug
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||||
|
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||||
|
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- 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
|
||||||
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
# Build your program with the given configuration
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{github.workspace}}/build
|
||||||
|
# Execute tests defined by the CMake configuration.
|
||||||
|
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||||
|
run: ${{github.workspace}}/build/bin/yaze_test
|
||||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
.vscode/
|
||||||
|
src/lib/SDL2
|
||||||
|
src/lib/cmake
|
||||||
|
src/lib/GL
|
||||||
|
src/lib/abseil-cpp
|
||||||
|
src/lib/libGLEW.2.2.0.dylib
|
||||||
|
src/lib/libGLEW.2.2.dylib
|
||||||
|
src/lib/libGLEW.a
|
||||||
|
src/lib/libGLEW.dylib
|
||||||
|
src/lib/libSDL2_test.a
|
||||||
|
src/lib/libSDL2-2.0.0.dylib
|
||||||
|
src/lib/libSDL2.a
|
||||||
|
src/lib/libSDL2.dylib
|
||||||
|
src/lib/libSDL2main.a
|
||||||
|
checks.json
|
||||||
|
assets/lib/libasar.dll
|
||||||
|
cmake/yaze.plist.in
|
||||||
31
.gitmodules
vendored
31
.gitmodules
vendored
@@ -1,3 +1,30 @@
|
|||||||
[submodule "src/Library/imgui"]
|
[submodule "src/lib/imgui"]
|
||||||
path = src/Library/imgui
|
path = src/lib/imgui
|
||||||
url = https://github.com/ocornut/imgui.git
|
url = https://github.com/ocornut/imgui.git
|
||||||
|
[submodule "src/lib/ImGuiFileDialog"]
|
||||||
|
path = src/lib/ImGuiFileDialog
|
||||||
|
url = https://github.com/aiekick/ImGuiFileDialog.git
|
||||||
|
[submodule "src/lib/ImGuiColorTextEdit"]
|
||||||
|
path = src/lib/ImGuiColorTextEdit
|
||||||
|
url = https://github.com/BalazsJako/ImGuiColorTextEdit.git
|
||||||
|
[submodule "src/lib/sneshacking"]
|
||||||
|
path = src/lib/sneshacking
|
||||||
|
url = https://github.com/Skarsnik/sneshacking.git
|
||||||
|
[submodule "assets/asm/alttp-hacker-workspace"]
|
||||||
|
path = assets/asm/alttp-hacker-workspace
|
||||||
|
url = https://github.com/scawful/alttp-hacker-workspace.git
|
||||||
|
[submodule "src/lib/abseil-cpp"]
|
||||||
|
path = src/lib/abseil-cpp
|
||||||
|
url = https://github.com/abseil/abseil-cpp.git
|
||||||
|
[submodule "src/lib/SDL"]
|
||||||
|
path = src/lib/SDL
|
||||||
|
url = https://github.com/libsdl-org/SDL.git
|
||||||
|
[submodule "src/lib/asar"]
|
||||||
|
path = src/lib/asar
|
||||||
|
url = https://github.com/RPGHacker/asar.git
|
||||||
|
[submodule "src/lib/snes_spc"]
|
||||||
|
path = src/lib/snes_spc
|
||||||
|
url = https://github.com/blarggs-audio-libraries/snes_spc.git
|
||||||
|
[submodule "src/lib/SDL_mixer"]
|
||||||
|
path = src/lib/SDL_mixer
|
||||||
|
url = https://github.com/libsdl-org/SDL_mixer.git
|
||||||
|
|||||||
57
CMakeLists.txt
Normal file
57
CMakeLists.txt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# CMake Specifications
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
# Yet Another Zelda3 Editor
|
||||||
|
# by scawful
|
||||||
|
project(yaze VERSION 0.01)
|
||||||
|
|
||||||
|
# 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_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||||
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
set(CMAKE_MODULE_LINKER_FLAGS \"-Wl,--no-undefined -Wl,--no-undefined\")
|
||||||
|
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
|
||||||
|
set(BUILD_SHARED_LIBS ON)
|
||||||
|
|
||||||
|
# Abseil Standard Specifications
|
||||||
|
include(cmake/absl.cmake)
|
||||||
|
add_subdirectory(src/lib/abseil-cpp)
|
||||||
|
|
||||||
|
include(cmake/openssl.cmake)
|
||||||
|
|
||||||
|
# Video Libraries
|
||||||
|
find_package(PNG REQUIRED)
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
include(cmake/sdl2.cmake)
|
||||||
|
|
||||||
|
# Asar
|
||||||
|
# add_subdirectory(src/lib/asar/src)
|
||||||
|
# include(cmake/asar.cmake)
|
||||||
|
|
||||||
|
# snes-spc
|
||||||
|
ADD_DEFINITIONS(-DSNES_SPC_EXPORTS)
|
||||||
|
set(SNES_SPC_SOURCES
|
||||||
|
"../src/lib/snes_spc/snes_spc/spc.cpp"
|
||||||
|
"../src/lib/snes_spc/snes_spc/SNES_SPC.cpp"
|
||||||
|
"../src/lib/snes_spc/snes_spc/SNES_SPC_misc.cpp"
|
||||||
|
"../src/lib/snes_spc/snes_spc/SNES_SPC_state.cpp"
|
||||||
|
"../src/lib/snes_spc/snes_spc/SPC_DSP.cpp"
|
||||||
|
"../src/lib/snes_spc/snes_spc/dsp.cpp"
|
||||||
|
"../src/lib/snes_spc/snes_spc/SPC_Filter.cpp"
|
||||||
|
"../src/lib/snes_spc/demo/wave_writer.c"
|
||||||
|
"../src/lib/snes_spc/demo/demo_util.c"
|
||||||
|
)
|
||||||
|
include_directories(src/lib/snes_spc/snes_spc)
|
||||||
|
ADD_LIBRARY(snes_spc STATIC ${SNES_SPC_SOURCES} src/app/zelda3/music/spc700.def)
|
||||||
|
|
||||||
|
# ImGui
|
||||||
|
include(cmake/imgui.cmake)
|
||||||
|
|
||||||
|
# Project Files
|
||||||
|
add_subdirectory(src)
|
||||||
|
add_subdirectory(test)
|
||||||
15
LICENSE
Normal file
15
LICENSE
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Copyright (C) 2022 Justin Scofield
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
52
README.md
Normal file
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Yet Another Zelda3 Editor
|
||||||
|
|
||||||
|
- Platform: Windows, macOS, GNU/Linux
|
||||||
|
- Dependencies: SDL2, ImGui
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo.
|
||||||
|
|
||||||
|
Takes heavy inspiration from ALTTP community efforts such as [Hyrule Magic](https://www.romhacking.net/utilities/200/) and [ZScream](https://github.com/Zarby89/ZScreamDungeon)
|
||||||
|
|
||||||
|
Building and installation
|
||||||
|
-------------------------
|
||||||
|
[CMake](http://www.cmake.org "CMake") is required to build yaze
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Screenshots
|
||||||
|
--------
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
1
assets/asm/alttp-hacker-workspace
Submodule
1
assets/asm/alttp-hacker-workspace
Submodule
Submodule assets/asm/alttp-hacker-workspace added at 2520fb70c3
32
assets/asm/mosaic_change.asm
Normal file
32
assets/asm/mosaic_change.asm
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
org <HOOK>
|
||||||
|
JML AreaCheck
|
||||||
|
|
||||||
|
org <EXPANDED_SPACE>
|
||||||
|
|
||||||
|
AreaCheck:
|
||||||
|
PHB : PHK : PLB
|
||||||
|
|
||||||
|
TAX
|
||||||
|
LDA .pool, X
|
||||||
|
|
||||||
|
BEQ .noMosaic1
|
||||||
|
PLB
|
||||||
|
JML $02AAE5
|
||||||
|
|
||||||
|
.noMosaic1
|
||||||
|
|
||||||
|
LDX $8A
|
||||||
|
LDA .pool, X
|
||||||
|
|
||||||
|
BEQ .noMosaic2
|
||||||
|
PLB
|
||||||
|
JML $02AAE5
|
||||||
|
|
||||||
|
.noMosaic2
|
||||||
|
|
||||||
|
PLB
|
||||||
|
JML $02AAF4
|
||||||
|
|
||||||
|
NOP
|
||||||
|
.pool
|
||||||
160
assets/asm/template_song.asm
Normal file
160
assets/asm/template_song.asm
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
;
|
||||||
|
; Credit to Zarby89
|
||||||
|
;
|
||||||
|
lorom
|
||||||
|
|
||||||
|
!End = $00
|
||||||
|
!Rest = $C9
|
||||||
|
!Tie = $C8
|
||||||
|
|
||||||
|
macro SetChannelVolume(v)
|
||||||
|
db $ED, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro SetMasterVolume(v)
|
||||||
|
db $E5, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro SetTempo(v)
|
||||||
|
db $E7, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro SetInstrument(v)
|
||||||
|
db $E0, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro CallSubroutine(addr, repeat)
|
||||||
|
db $EF
|
||||||
|
dw <addr>
|
||||||
|
db <repeat>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
;1/4 = $48
|
||||||
|
;1/4 double = $6C
|
||||||
|
;1/4 triplet = $30
|
||||||
|
;1/8 = $24
|
||||||
|
;1/8 double = $36
|
||||||
|
;1/8 triplet = $18
|
||||||
|
;1/16 = $12
|
||||||
|
;1/16 double = $1B
|
||||||
|
;1/32 = $09
|
||||||
|
; To make a whole note you tie 4 1/4 so something like
|
||||||
|
;%SetDuration(48)
|
||||||
|
;db !C4, !Tie, !Tie, !Tie ; will play a whole note (1/1)
|
||||||
|
;db !C4, !Tie ; will play a half note (1/2)
|
||||||
|
|
||||||
|
macro SetDuration(v)
|
||||||
|
db <v>, $7F
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
|
||||||
|
!C1 = $80
|
||||||
|
!C1s = $81
|
||||||
|
!D1 = $82
|
||||||
|
!D1s = $83
|
||||||
|
!E1 = $84
|
||||||
|
!F1 = $85
|
||||||
|
!F1s = $86
|
||||||
|
!G1 = $87
|
||||||
|
!G1s = $88
|
||||||
|
!A1 = $89
|
||||||
|
!A1s = $8A
|
||||||
|
!B1 = $8B
|
||||||
|
|
||||||
|
|
||||||
|
!C2 = $8C
|
||||||
|
!C2s = $8D
|
||||||
|
!D2 = $8E
|
||||||
|
!D2s = $8F
|
||||||
|
!E2 = $90
|
||||||
|
!F2 = $91
|
||||||
|
!F2s = $92
|
||||||
|
!G2 = $93
|
||||||
|
!G2s = $94
|
||||||
|
!A2 = $95
|
||||||
|
!A2s = $96
|
||||||
|
!B2 = $97
|
||||||
|
|
||||||
|
|
||||||
|
!C3 = $98
|
||||||
|
!C3s = $99
|
||||||
|
!D3 = $9A
|
||||||
|
!D3s = $9B
|
||||||
|
!E3 = $9C
|
||||||
|
!F3 = $9D
|
||||||
|
!F3s = $9E
|
||||||
|
!G3 = $9F
|
||||||
|
!G3s = $A0
|
||||||
|
!A3 = $A1
|
||||||
|
!A3s = $A2
|
||||||
|
!B3 = $A3
|
||||||
|
|
||||||
|
!C4 = $A4
|
||||||
|
!C4s = $A5
|
||||||
|
!D4 = $A6
|
||||||
|
!D4s = $A7
|
||||||
|
!E4 = $A8
|
||||||
|
!F4 = $A9
|
||||||
|
!F4s = $AA
|
||||||
|
!G4 = $AB
|
||||||
|
!G4s = $AC
|
||||||
|
!A4 = $AD
|
||||||
|
!A4s = $AE
|
||||||
|
!B4 = $AF
|
||||||
|
|
||||||
|
!C5 = $B0
|
||||||
|
!C5s = $B1
|
||||||
|
!D5 = $B2
|
||||||
|
!D5s = $B3
|
||||||
|
!E5 = $B4
|
||||||
|
!F5 = $B5
|
||||||
|
!F5s = $B6
|
||||||
|
!G5 = $B7
|
||||||
|
!G5s = $B8
|
||||||
|
!A5 = $B9
|
||||||
|
!A5s = $BA
|
||||||
|
!B5 = $BB
|
||||||
|
|
||||||
|
!C6 = $BC
|
||||||
|
!C6s = $BD
|
||||||
|
!D6 = $BE
|
||||||
|
!D6s = $BF
|
||||||
|
!E6 = $C0
|
||||||
|
!F6 = $C1
|
||||||
|
!F6s = $C2
|
||||||
|
!G6 = $C3
|
||||||
|
!G6s = $C4
|
||||||
|
!A6 = $C5
|
||||||
|
!A6s = $C6
|
||||||
|
!B6 = $C7
|
||||||
|
|
||||||
|
org $1A9FF8 ; Hyrule Castle (Song Header information)
|
||||||
|
Sections:
|
||||||
|
!ARAMAddr = $D0FF
|
||||||
|
!StartingAddr = Sections
|
||||||
|
dw !ARAMAddr+$0A
|
||||||
|
dw !ARAMAddr+$0A
|
||||||
|
dw $00FF
|
||||||
|
dw !ARAMAddr
|
||||||
|
dw $0000
|
||||||
|
|
||||||
|
Channels:
|
||||||
|
!ARAMC = !ARAMAddr-Sections
|
||||||
|
dw Channel0+!ARAMC
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
|
||||||
|
|
||||||
|
Channel0:
|
||||||
|
SetMasterVolume($80)
|
||||||
|
SetTempo($40)
|
||||||
|
SetInstrument($17)
|
||||||
|
|
||||||
|
db !Rest, !Rest, !Rest
|
||||||
|
|
||||||
|
db !End
|
||||||
BIN
assets/font/Cousine-Regular.ttf
Normal file
BIN
assets/font/Cousine-Regular.ttf
Normal file
Binary file not shown.
BIN
assets/font/DroidSans.ttf
Normal file
BIN
assets/font/DroidSans.ttf
Normal file
Binary file not shown.
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/font/Karla-Regular.ttf
Normal file
BIN
assets/font/Karla-Regular.ttf
Normal file
Binary file not shown.
BIN
assets/font/MaterialIcons-Regular.ttf
Normal file
BIN
assets/font/MaterialIcons-Regular.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.
BIN
assets/font/Roboto-Medium.ttf
Normal file
BIN
assets/font/Roboto-Medium.ttf
Normal file
Binary file not shown.
19
cmake/absl.cmake
Normal file
19
cmake/absl.cmake
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
set(ABSL_PROPAGATE_CXX_STD ON)
|
||||||
|
set(ABSL_CXX_STANDARD 17)
|
||||||
|
set(ABSL_USE_GOOGLETEST_HEAD ON)
|
||||||
|
set(ABSL_ENABLE_INSTALL ON)
|
||||||
|
set(
|
||||||
|
ABSL_TARGETS
|
||||||
|
absl::strings
|
||||||
|
absl::flags
|
||||||
|
absl::status
|
||||||
|
absl::statusor
|
||||||
|
absl::examine_stack
|
||||||
|
absl::stacktrace
|
||||||
|
absl::base
|
||||||
|
absl::config
|
||||||
|
absl::core_headers
|
||||||
|
absl::raw_logging_internal
|
||||||
|
absl::failure_signal_handler
|
||||||
|
absl::flat_hash_map
|
||||||
|
)
|
||||||
33
cmake/asar.cmake
Normal file
33
cmake/asar.cmake
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES)
|
||||||
|
target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR})
|
||||||
|
set(ASAR_GEN_EXE OFF)
|
||||||
|
set(ASAR_GEN_DLL ON)
|
||||||
|
set(ASAR_GEN_LIB ON)
|
||||||
|
set(ASAR_GEN_EXE_TEST OFF)
|
||||||
|
set(ASAR_GEN_DLL_TEST OFF)
|
||||||
|
|
||||||
|
set(ASAR_STATIC_SRC
|
||||||
|
"../src/lib/asar/src/asar/interface-lib.cpp"
|
||||||
|
"../src/lib/asar/src/asar/addr2line.cpp"
|
||||||
|
"../src/lib/asar/src/asar/arch-65816.cpp"
|
||||||
|
"../src/lib/asar/src/asar/arch-spc700.cpp"
|
||||||
|
"../src/lib/asar/src/asar/arch-superfx.cpp"
|
||||||
|
"../src/lib/asar/src/asar/assembleblock.cpp"
|
||||||
|
"../src/lib/asar/src/asar/crc32.cpp"
|
||||||
|
"../src/lib/asar/src/asar/libcon.cpp"
|
||||||
|
"../src/lib/asar/src/asar/libsmw.cpp"
|
||||||
|
"../src/lib/asar/src/asar/libstr.cpp"
|
||||||
|
"../src/lib/asar/src/asar/macro.cpp"
|
||||||
|
"../src/lib/asar/src/asar/main.cpp"
|
||||||
|
"../src/lib/asar/src/asar/asar_math.cpp"
|
||||||
|
"../src/lib/asar/src/asar/virtualfile.cpp"
|
||||||
|
"../src/lib/asar/src/asar/warnings.cpp"
|
||||||
|
"../src/lib/asar/src/asar/errors.cpp"
|
||||||
|
"../src/lib/asar/src/asar/platform/file-helpers.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32 OR MINGW)
|
||||||
|
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/windows/file-helpers-win32.cpp")
|
||||||
|
else()
|
||||||
|
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/linux/file-helpers-linux.cpp")
|
||||||
|
endif()
|
||||||
35
cmake/imgui.cmake
Normal file
35
cmake/imgui.cmake
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# gui libraries ---------------------------------------------------------------
|
||||||
|
set(IMGUI_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui)
|
||||||
|
file(GLOB IMGUI_SOURCES ${IMGUI_PATH}/*.cpp)
|
||||||
|
add_library("ImGui" STATIC ${IMGUI_SOURCES})
|
||||||
|
target_include_directories("ImGui" PUBLIC ${IMGUI_PATH})
|
||||||
|
target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR})
|
||||||
|
target_compile_definitions(ImGui PUBLIC
|
||||||
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
|
set(IMGUI_FILE_DLG_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiFileDialog)
|
||||||
|
file(GLOB IMGUI_FILE_DLG_SOURCES ${IMGUI_FILE_DLG_PATH}/*.cpp)
|
||||||
|
add_library("ImGuiFileDialog" STATIC ${IMGUI_FILE_DLG_SOURCES})
|
||||||
|
target_include_directories(ImGuiFileDialog PUBLIC ${IMGUI_PATH})
|
||||||
|
target_compile_definitions(ImGuiFileDialog PUBLIC
|
||||||
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
|
set(IMGUI_COLOR_TEXT_EDIT_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiColorTextEdit)
|
||||||
|
file(GLOB IMGUI_COLOR_TEXT_EDIT_SOURCES ${IMGUI_COLOR_TEXT_EDIT_PATH}/*.cpp)
|
||||||
|
add_library("ImGuiColorTextEdit" STATIC ${IMGUI_COLOR_TEXT_EDIT_SOURCES})
|
||||||
|
target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
|
||||||
|
target_compile_definitions(ImGuiColorTextEdit PUBLIC
|
||||||
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
|
set(
|
||||||
|
IMGUI_SRC
|
||||||
|
${IMGUI_PATH}/imgui.cpp
|
||||||
|
${IMGUI_PATH}/imgui_demo.cpp
|
||||||
|
${IMGUI_PATH}/imgui_draw.cpp
|
||||||
|
${IMGUI_PATH}/imgui_widgets.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
|
||||||
|
)
|
||||||
5
cmake/openssl.cmake
Normal file
5
cmake/openssl.cmake
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
if (UNIX)
|
||||||
|
set(OPENSSL_INCLUDE_DIR "/usr/local/Cellar/openssl@1.1/1.1.1q/include")
|
||||||
|
set(OPENSSL_CRYPTO_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libcrypto.dylib")
|
||||||
|
set(OPENSSL_SSL_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libssl.dylib")
|
||||||
|
endif()
|
||||||
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)
|
||||||
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
|
||||||
17
docs/windows-build.md
Normal file
17
docs/windows-build.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
For VSCode users, use the following CMake extensions with MinGW-w64
|
||||||
|
|
||||||
|
https://marketplace.visualstudio.com/items?itemName=twxs.cmake
|
||||||
|
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
|
||||||
|
|
||||||
|
https://www.msys2.org/
|
||||||
|
|
||||||
|
Add to environment variables `C:\msys64\mingw64\bin`
|
||||||
|
|
||||||
|
Install the following packages using `pacman -S <package-name>`
|
||||||
|
|
||||||
|
`mingw-w64-x86_64-gcc`
|
||||||
|
`mingw-w64-x86_64-gcc-libs`
|
||||||
|
`mingw-w64-x86_64-cmake`
|
||||||
|
`mingw-w64-x86_64-glew`
|
||||||
|
`mingw-w64-x86_64-lib-png`
|
||||||
137
src/CMakeLists.txt
Normal file
137
src/CMakeLists.txt
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
set(
|
||||||
|
YAZE_APP_CORE_SRC
|
||||||
|
app/core/common.cc
|
||||||
|
app/core/controller.cc
|
||||||
|
app/core/labeling.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
set(
|
||||||
|
YAZE_APP_EDITOR_SRC
|
||||||
|
app/editor/dungeon_editor.cc
|
||||||
|
app/editor/graphics_editor.cc
|
||||||
|
app/editor/master_editor.cc
|
||||||
|
app/editor/overworld_editor.cc
|
||||||
|
app/editor/screen_editor.cc
|
||||||
|
app/editor/sprite_editor.cc
|
||||||
|
app/editor/modules/music_editor.cc
|
||||||
|
app/editor/modules/palette_editor.cc
|
||||||
|
app/editor/modules/assembly_editor.cc
|
||||||
|
app/editor/modules/tile16_editor.cc
|
||||||
|
app/editor/modules/gfx_group_editor.cc
|
||||||
|
app/editor/context/gfx_context.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
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/overworld_map.cc
|
||||||
|
app/zelda3/overworld.cc
|
||||||
|
app/zelda3/screen/inventory.cc
|
||||||
|
app/zelda3/screen/title_screen.cc
|
||||||
|
app/zelda3/sprite/sprite.cc
|
||||||
|
app/zelda3/music/tracker.cc
|
||||||
|
app/zelda3/dungeon/room.cc
|
||||||
|
app/zelda3/dungeon/room_object.cc
|
||||||
|
app/zelda3/dungeon/object_renderer.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
set(
|
||||||
|
YAZE_GUI_SRC
|
||||||
|
app/gui/canvas.cc
|
||||||
|
app/gui/input.cc
|
||||||
|
app/gui/style.cc
|
||||||
|
app/gui/widgets.cc
|
||||||
|
app/gui/color.cc
|
||||||
|
app/gui/pipeline.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
set(
|
||||||
|
YAZE_APP_EMU_SRC
|
||||||
|
app/emu/emulator.cc
|
||||||
|
app/emu/audio/apu.cc
|
||||||
|
app/emu/audio/spc700.cc
|
||||||
|
app/emu/audio/dsp.cc
|
||||||
|
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 ws2_32)
|
||||||
|
add_definitions(-DSDL_MAIN_HANDLED)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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")
|
||||||
|
target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MACOS)
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/yaze.ico)
|
||||||
|
set_target_properties(yaze
|
||||||
|
PROPERTIES
|
||||||
|
BUNDLE True
|
||||||
|
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
|
||||||
|
)
|
||||||
|
elseif(UNIX)
|
||||||
|
set_target_properties(yaze
|
||||||
|
PROPERTIES
|
||||||
|
BUNDLE True
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set_target_properties(yaze
|
||||||
|
PROPERTIES
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||||
|
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
Submodule src/Library/imgui deleted from 0b1bcfcc20
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()
|
||||||
179
src/app/core/common.cc
Normal file
179
src/app/core/common.cc
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#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 {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hextodec has been imported from SNESDisasm to parse hex numbers
|
||||||
|
int HexToDec(char *input, int length) {
|
||||||
|
int result = 0;
|
||||||
|
int value;
|
||||||
|
int ceiling = length - 1;
|
||||||
|
int power16 = 16;
|
||||||
|
|
||||||
|
int j = ceiling;
|
||||||
|
|
||||||
|
for (; j >= 0; j--) {
|
||||||
|
if (input[j] >= 'A' && input[j] <= 'F') {
|
||||||
|
value = input[j] - 'F';
|
||||||
|
value += 15;
|
||||||
|
} else {
|
||||||
|
value = input[j] - '9';
|
||||||
|
value += 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j == ceiling) {
|
||||||
|
result += value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += (value * power16);
|
||||||
|
power16 *= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StringReplace(std::string &str, const std::string &from,
|
||||||
|
const std::string &to) {
|
||||||
|
size_t start = str.find(from);
|
||||||
|
if (start == std::string::npos) return false;
|
||||||
|
|
||||||
|
str.replace(start, from.length(), to);
|
||||||
|
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
|
||||||
278
src/app/core/common.h
Normal file
278
src/app/core/common.h
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
#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 {
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
#endif
|
||||||
893
src/app/core/constants.h
Normal file
893
src/app/core/constants.h
Normal file
@@ -0,0 +1,893 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_CONSTANTS_H
|
||||||
|
#define YAZE_APP_CORE_CONSTANTS_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
|
||||||
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
|
||||||
|
#define BASIC_BUTTON(w) if (ImGui::Button(w))
|
||||||
|
|
||||||
|
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
|
||||||
|
#define END_TAB_BAR() \
|
||||||
|
ImGui::EndTabBar(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TAB_ITEM(w) if (ImGui::BeginTabItem(w)) {
|
||||||
|
#define END_TAB_ITEM() \
|
||||||
|
ImGui::EndTabItem(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MENU_BAR() if (ImGui::BeginMenuBar()) {
|
||||||
|
#define END_MENU_BAR() \
|
||||||
|
ImGui::EndMenuBar(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MENU_ITEM(w) if (ImGui::MenuItem(w))
|
||||||
|
#define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v))
|
||||||
|
|
||||||
|
#define BUTTON_COLUMN(w) \
|
||||||
|
ImGui::TableNextColumn(); \
|
||||||
|
ImGui::Button(w);
|
||||||
|
|
||||||
|
#define TEXT_COLUMN(w) \
|
||||||
|
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; \
|
||||||
|
if (!error.ok()) { \
|
||||||
|
std::cout << error.ToString() << std::endl; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#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; \
|
||||||
|
if (!error.ok()) { \
|
||||||
|
return error; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSIGN_OR_RETURN(type_variable_name, expression) \
|
||||||
|
ASSIGN_OR_RETURN_IMPL(APPEND_NUMBER(error_or_value, __LINE__), \
|
||||||
|
type_variable_name, expression)
|
||||||
|
|
||||||
|
#define ASSIGN_OR_RETURN_IMPL(error_or_value, type_variable_name, expression) \
|
||||||
|
auto error_or_value = expression; \
|
||||||
|
if (!error_or_value.ok()) { \
|
||||||
|
return error_or_value.status(); \
|
||||||
|
} \
|
||||||
|
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<uint8_t>;
|
||||||
|
|
||||||
|
using OWBlockset = std::vector<std::vector<uint16_t>>;
|
||||||
|
struct OWMapTiles {
|
||||||
|
OWBlockset light_world; // 64 maps
|
||||||
|
OWBlockset dark_world; // 64 maps
|
||||||
|
OWBlockset special_world; // 32 maps
|
||||||
|
};
|
||||||
|
using OWMapTiles = struct OWMapTiles;
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
constexpr uint32_t kRedPen = 0xFF0000FF;
|
||||||
|
constexpr float kYazeVersion = 0.07;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Magic numbers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Bit set for object priority
|
||||||
|
constexpr ushort TilePriorityBit = 0x2000;
|
||||||
|
|
||||||
|
/// Bit set for object hflip
|
||||||
|
constexpr ushort TileHFlipBit = 0x4000;
|
||||||
|
|
||||||
|
/// Bit set for object vflip
|
||||||
|
constexpr ushort TileVFlipBit = 0x8000;
|
||||||
|
|
||||||
|
/// Bits used for tile name
|
||||||
|
constexpr ushort TileNameMask = 0x03FF;
|
||||||
|
|
||||||
|
constexpr int Uncompressed3BPPSize = 0x0600;
|
||||||
|
constexpr int UncompressedSheetSize = 0x0800;
|
||||||
|
|
||||||
|
constexpr int NumberOfRooms = 296;
|
||||||
|
|
||||||
|
constexpr int NumberOfColors = 3143;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Game Graphics
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
constexpr int tile_address = 0x1B52; // JP = Same
|
||||||
|
constexpr int tile_address_floor = 0x1B5A; // JP = Same
|
||||||
|
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 hud_palettes = 0xDD660;
|
||||||
|
constexpr int maxGfx = 0xC3FB5;
|
||||||
|
|
||||||
|
constexpr int kTilesheetWidth = 128;
|
||||||
|
constexpr int kTilesheetHeight = 32;
|
||||||
|
constexpr int kTilesheetDepth = 8;
|
||||||
|
|
||||||
|
// TEXT EDITOR RELATED CONSTANTS
|
||||||
|
constexpr int gfx_font = 0x70000; // 2bpp format
|
||||||
|
constexpr int text_data = 0xE0000;
|
||||||
|
constexpr int text_data2 = 0x75F40;
|
||||||
|
constexpr int pointers_dictionaries = 0x74703;
|
||||||
|
constexpr int characters_width = 0x74ADF;
|
||||||
|
|
||||||
|
constexpr int entrance_gfx_group = 0x5D97;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Gravestones related variables
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
constexpr int GravesYTilePos = 0x49968; // short (0x0F entries)
|
||||||
|
constexpr int GravesXTilePos = 0x49986; // short (0x0F entries)
|
||||||
|
constexpr int GravesTilemapPos = 0x499A4; // short (0x0F entries)
|
||||||
|
constexpr int GravesGFX = 0x499C2; // short (0x0F entries)
|
||||||
|
|
||||||
|
constexpr int GravesXPos = 0x4994A; // short (0x0F entries)
|
||||||
|
constexpr int GravesYLine = 0x4993A; // short (0x08 entries)
|
||||||
|
constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
|
||||||
|
|
||||||
|
constexpr int GraveLinkSpecialHole = 0x46DD9; // short
|
||||||
|
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Palettes Related Variables - This contain all the palettes of the game
|
||||||
|
// ============================================================================
|
||||||
|
constexpr int overworldPaletteMain = 0xDE6C8;
|
||||||
|
constexpr int overworldPaletteAuxialiary = 0xDE86C;
|
||||||
|
constexpr int overworldPaletteAnimated = 0xDE604;
|
||||||
|
constexpr int globalSpritePalettesLW = 0xDD218;
|
||||||
|
constexpr int globalSpritePalettesDW = 0xDD290;
|
||||||
|
// Green, Blue, Red, Bunny, Electrocuted (15 colors each)
|
||||||
|
constexpr int armorPalettes = 0xDD308;
|
||||||
|
constexpr int spritePalettesAux1 = 0xDD39E; // 7 colors each
|
||||||
|
constexpr int spritePalettesAux2 = 0xDD446; // 7 colors each
|
||||||
|
constexpr int spritePalettesAux3 = 0xDD4E0; // 7 colors each
|
||||||
|
constexpr int swordPalettes = 0xDD630; // 3 colors each - 4 entries
|
||||||
|
constexpr int shieldPalettes = 0xDD648; // 4 colors each - 3 entries
|
||||||
|
constexpr int hudPalettes = 0xDD660;
|
||||||
|
constexpr int dungeonMapPalettes = 0xDD70A; // 21 colors
|
||||||
|
constexpr int dungeonMainPalettes = 0xDD734; //(15*6) colors each - 20 entries
|
||||||
|
constexpr int dungeonMapBgPalettes = 0xDE544; // 16*6
|
||||||
|
// Mirrored Value at 0x75645 : 0x75625
|
||||||
|
constexpr int hardcodedGrassLW = 0x5FEA9;
|
||||||
|
constexpr int hardcodedGrassDW = 0x05FEB3; // 0x7564F
|
||||||
|
constexpr int hardcodedGrassSpecial = 0x75640;
|
||||||
|
constexpr int overworldMiniMapPalettes = 0x55B27;
|
||||||
|
constexpr int triforcePalette = 0x64425;
|
||||||
|
constexpr int crystalPalette = 0xF4CD3;
|
||||||
|
constexpr int customAreaSpecificBGPalette =
|
||||||
|
0x140000; // 2 bytes for each overworld area (320)
|
||||||
|
constexpr int customAreaSpecificBGASM = 0x140150;
|
||||||
|
constexpr int customAreaSpecificBGEnabled =
|
||||||
|
0x140140; // 1 byte, not 0 if enabled
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Names
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static const absl::string_view RoomEffect[] = {"Nothing",
|
||||||
|
"Nothing",
|
||||||
|
"Moving Floor",
|
||||||
|
"Moving Water",
|
||||||
|
"Trinexx Shell",
|
||||||
|
"Red Flashes",
|
||||||
|
"Light Torch to See Floor",
|
||||||
|
"Ganon's Darkness"};
|
||||||
|
|
||||||
|
static const absl::string_view RoomTag[] = {"Nothing",
|
||||||
|
|
||||||
|
"NW Kill Enemy to Open",
|
||||||
|
"NE Kill Enemy to Open",
|
||||||
|
"SW Kill Enemy to Open",
|
||||||
|
"SE Kill Enemy to Open",
|
||||||
|
"W Kill Enemy to Open",
|
||||||
|
"E Kill Enemy to Open",
|
||||||
|
"N Kill Enemy to Open",
|
||||||
|
"S Kill Enemy to Open",
|
||||||
|
"Clear Quadrant to Open",
|
||||||
|
"Clear Full Tile to Open",
|
||||||
|
|
||||||
|
"NW Push Block to Open",
|
||||||
|
"NE Push Block to Open",
|
||||||
|
"SW Push Block to Open",
|
||||||
|
"SE Push Block to Open",
|
||||||
|
"W Push Block to Open",
|
||||||
|
"E Push Block to Open",
|
||||||
|
"N Push Block to Open",
|
||||||
|
"S Push Block to Open",
|
||||||
|
"Push Block to Open",
|
||||||
|
"Pull Lever to Open",
|
||||||
|
"Collect Prize to Open",
|
||||||
|
|
||||||
|
"Hold Switch Open Door",
|
||||||
|
"Toggle Switch to Open Door",
|
||||||
|
"Turn off Water",
|
||||||
|
"Turn on Water",
|
||||||
|
"Water Gate",
|
||||||
|
"Water Twin",
|
||||||
|
"Moving Wall Right",
|
||||||
|
"Moving Wall Left",
|
||||||
|
"Crash",
|
||||||
|
"Crash",
|
||||||
|
"Push Switch Exploding Wall",
|
||||||
|
"Holes 0",
|
||||||
|
"Open Chest (Holes 0)",
|
||||||
|
"Holes 1",
|
||||||
|
"Holes 2",
|
||||||
|
"Defeat Boss for Dungeon Prize",
|
||||||
|
|
||||||
|
"SE Kill Enemy to Push Block",
|
||||||
|
"Trigger Switch Chest",
|
||||||
|
"Pull Lever Exploding Wall",
|
||||||
|
"NW Kill Enemy for Chest",
|
||||||
|
"NE Kill Enemy for Chest",
|
||||||
|
"SW Kill Enemy for Chest",
|
||||||
|
"SE Kill Enemy for Chest",
|
||||||
|
"W Kill Enemy for Chest",
|
||||||
|
"E Kill Enemy for Chest",
|
||||||
|
"N Kill Enemy for Chest",
|
||||||
|
"S Kill Enemy for Chest",
|
||||||
|
"Clear Quadrant for Chest",
|
||||||
|
"Clear Full Tile for Chest",
|
||||||
|
|
||||||
|
"Light Torches to Open",
|
||||||
|
"Holes 3",
|
||||||
|
"Holes 4",
|
||||||
|
"Holes 5",
|
||||||
|
"Holes 6",
|
||||||
|
"Agahnim Room",
|
||||||
|
"Holes 7",
|
||||||
|
"Holes 8",
|
||||||
|
"Open Chest for Holes 8",
|
||||||
|
"Push Block for Chest",
|
||||||
|
"Clear Room for Triforce Door",
|
||||||
|
"Light Torches for Chest",
|
||||||
|
"Kill Boss Again"};
|
||||||
|
|
||||||
|
static const absl::string_view SecretItemNames[] = {
|
||||||
|
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
|
||||||
|
"Bomb", "Heart ", "Blue Rupee",
|
||||||
|
|
||||||
|
"Key", "Arrow", "Bomb", "Heart", "Magic",
|
||||||
|
"Full Magic", "Cucco", "Green Soldier", "Bush Stal", "Blue Soldier",
|
||||||
|
|
||||||
|
"Landmine", "Heart", "Fairy", "Heart",
|
||||||
|
"Nothing ", // 22
|
||||||
|
|
||||||
|
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
|
||||||
|
|
||||||
|
static const absl::string_view TileTypeNames[] = {
|
||||||
|
"$00 Nothing (standard floor)",
|
||||||
|
"$01 Collision",
|
||||||
|
"$02 Collision",
|
||||||
|
"$03 Collision",
|
||||||
|
"$04 Collision",
|
||||||
|
"$05 Nothing (unused?)",
|
||||||
|
"$06 Nothing (unused?)",
|
||||||
|
"$07 Nothing (unused?)",
|
||||||
|
"$08 Deep water",
|
||||||
|
"$09 Shallow water",
|
||||||
|
"$0A Unknown? Possibly unused",
|
||||||
|
"$0B Collision (different in Overworld and unknown)",
|
||||||
|
"$0C Overlay mask",
|
||||||
|
"$0D Spike floor",
|
||||||
|
"$0E GT ice",
|
||||||
|
"$0F Ice palace ice",
|
||||||
|
"$10 Slope ◤",
|
||||||
|
"$11 Slope ◥",
|
||||||
|
"$12 Slope ◣",
|
||||||
|
"$13 Slope ◢",
|
||||||
|
"$14 Nothing (unused?)",
|
||||||
|
"$15 Nothing (unused?)",
|
||||||
|
"$16 Nothing (unused?)",
|
||||||
|
"$17 Nothing (unused?)",
|
||||||
|
"$18 Slope ◤",
|
||||||
|
"$19 Slope ◥",
|
||||||
|
"$1A Slope ◣",
|
||||||
|
"$1B Slope ◢",
|
||||||
|
"$1C Layer 2 overlay",
|
||||||
|
"$1D North single-layer auto stairs",
|
||||||
|
"$1E North layer-swap auto stairs",
|
||||||
|
"$1F North layer-swap auto stairs",
|
||||||
|
"$20 Pit",
|
||||||
|
"$21 Nothing (unused?)",
|
||||||
|
"$22 Manual stairs",
|
||||||
|
"$23 Pot switch",
|
||||||
|
"$24 Pressure switch",
|
||||||
|
"$25 Nothing (unused but referenced by somaria blocks)",
|
||||||
|
"$26 Collision (near stairs?)",
|
||||||
|
"$27 Brazier/Fence/Statue/Block/General hookable things",
|
||||||
|
"$28 North ledge",
|
||||||
|
"$29 South ledge",
|
||||||
|
"$2A East ledge",
|
||||||
|
"$2B West ledge",
|
||||||
|
"$2C ◤ ledge",
|
||||||
|
"$2D ◣ ledge",
|
||||||
|
"$2E ◥ ledge",
|
||||||
|
"$2F ◢ ledge",
|
||||||
|
"$30 Straight inter-room stairs south/up 0",
|
||||||
|
"$31 Straight inter-room stairs south/up 1",
|
||||||
|
"$32 Straight inter-room stairs south/up 2",
|
||||||
|
"$33 Straight inter-room stairs south/up 3",
|
||||||
|
"$34 Straight inter-room stairs north/down 0",
|
||||||
|
"$35 Straight inter-room stairs north/down 1",
|
||||||
|
"$36 Straight inter-room stairs north/down 2",
|
||||||
|
"$37 Straight inter-room stairs north/down 3",
|
||||||
|
"$38 Straight inter-room stairs north/down edge",
|
||||||
|
"$39 Straight inter-room stairs south/up edge",
|
||||||
|
"$3A Star tile (inactive on load)",
|
||||||
|
"$3B Star tile (active on load)",
|
||||||
|
"$3C Nothing (unused?)",
|
||||||
|
"$3D South single-layer auto stairs",
|
||||||
|
"$3E South layer-swap auto stairs",
|
||||||
|
"$3F South layer-swap auto stairs",
|
||||||
|
"$40 Thick grass",
|
||||||
|
"$41 Nothing (unused?)",
|
||||||
|
"$42 Gravestone / Tower of hera ledge shadows??",
|
||||||
|
"$43 Skull Woods entrance/Hera columns???",
|
||||||
|
"$44 Spike",
|
||||||
|
"$45 Nothing (unused?)",
|
||||||
|
"$46 Desert Tablet",
|
||||||
|
"$47 Nothing (unused?)",
|
||||||
|
"$48 Diggable ground",
|
||||||
|
"$49 Nothing (unused?)",
|
||||||
|
"$4A Diggable ground",
|
||||||
|
"$4B Warp tile",
|
||||||
|
"$4C Nothing (unused?) | Something unknown in overworld",
|
||||||
|
"$4D Nothing (unused?) | Something unknown in overworld",
|
||||||
|
"$4E Square corners in EP overworld",
|
||||||
|
"$4F Square corners in EP overworld",
|
||||||
|
"$50 Green bush",
|
||||||
|
"$51 Dark bush",
|
||||||
|
"$52 Gray rock",
|
||||||
|
"$53 Black rock",
|
||||||
|
"$54 Hint tile/Sign",
|
||||||
|
"$55 Big gray rock",
|
||||||
|
"$56 Big black rock",
|
||||||
|
"$57 Bonk rocks",
|
||||||
|
"$58 Chest 0",
|
||||||
|
"$59 Chest 1",
|
||||||
|
"$5A Chest 2",
|
||||||
|
"$5B Chest 3",
|
||||||
|
"$5C Chest 4",
|
||||||
|
"$5D Chest 5",
|
||||||
|
"$5E Spiral stairs",
|
||||||
|
"$5F Spiral stairs",
|
||||||
|
"$60 Rupee tile",
|
||||||
|
"$61 Nothing (unused?)",
|
||||||
|
"$62 Bombable floor",
|
||||||
|
"$63 Minigame chest",
|
||||||
|
"$64 Nothing (unused?)",
|
||||||
|
"$65 Nothing (unused?)",
|
||||||
|
"$66 Crystal peg down",
|
||||||
|
"$67 Crystal peg up",
|
||||||
|
"$68 Upwards conveyor",
|
||||||
|
"$69 Downwards conveyor",
|
||||||
|
"$6A Leftwards conveyor",
|
||||||
|
"$6B Rightwards conveyor",
|
||||||
|
"$6C North vines",
|
||||||
|
"$6D South vines",
|
||||||
|
"$6E West vines",
|
||||||
|
"$6F East vines",
|
||||||
|
"$70 Pot/Hammer peg/Push block 00",
|
||||||
|
"$71 Pot/Hammer peg/Push block 01",
|
||||||
|
"$72 Pot/Hammer peg/Push block 02",
|
||||||
|
"$73 Pot/Hammer peg/Push block 03",
|
||||||
|
"$74 Pot/Hammer peg/Push block 04",
|
||||||
|
"$75 Pot/Hammer peg/Push block 05",
|
||||||
|
"$76 Pot/Hammer peg/Push block 06",
|
||||||
|
"$77 Pot/Hammer peg/Push block 07",
|
||||||
|
"$78 Pot/Hammer peg/Push block 08",
|
||||||
|
"$79 Pot/Hammer peg/Push block 09",
|
||||||
|
"$7A Pot/Hammer peg/Push block 0A",
|
||||||
|
"$7B Pot/Hammer peg/Push block 0B",
|
||||||
|
"$7C Pot/Hammer peg/Push block 0C",
|
||||||
|
"$7D Pot/Hammer peg/Push block 0D",
|
||||||
|
"$7E Pot/Hammer peg/Push block 0E",
|
||||||
|
"$7F Pot/Hammer peg/Push block 0F",
|
||||||
|
"$80 North/South door",
|
||||||
|
"$81 East/West door",
|
||||||
|
"$82 North/South shutter door",
|
||||||
|
"$83 East/West shutter door",
|
||||||
|
"$84 North/South layer 2 door",
|
||||||
|
"$85 East/West layer 2 door",
|
||||||
|
"$86 North/South layer 2 shutter door",
|
||||||
|
"$87 East/West layer 2 shutter door",
|
||||||
|
"$88 Some type of door (?)",
|
||||||
|
"$89 East/West transport door",
|
||||||
|
"$8A Some type of door (?)",
|
||||||
|
"$8B Some type of door (?)",
|
||||||
|
"$8C Some type of door (?)",
|
||||||
|
"$8D Some type of door (?)",
|
||||||
|
"$8E Entrance door",
|
||||||
|
"$8F Entrance door",
|
||||||
|
"$90 Layer toggle shutter door (?)",
|
||||||
|
"$91 Layer toggle shutter door (?)",
|
||||||
|
"$92 Layer toggle shutter door (?)",
|
||||||
|
"$93 Layer toggle shutter door (?)",
|
||||||
|
"$94 Layer toggle shutter door (?)",
|
||||||
|
"$95 Layer toggle shutter door (?)",
|
||||||
|
"$96 Layer toggle shutter door (?)",
|
||||||
|
"$97 Layer toggle shutter door (?)",
|
||||||
|
"$98 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$99 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9A Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9B Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9C Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9D Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9E Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9F Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$A0 North/South Dungeon swap door",
|
||||||
|
"$A1 Dungeon toggle door (?)",
|
||||||
|
"$A2 Dungeon toggle door (?)",
|
||||||
|
"$A3 Dungeon toggle door (?)",
|
||||||
|
"$A4 Dungeon toggle door (?)",
|
||||||
|
"$A5 Dungeon toggle door (?)",
|
||||||
|
"$A6 Nothing (unused?)",
|
||||||
|
"$A7 Nothing (unused?)",
|
||||||
|
"$A8 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$A9 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AA Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AB Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AC Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AD Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AE Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AF Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$B0 Somaria ─",
|
||||||
|
"$B1 Somaria │",
|
||||||
|
"$B2 Somaria ┌",
|
||||||
|
"$B3 Somaria └",
|
||||||
|
"$B4 Somaria ┐",
|
||||||
|
"$B5 Somaria ┘",
|
||||||
|
"$B6 Somaria ⍰ 1 way",
|
||||||
|
"$B7 Somaria ┬",
|
||||||
|
"$B8 Somaria ┴",
|
||||||
|
"$B9 Somaria ├",
|
||||||
|
"$BA Somaria ┤",
|
||||||
|
"$BB Somaria ┼",
|
||||||
|
"$BC Somaria ⍰ 2 way",
|
||||||
|
"$BD Somaria ┼ crossover",
|
||||||
|
"$BE Pipe entrance",
|
||||||
|
"$BF Nothing (unused?)",
|
||||||
|
"$C0 Torch 00",
|
||||||
|
"$C1 Torch 01",
|
||||||
|
"$C2 Torch 02",
|
||||||
|
"$C3 Torch 03",
|
||||||
|
"$C4 Torch 04",
|
||||||
|
"$C5 Torch 05",
|
||||||
|
"$C6 Torch 06",
|
||||||
|
"$C7 Torch 07",
|
||||||
|
"$C8 Torch 08",
|
||||||
|
"$C9 Torch 09",
|
||||||
|
"$CA Torch 0A",
|
||||||
|
"$CB Torch 0B",
|
||||||
|
"$CC Torch 0C",
|
||||||
|
"$CD Torch 0D",
|
||||||
|
"$CE Torch 0E",
|
||||||
|
"$CF Torch 0F",
|
||||||
|
"$D0 Nothing (unused?)",
|
||||||
|
"$D1 Nothing (unused?)",
|
||||||
|
"$D2 Nothing (unused?)",
|
||||||
|
"$D3 Nothing (unused?)",
|
||||||
|
"$D4 Nothing (unused?)",
|
||||||
|
"$D5 Nothing (unused?)",
|
||||||
|
"$D6 Nothing (unused?)",
|
||||||
|
"$D7 Nothing (unused?)",
|
||||||
|
"$D8 Nothing (unused?)",
|
||||||
|
"$D9 Nothing (unused?)",
|
||||||
|
"$DA Nothing (unused?)",
|
||||||
|
"$DB Nothing (unused?)",
|
||||||
|
"$DC Nothing (unused?)",
|
||||||
|
"$DD Nothing (unused?)",
|
||||||
|
"$DE Nothing (unused?)",
|
||||||
|
"$DF Nothing (unused?)",
|
||||||
|
"$E0 Nothing (unused?)",
|
||||||
|
"$E1 Nothing (unused?)",
|
||||||
|
"$E2 Nothing (unused?)",
|
||||||
|
"$E3 Nothing (unused?)",
|
||||||
|
"$E4 Nothing (unused?)",
|
||||||
|
"$E5 Nothing (unused?)",
|
||||||
|
"$E6 Nothing (unused?)",
|
||||||
|
"$E7 Nothing (unused?)",
|
||||||
|
"$E8 Nothing (unused?)",
|
||||||
|
"$E9 Nothing (unused?)",
|
||||||
|
"$EA Nothing (unused?)",
|
||||||
|
"$EB Nothing (unused?)",
|
||||||
|
"$EC Nothing (unused?)",
|
||||||
|
"$ED Nothing (unused?)",
|
||||||
|
"$EE Nothing (unused?)",
|
||||||
|
"$EF Nothing (unused?)",
|
||||||
|
"$F0 Door 0 bottom",
|
||||||
|
"$F1 Door 1 bottom",
|
||||||
|
"$F2 Door 2 bottom",
|
||||||
|
"$F3 Door 3 bottom",
|
||||||
|
"$F4 Door X bottom? (unused?)",
|
||||||
|
"$F5 Door X bottom? (unused?)",
|
||||||
|
"$F6 Door X bottom? (unused?)",
|
||||||
|
"$F7 Door X bottom? (unused?)",
|
||||||
|
"$F8 Door 0 top",
|
||||||
|
"$F9 Door 1 top",
|
||||||
|
"$FA Door 2 top",
|
||||||
|
"$FB Door 3 top",
|
||||||
|
"$FC Door X top? (unused?)",
|
||||||
|
"$FD Door X top? (unused?)",
|
||||||
|
"$FE Door X top? (unused?)",
|
||||||
|
"$FF Door X top? (unused?)"};
|
||||||
|
|
||||||
|
static const absl::string_view kSpriteDefaultNames[]{
|
||||||
|
"00 Raven",
|
||||||
|
"01 Vulture",
|
||||||
|
"02 Flying Stalfos Head",
|
||||||
|
"03 No Pointer (Empty",
|
||||||
|
"04 Pull Switch (good",
|
||||||
|
"05 Pull Switch (unused",
|
||||||
|
"06 Pull Switch (bad",
|
||||||
|
"07 Pull Switch (unused",
|
||||||
|
"08 Octorock (one way",
|
||||||
|
"09 Moldorm (Boss",
|
||||||
|
"0A Octorock (four way",
|
||||||
|
"0B Chicken",
|
||||||
|
"0C Octorock (?",
|
||||||
|
"0D Buzzblock",
|
||||||
|
"0E Snapdragon",
|
||||||
|
"0F Octoballoon",
|
||||||
|
"10 Octoballon Hatchlings",
|
||||||
|
"11 Hinox",
|
||||||
|
"12 Moblin",
|
||||||
|
"13 Mini Helmasaure",
|
||||||
|
"14 Gargoyle's Domain Gate",
|
||||||
|
"15 Antifairy",
|
||||||
|
"16 Sahasrahla / Aginah",
|
||||||
|
"17 Bush Hoarder",
|
||||||
|
"18 Mini Moldorm",
|
||||||
|
"19 Poe",
|
||||||
|
"1A Dwarves",
|
||||||
|
"1B Arrow in wall",
|
||||||
|
"1C Statue",
|
||||||
|
"1D Weathervane",
|
||||||
|
"1E Crystal Switch",
|
||||||
|
"1F Bug-Catching Kid",
|
||||||
|
"20 Sluggula",
|
||||||
|
"21 Push Switch",
|
||||||
|
"22 Ropa",
|
||||||
|
"23 Red Bari",
|
||||||
|
"24 Blue Bari",
|
||||||
|
"25 Talking Tree",
|
||||||
|
"26 Hardhat Beetle",
|
||||||
|
"27 Deadrock",
|
||||||
|
"28 Storytellers",
|
||||||
|
"29 Blind Hideout attendant",
|
||||||
|
"2A Sweeping Lady",
|
||||||
|
"2B Storytellers",
|
||||||
|
"2C Lumberjacks",
|
||||||
|
"2D Telepathic Stones",
|
||||||
|
"2E Multipurpose Sprite",
|
||||||
|
"2F Race Npc",
|
||||||
|
"30 Person?",
|
||||||
|
"31 Fortune Teller",
|
||||||
|
"32 Angry Brothers",
|
||||||
|
"33 Pull for items",
|
||||||
|
"34 Scared Girl",
|
||||||
|
"35 Innkeeper",
|
||||||
|
"36 Witch",
|
||||||
|
"37 Waterfall",
|
||||||
|
"38 Arrow Target",
|
||||||
|
"39 Average Middle",
|
||||||
|
"3A Half Magic Bat",
|
||||||
|
"3B Dash Item",
|
||||||
|
"3C Village Kid",
|
||||||
|
"3D Signs? Chicken lady also showed up / Scared ladies outside houses.",
|
||||||
|
"3E Rock Hoarder",
|
||||||
|
"3F Tutorial Soldier",
|
||||||
|
"40 Lightning Lock",
|
||||||
|
"41 Blue Sword Soldier / Used by guards to detect player",
|
||||||
|
"42 Green Sword Soldier",
|
||||||
|
"43 Red Spear Soldier",
|
||||||
|
"44 Assault Sword Soldier",
|
||||||
|
"45 Green Spear Soldier",
|
||||||
|
"46 Blue Archer",
|
||||||
|
"47 Green Archer",
|
||||||
|
"48 Red Javelin Soldier",
|
||||||
|
"49 Red Javelin Soldier 2",
|
||||||
|
"4A Red Bomb Soldiers",
|
||||||
|
"4B Green Soldier Recruits",
|
||||||
|
"4C Geldman",
|
||||||
|
"4D Rabbit",
|
||||||
|
"4E Popo",
|
||||||
|
"4F Popo 2",
|
||||||
|
"50 Cannon Balls",
|
||||||
|
"51 Armos",
|
||||||
|
"52 Giant Zora",
|
||||||
|
"53 Armos Knights (Boss",
|
||||||
|
"54 Lanmolas (Boss",
|
||||||
|
"55 Fireball Zora",
|
||||||
|
"56 Walking Zora",
|
||||||
|
"57 Desert Palace Barriers",
|
||||||
|
"58 Crab",
|
||||||
|
"59 Bird",
|
||||||
|
"5A Squirrel",
|
||||||
|
"5B Spark (Left to Right",
|
||||||
|
"5C Spark (Right to Left",
|
||||||
|
"5D Roller (vertical moving",
|
||||||
|
"5E Roller (vertical moving",
|
||||||
|
"5F Roller",
|
||||||
|
"60 Roller (horizontal moving",
|
||||||
|
"61 Beamos",
|
||||||
|
"62 Master Sword",
|
||||||
|
"63 Devalant (Non",
|
||||||
|
"64 Devalant (Shooter",
|
||||||
|
"65 Shooting Gallery Proprietor",
|
||||||
|
"66 Moving Cannon Ball Shooters (Right",
|
||||||
|
"67 Moving Cannon Ball Shooters (Left",
|
||||||
|
"68 Moving Cannon Ball Shooters (Down",
|
||||||
|
"69 Moving Cannon Ball Shooters (Up",
|
||||||
|
"6A Ball N' Chain Trooper",
|
||||||
|
"6B Cannon Soldier",
|
||||||
|
"6C Mirror Portal",
|
||||||
|
"6D Rat",
|
||||||
|
"6E Rope",
|
||||||
|
"6F Keese",
|
||||||
|
"70 Helmasaur King Fireball",
|
||||||
|
"71 Leever",
|
||||||
|
"72 Activator for the ponds (where you throw in items",
|
||||||
|
"73 Uncle / Priest",
|
||||||
|
"74 Running Man",
|
||||||
|
"75 Bottle Salesman",
|
||||||
|
"76 Princess Zelda",
|
||||||
|
"77 Antifairy (Alternate",
|
||||||
|
"78 Village Elder",
|
||||||
|
"79 Bee",
|
||||||
|
"7A Agahnim",
|
||||||
|
"7B Agahnim Energy Ball",
|
||||||
|
"7C Hyu",
|
||||||
|
"7D Big Spike Trap",
|
||||||
|
"7E Guruguru Bar (Clockwise",
|
||||||
|
"7F Guruguru Bar (Counter Clockwise",
|
||||||
|
"80 Winder",
|
||||||
|
"81 Water Tektite",
|
||||||
|
"82 Antifairy Circle",
|
||||||
|
"83 Green Eyegore",
|
||||||
|
"84 Red Eyegore",
|
||||||
|
"85 Yellow Stalfos",
|
||||||
|
"86 Kodongos",
|
||||||
|
"87 Flames",
|
||||||
|
"88 Mothula (Boss",
|
||||||
|
"89 Mothula's Beam",
|
||||||
|
"8A Spike Trap",
|
||||||
|
"8B Gibdo",
|
||||||
|
"8C Arrghus (Boss",
|
||||||
|
"8D Arrghus spawn",
|
||||||
|
"8E Terrorpin",
|
||||||
|
"8F Slime",
|
||||||
|
"90 Wallmaster",
|
||||||
|
"91 Stalfos Knight",
|
||||||
|
"92 Helmasaur King",
|
||||||
|
"93 Bumper",
|
||||||
|
"94 Swimmers",
|
||||||
|
"95 Eye Laser (Right",
|
||||||
|
"96 Eye Laser (Left",
|
||||||
|
"97 Eye Laser (Down",
|
||||||
|
"98 Eye Laser (Up",
|
||||||
|
"99 Pengator",
|
||||||
|
"9A Kyameron",
|
||||||
|
"9B Wizzrobe",
|
||||||
|
"9C Tadpoles",
|
||||||
|
"9D Tadpoles",
|
||||||
|
"9E Ostrich (Haunted Grove",
|
||||||
|
"9F Flute",
|
||||||
|
"A0 Birds (Haunted Grove",
|
||||||
|
"A1 Freezor",
|
||||||
|
"A2 Kholdstare (Boss",
|
||||||
|
"A3 Kholdstare's Shell",
|
||||||
|
"A4 Falling Ice",
|
||||||
|
"A5 Zazak Fireball",
|
||||||
|
"A6 Red Zazak",
|
||||||
|
"A7 Stalfos",
|
||||||
|
"A8 Bomber Flying Creatures from Darkworld",
|
||||||
|
"A9 Bomber Flying Creatures from Darkworld",
|
||||||
|
"AA Pikit",
|
||||||
|
"AB Maiden",
|
||||||
|
"AC Apple",
|
||||||
|
"AD Lost Old Man",
|
||||||
|
"AE Down Pipe",
|
||||||
|
"AF Up Pipe",
|
||||||
|
"B0 Right Pip",
|
||||||
|
"B1 Left Pipe",
|
||||||
|
"B2 Good bee again?",
|
||||||
|
"B3 Hylian Inscription",
|
||||||
|
"B4 Thief?s chest (not the one that follows you",
|
||||||
|
"B5 Bomb Salesman",
|
||||||
|
"B6 Kiki",
|
||||||
|
"B7 Maiden following you in Blind Dungeon",
|
||||||
|
"B8 Monologue Testing Sprite",
|
||||||
|
"B9 Feuding Friends on Death Mountain",
|
||||||
|
"BA Whirlpool",
|
||||||
|
"BB Salesman / chestgame guy / 300 rupee giver guy / Chest game thief",
|
||||||
|
"BC Drunk in the inn",
|
||||||
|
"BD Vitreous (Large Eyeball",
|
||||||
|
"BE Vitreous (Small Eyeball",
|
||||||
|
"BF Vitreous' Lightning",
|
||||||
|
"C0 Monster in Lake of Ill Omen / Quake Medallion",
|
||||||
|
"C1 Agahnim teleporting Zelda to dark world",
|
||||||
|
"C2 Boulders",
|
||||||
|
"C3 Gibo",
|
||||||
|
"C4 Thief",
|
||||||
|
"C5 Medusa",
|
||||||
|
"C6 Four Way Fireball Spitters (spit when you use your sword",
|
||||||
|
"C7 Hokku",
|
||||||
|
"C8 Big Fairy who heals you",
|
||||||
|
"C9 Tektite",
|
||||||
|
"CA Chain Chomp",
|
||||||
|
"CB Trinexx",
|
||||||
|
"CC Another part of trinexx",
|
||||||
|
"CD Yet another part of trinexx",
|
||||||
|
"CE Blind The Thief (Boss)",
|
||||||
|
"CF Swamola",
|
||||||
|
"D0 Lynel",
|
||||||
|
"D1 Bunny Beam",
|
||||||
|
"D2 Flopping fish",
|
||||||
|
"D3 Stal",
|
||||||
|
"D4 Landmine",
|
||||||
|
"D5 Digging Game Proprietor",
|
||||||
|
"D6 Ganon",
|
||||||
|
"D7 Copy of Ganon",
|
||||||
|
"D8 Heart",
|
||||||
|
"D9 Green Rupee",
|
||||||
|
"DA Blue Rupee",
|
||||||
|
"DB Red Rupee",
|
||||||
|
"DC Bomb Refill (1)",
|
||||||
|
"DD Bomb Refill (4)",
|
||||||
|
"DE Bomb Refill (8)",
|
||||||
|
"DF Small Magic Refill",
|
||||||
|
"E0 Full Magic Refill",
|
||||||
|
"E1 Arrow Refill (5)",
|
||||||
|
"E2 Arrow Refill (10)",
|
||||||
|
"E3 Fairy",
|
||||||
|
"E4 Key",
|
||||||
|
"E5 Big Key",
|
||||||
|
"E6 Shield",
|
||||||
|
"E7 Mushroom",
|
||||||
|
"E8 Fake Master Sword",
|
||||||
|
"E9 Magic Shop dude / His items",
|
||||||
|
"EA Heart Container",
|
||||||
|
"EB Heart Piece",
|
||||||
|
"EC Bushes",
|
||||||
|
"ED Cane Of Somaria Platform",
|
||||||
|
"EE Mantle",
|
||||||
|
"EF Cane of Somaria Platform (Unused)",
|
||||||
|
"F0 Cane of Somaria Platform (Unused)",
|
||||||
|
"F1 Cane of Somaria Platform (Unused)",
|
||||||
|
"F2 Medallion Tablet",
|
||||||
|
"F3",
|
||||||
|
"F4 Falling Rocks",
|
||||||
|
"F5",
|
||||||
|
"F6",
|
||||||
|
"F7",
|
||||||
|
"F8",
|
||||||
|
"F9",
|
||||||
|
"FA",
|
||||||
|
"FB",
|
||||||
|
"FC",
|
||||||
|
"FD",
|
||||||
|
"FE",
|
||||||
|
"FF",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const absl::string_view overlordnames[] = {
|
||||||
|
"Overlord_SpritePositionTarget",
|
||||||
|
"Overlord_AllDirectionMetalBallFactory",
|
||||||
|
"Overlord_CascadeMetalBallFactory",
|
||||||
|
"Overlord_StalfosFactory",
|
||||||
|
"Overlord_StalfosTrap",
|
||||||
|
"Overlord_SnakeTrap",
|
||||||
|
"Overlord_MovingFloor",
|
||||||
|
"Overlord_ZolFactory",
|
||||||
|
"Overlord_WallMasterFactory",
|
||||||
|
"Overlord_CrumbleTilePath 1",
|
||||||
|
"Overlord_CrumbleTilePath 2",
|
||||||
|
"Overlord_CrumbleTilePath 3",
|
||||||
|
"Overlord_CrumbleTilePath 4",
|
||||||
|
"Overlord_CrumbleTilePath 5",
|
||||||
|
"Overlord_CrumbleTilePath 6",
|
||||||
|
"Overlord_PirogusuFactory 1",
|
||||||
|
"Overlord_PirogusuFactory 2",
|
||||||
|
"Overlord_PirogusuFactory 3",
|
||||||
|
"Overlord_PirogusuFactory 4",
|
||||||
|
"Overlord_FlyingTileFactory",
|
||||||
|
"Overlord_WizzrobeFactory",
|
||||||
|
"Overlord_ZoroFactory",
|
||||||
|
"Overlord_StalfosTrapTriggerWindow",
|
||||||
|
"Overlord_RedStalfosTrap",
|
||||||
|
"Overlord_ArmosCoordinator",
|
||||||
|
"Overlord_BombTrap",
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
321
src/app/core/controller.cc
Normal file
321
src/app/core/controller.cc
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
#include "controller.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_mixer.h>
|
||||||
|
#include <imgui/backends/imgui_impl_sdl2.h>
|
||||||
|
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
#include <imgui/imgui_internal.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#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 "app/gui/icons.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void InitializeKeymap() {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
|
||||||
|
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
|
||||||
|
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:
|
||||||
|
case SDLK_RETURN:
|
||||||
|
case SDLK_BACKSPACE:
|
||||||
|
case SDLK_LSHIFT:
|
||||||
|
case SDLK_LCTRL:
|
||||||
|
case SDLK_TAB:
|
||||||
|
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleKeyUp(SDL_Event &event) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
int key = event.key.keysym.scancode;
|
||||||
|
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
|
||||||
|
io.KeysDown[key] = (event.type == SDL_KEYDOWN);
|
||||||
|
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
|
||||||
|
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
||||||
|
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
||||||
|
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChangeWindowSizeEvent(SDL_Event &event) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.DisplaySize.x = static_cast<float>(event.window.data1);
|
||||||
|
io.DisplaySize.y = static_cast<float>(event.window.data2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleMouseMovement(int &wheel) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
int mouseX;
|
||||||
|
int mouseY;
|
||||||
|
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
|
||||||
|
|
||||||
|
io.DeltaTime = 1.0f / 60.0f;
|
||||||
|
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
|
||||||
|
|
||||||
|
absl::Status Controller::OnEntry() {
|
||||||
|
RETURN_IF_ERROR(CreateSDL_Window())
|
||||||
|
RETURN_IF_ERROR(CreateRenderer())
|
||||||
|
RETURN_IF_ERROR(CreateGuiContext())
|
||||||
|
InitializeKeymap();
|
||||||
|
master_editor_.SetupScreen(renderer_);
|
||||||
|
active_ = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::OnInput() {
|
||||||
|
int wheel = 0;
|
||||||
|
SDL_Event event;
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
HandleKeyDown(event);
|
||||||
|
break;
|
||||||
|
case SDL_KEYUP:
|
||||||
|
HandleKeyUp(event);
|
||||||
|
break;
|
||||||
|
case SDL_TEXTINPUT:
|
||||||
|
io.AddInputCharactersUTF8(event.text.text);
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEWHEEL:
|
||||||
|
wheel = event.wheel.y;
|
||||||
|
break;
|
||||||
|
case SDL_WINDOWEVENT:
|
||||||
|
switch (event.window.event) {
|
||||||
|
case SDL_WINDOWEVENT_CLOSE:
|
||||||
|
CloseWindow();
|
||||||
|
break;
|
||||||
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
|
ChangeWindowSizeEvent(event);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleMouseMovement(wheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::OnLoad() { PRINT_IF_ERROR(master_editor_.Update()); }
|
||||||
|
|
||||||
|
void Controller::DoRender() const {
|
||||||
|
ImGui::Render();
|
||||||
|
SDL_RenderClear(renderer_.get());
|
||||||
|
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
SDL_RenderPresent(renderer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::OnExit() {
|
||||||
|
master_editor_.Shutdown();
|
||||||
|
Mix_CloseAudio();
|
||||||
|
ImGui_ImplSDLRenderer2_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
screenWidth, // width, in pixels
|
||||||
|
screenHeight, // height, in pixels
|
||||||
|
SDL_WINDOW_RESIZABLE),
|
||||||
|
sdl_deleter());
|
||||||
|
if (window_ == nullptr) {
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
|
||||||
|
}
|
||||||
|
// Initialize SDL_mixer
|
||||||
|
if (Mix_OpenAudio(32000, MIX_DEFAULT_FORMAT, 2, 1024) < 0) {
|
||||||
|
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
|
||||||
|
Mix_GetError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Controller::CreateRenderer() {
|
||||||
|
renderer_ = std::unique_ptr<SDL_Renderer, sdl_deleter>(
|
||||||
|
SDL_CreateRenderer(window_.get(), -1,
|
||||||
|
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
|
||||||
|
sdl_deleter());
|
||||||
|
if (renderer_ == nullptr) {
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
|
||||||
|
} else {
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
||||||
|
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_ImplSDLRenderer2_Init(renderer_.get());
|
||||||
|
|
||||||
|
if (flags()->kLoadSystemFonts) {
|
||||||
|
LoadSystemFonts();
|
||||||
|
} else {
|
||||||
|
RETURN_IF_ERROR(LoadFontFamilies());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
67
src/app/core/controller.h
Normal file
67
src/app/core/controller.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_CONTROLLER_H
|
||||||
|
#define YAZE_APP_CORE_CONTROLLER_H
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <imgui/backends/imgui_impl_sdl2.h>
|
||||||
|
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
#include <imgui/imgui_internal.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/editor.h"
|
||||||
|
#include "app/editor/master_editor.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv);
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
class Controller : public ExperimentFlags {
|
||||||
|
public:
|
||||||
|
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 {
|
||||||
|
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 CreateSDL_Window();
|
||||||
|
absl::Status CreateRenderer();
|
||||||
|
absl::Status CreateGuiContext();
|
||||||
|
absl::Status LoadFontFamilies() const;
|
||||||
|
void CloseWindow() { active_ = false; }
|
||||||
|
|
||||||
|
friend int ::main(int argc, char **argv);
|
||||||
|
|
||||||
|
bool active_;
|
||||||
|
editor::MasterEditor master_editor_;
|
||||||
|
std::shared_ptr<SDL_Window> window_;
|
||||||
|
std::shared_ptr<SDL_Renderer> renderer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_CONTROLLER_H
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
||||||
855
src/app/editor/dungeon_editor.cc
Normal file
855
src/app/editor/dungeon_editor.cc
Normal file
@@ -0,0 +1,855 @@
|
|||||||
|
#include "dungeon_editor.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 {
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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::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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem("Object Renderer")) {
|
||||||
|
DrawObjectRenderer();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawObjectRenderer() {
|
||||||
|
if (ImGui::BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
ImGui::GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Canvas");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
|
||||||
|
|
||||||
|
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::EndChild();
|
||||||
|
|
||||||
|
// Right side of the table - Canvas
|
||||||
|
TableNextColumn();
|
||||||
|
ImGui::BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1),
|
||||||
|
true);
|
||||||
|
|
||||||
|
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::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
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
137
src/app/editor/dungeon_editor.h
Normal file
137
src/app/editor/dungeon_editor.h
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
|
||||||
|
#include <imgui/imgui.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 {
|
||||||
|
|
||||||
|
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:
|
||||||
|
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_;
|
||||||
|
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
|
||||||
|
|
||||||
|
#endif
|
||||||
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
|
||||||
610
src/app/editor/master_editor.cc
Normal file
610
src/app/editor/master_editor.cc
Normal file
@@ -0,0 +1,610 @@
|
|||||||
|
#include "master_editor.h"
|
||||||
|
|
||||||
|
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||||
|
#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/core/platform/file_dialog.h"
|
||||||
|
#include "app/editor/dungeon_editor.h"
|
||||||
|
#include "app/editor/graphics_editor.h"
|
||||||
|
#include "app/editor/modules/assembly_editor.h"
|
||||||
|
#include "app/editor/modules/music_editor.h"
|
||||||
|
#include "app/editor/modules/palette_editor.h"
|
||||||
|
#include "app/editor/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"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
||||||
|
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||||
|
|
||||||
|
void NewMasterFrame() {
|
||||||
|
const ImGuiIO& io = ImGui::GetIO();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
ImGui::SetNextWindowPos(gui::kZeroPos);
|
||||||
|
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||||
|
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||||
|
|
||||||
|
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BeginCentered(const char* name) {
|
||||||
|
ImGuiIO const& io = ImGui::GetIO();
|
||||||
|
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||||
|
ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||||
|
ImGuiWindowFlags flags =
|
||||||
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
|
||||||
|
return ImGui::Begin(name, nullptr, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
DrawFileDialog();
|
||||||
|
DrawStatusPopup();
|
||||||
|
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")
|
||||||
|
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() {
|
||||||
|
gui::FileDialogPipeline("ChooseFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||||
|
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||||
|
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()) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawAboutPopup() {
|
||||||
|
if (about_) ImGui::OpenPopup("About");
|
||||||
|
if (ImGui::BeginPopupModal("About", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Yet Another Zelda3 Editor - v%.2f", core::kYazeVersion);
|
||||||
|
Text("Written by: scawful");
|
||||||
|
ImGui::Spacing();
|
||||||
|
Text("Special Thanks: Zarby89, JaredBrian");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
about_ = false;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawInfoPopup() {
|
||||||
|
if (rom_info_) ImGui::OpenPopup("ROM Information");
|
||||||
|
if (ImGui::BeginPopupModal("ROM Information", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Title: %s", rom()->title());
|
||||||
|
Text("ROM Size: %ld", rom()->size());
|
||||||
|
|
||||||
|
if (ImGui::Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
rom_info_ = false;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawYazeMenu() {
|
||||||
|
static bool show_display_settings = false;
|
||||||
|
static bool show_command_line_interface = false;
|
||||||
|
|
||||||
|
MENU_BAR()
|
||||||
|
DrawFileMenu();
|
||||||
|
DrawEditMenu();
|
||||||
|
DrawViewMenu();
|
||||||
|
DrawHelpMenu();
|
||||||
|
|
||||||
|
ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::GetStyle().ItemSpacing.x -
|
||||||
|
ImGui::CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150);
|
||||||
|
// Modify the style of the button to have no background color
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||||
|
if (ImGui::Button(ICON_MD_DISPLAY_SETTINGS)) {
|
||||||
|
show_display_settings = !show_display_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
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", ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 (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_ = 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();
|
||||||
|
MENU_ITEM("ROM Information") rom_info_ = true;
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawViewMenu() {
|
||||||
|
static bool show_imgui_metrics = 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_asm_editor) {
|
||||||
|
assembly_editor_.Update(show_asm_editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_palette_editor) {
|
||||||
|
ImGui::Begin("Palette Editor", &show_palette_editor);
|
||||||
|
status_ = palette_editor_.Update();
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_memory_viewer) {
|
||||||
|
ImGui::Begin("Memory Viewer (ImGui)", &show_memory_viewer);
|
||||||
|
|
||||||
|
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
static ImGuiTableFlags flags =
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX;
|
||||||
|
if (auto outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f);
|
||||||
|
ImGui::BeginTable("table1", 3, flags, outer_size)) {
|
||||||
|
for (int row = 0; row < 10; row++) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
for (int column = 0; column < 3; column++) {
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
Text("Cell %d,%d", column, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_resource_label_manager) {
|
||||||
|
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::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_);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = rom()->SaveToFile(backup_rom_, save_new_auto_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
92
src/app/editor/master_editor.h
Normal file
92
src/app/editor/master_editor.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#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>
|
||||||
|
#include <imgui/misc/cpp/imgui_stdlib.h>
|
||||||
|
#include <imgui_memory_editor.h>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/gui/pipeline.h"
|
||||||
|
#include "app/editor/context/gfx_context.h"
|
||||||
|
#include "app/editor/dungeon_editor.h"
|
||||||
|
#include "app/editor/graphics_editor.h"
|
||||||
|
#include "app/editor/modules/assembly_editor.h"
|
||||||
|
#include "app/editor/modules/music_editor.h"
|
||||||
|
#include "app/editor/modules/palette_editor.h"
|
||||||
|
#include "app/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/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
class MasterEditor : public SharedROM,
|
||||||
|
public GfxContext,
|
||||||
|
public core::ExperimentFlags {
|
||||||
|
public:
|
||||||
|
MasterEditor() { current_editor_ = &overworld_editor_; }
|
||||||
|
|
||||||
|
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer);
|
||||||
|
absl::Status Update();
|
||||||
|
|
||||||
|
void Shutdown() { overworld_editor_.Shutdown(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawFileDialog();
|
||||||
|
void DrawStatusPopup();
|
||||||
|
void DrawAboutPopup();
|
||||||
|
void DrawInfoPopup();
|
||||||
|
|
||||||
|
void DrawYazeMenu();
|
||||||
|
void DrawFileMenu();
|
||||||
|
void DrawEditMenu();
|
||||||
|
void DrawViewMenu();
|
||||||
|
void DrawHelpMenu();
|
||||||
|
|
||||||
|
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_;
|
||||||
|
|
||||||
|
emu::Emulator emulator_;
|
||||||
|
|
||||||
|
AssemblyEditor assembly_editor_;
|
||||||
|
DungeonEditor dungeon_editor_;
|
||||||
|
GraphicsEditor graphics_editor_;
|
||||||
|
MusicEditor music_editor_;
|
||||||
|
OverworldEditor overworld_editor_;
|
||||||
|
PaletteEditor palette_editor_;
|
||||||
|
ScreenEditor screen_editor_;
|
||||||
|
SpriteEditor sprite_editor_;
|
||||||
|
|
||||||
|
Editor *current_editor_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||||
152
src/app/editor/modules/assembly_editor.cc
Normal file
152
src/app/editor/modules/assembly_editor.cc
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#include "assembly_editor.h"
|
||||||
|
|
||||||
|
#include "app/gui/widgets.h"
|
||||||
|
#include "core/constants.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
AssemblyEditor::AssemblyEditor() {
|
||||||
|
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
|
||||||
|
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::Update(bool& is_loaded) {
|
||||||
|
ImGui::Begin("Assembly Editor", &is_loaded);
|
||||||
|
MENU_BAR()
|
||||||
|
DrawFileMenu();
|
||||||
|
DrawEditMenu();
|
||||||
|
END_MENU_BAR()
|
||||||
|
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor");
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::InlineUpdate() {
|
||||||
|
ChangeActiveFile("assets/asm/template_song.asm");
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor", ImVec2(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::ChangeActiveFile(const std::string_view& filename) {
|
||||||
|
current_file_ = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawFileView() {
|
||||||
|
ImGui::BeginTable("##table_view", 4,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg);
|
||||||
|
|
||||||
|
// Table headers
|
||||||
|
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 150.0f);
|
||||||
|
ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed, 60.0f);
|
||||||
|
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100.0f);
|
||||||
|
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Table data
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
// TODO: Add tree view of files
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
// TODO: Add line number
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
// TODO: Add address per line
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawFileMenu() {
|
||||||
|
if (ImGui::BeginMenu("File")) {
|
||||||
|
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||||
|
ImGuiFileDialog::Instance()->OpenDialog(
|
||||||
|
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Save", "Ctrl+S")) {
|
||||||
|
// TODO: Implement this
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
|
||||||
|
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||||
|
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
|
||||||
|
}
|
||||||
|
ImGuiFileDialog::Instance()->Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawEditMenu() {
|
||||||
|
if (ImGui::BeginMenu("Edit")) {
|
||||||
|
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
|
||||||
|
text_editor_.Undo();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
|
||||||
|
text_editor_.Redo();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
|
||||||
|
text_editor_.Cut();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
|
||||||
|
text_editor_.Copy();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
|
||||||
|
text_editor_.Paste();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Find", "Ctrl+F")) {
|
||||||
|
// TODO: Implement this.
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::SetEditorText() {
|
||||||
|
if (!file_is_loaded_) {
|
||||||
|
std::ifstream t(current_file_);
|
||||||
|
if (t.good()) {
|
||||||
|
std::string str((std::istreambuf_iterator<char>(t)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
text_editor_.SetText(str);
|
||||||
|
file_is_loaded_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
41
src/app/editor/modules/assembly_editor.h
Normal file
41
src/app/editor/modules/assembly_editor.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
||||||
|
|
||||||
|
#include <ImGuiColorTextEdit/TextEditor.h>
|
||||||
|
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <istream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
class AssemblyEditor {
|
||||||
|
public:
|
||||||
|
AssemblyEditor();
|
||||||
|
|
||||||
|
void Update(bool &is_loaded);
|
||||||
|
void InlineUpdate();
|
||||||
|
void ChangeActiveFile(const std::string_view &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawFileMenu();
|
||||||
|
void DrawEditMenu();
|
||||||
|
|
||||||
|
void DrawFileView();
|
||||||
|
|
||||||
|
void SetEditorText();
|
||||||
|
|
||||||
|
bool file_is_loaded_ = false;
|
||||||
|
|
||||||
|
std::string current_file_;
|
||||||
|
TextEditor text_editor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
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
|
||||||
297
src/app/editor/modules/music_editor.cc
Normal file
297
src/app/editor/modules/music_editor.cc
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
#include "music_editor.h"
|
||||||
|
|
||||||
|
#include <SDL_mixer.h>
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.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 {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#define BUF_SIZE 2048
|
||||||
|
|
||||||
|
void PlaySPC() {
|
||||||
|
// /* Create emulator and filter */
|
||||||
|
// SNES_SPC* snes_spc = spc_new();
|
||||||
|
// SPC_Filter* filter = spc_filter_new();
|
||||||
|
// if (!snes_spc || !filter) error("Out of memory");
|
||||||
|
|
||||||
|
// /* Load SPC */
|
||||||
|
// {
|
||||||
|
// /* Load file into memory */
|
||||||
|
// long spc_size;
|
||||||
|
// void* spc = load_file("assets/music/hyrule_field.spc", &spc_size);
|
||||||
|
|
||||||
|
// /* Load SPC data into emulator */
|
||||||
|
// error(spc_load_spc(snes_spc, spc, spc_size));
|
||||||
|
// free(spc); /* emulator makes copy of data */
|
||||||
|
|
||||||
|
// /* Most SPC files have garbage data in the echo buffer, so clear that */
|
||||||
|
// spc_clear_echo(snes_spc);
|
||||||
|
|
||||||
|
// /* Clear filter before playing */
|
||||||
|
// spc_filter_clear(filter);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Record 20 seconds to wave file */
|
||||||
|
// wave_open(spc_sample_rate, "out.wav");
|
||||||
|
// wave_enable_stereo();
|
||||||
|
// while (wave_sample_count() < 30 * spc_sample_rate * 2) {
|
||||||
|
// /* Play into buffer */
|
||||||
|
// short buf[BUF_SIZE];
|
||||||
|
// error(spc_play(snes_spc, BUF_SIZE, buf));
|
||||||
|
|
||||||
|
// /* Filter samples */
|
||||||
|
// spc_filter_run(filter, buf, BUF_SIZE);
|
||||||
|
|
||||||
|
// wave_write(buf, BUF_SIZE);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Cleanup */
|
||||||
|
// spc_filter_delete(filter);
|
||||||
|
// spc_delete(snes_spc);
|
||||||
|
// wave_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void MusicEditor::Update() {
|
||||||
|
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("Assembly");
|
||||||
|
ImGui::TableSetupColumn("Composition");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
assembly_editor_.InlineUpdate();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawToolset();
|
||||||
|
DrawChannels();
|
||||||
|
DrawPianoRoll();
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawChannels() {
|
||||||
|
if (ImGui::BeginTabBar("MyTabBar", ImGuiTabBarFlags_None)) {
|
||||||
|
for (int i = 1; i <= 8; ++i) {
|
||||||
|
if (ImGui::BeginTabItem(absl::StrFormat("%d", i).data())) {
|
||||||
|
DrawPianoStaff();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int NUM_KEYS = 25;
|
||||||
|
static bool keys[NUM_KEYS];
|
||||||
|
|
||||||
|
void MusicEditor::DrawPianoStaff() {
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||||
|
ImGui::BeginChild(child_id, ImVec2(0, 170), false)) {
|
||||||
|
const int NUM_LINES = 5;
|
||||||
|
const int LINE_THICKNESS = 2;
|
||||||
|
const int LINE_SPACING = 40;
|
||||||
|
|
||||||
|
// Get the draw list for the current window
|
||||||
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
// Draw the staff lines
|
||||||
|
ImVec2 canvas_p0 =
|
||||||
|
ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
|
||||||
|
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + ImGui::GetContentRegionAvail().y);
|
||||||
|
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(32, 32, 32, 255));
|
||||||
|
for (int i = 0; i < NUM_LINES; i++) {
|
||||||
|
auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING);
|
||||||
|
auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + i * LINE_SPACING);
|
||||||
|
draw_list->AddLine(line_start, line_end, IM_COL32(200, 200, 200, 255),
|
||||||
|
LINE_THICKNESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the ledger lines
|
||||||
|
const int NUM_LEDGER_LINES = 3;
|
||||||
|
for (int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) {
|
||||||
|
if (i % 2 == 0) continue; // skip every other line
|
||||||
|
auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2);
|
||||||
|
auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + i * LINE_SPACING / 2);
|
||||||
|
draw_list->AddLine(line_start, line_end, IM_COL32(150, 150, 150, 255),
|
||||||
|
LINE_THICKNESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawPianoRoll() {
|
||||||
|
// Render the piano roll
|
||||||
|
float key_width = ImGui::GetContentRegionAvail().x / NUM_KEYS;
|
||||||
|
float white_key_height = ImGui::GetContentRegionAvail().y * 0.8f;
|
||||||
|
float black_key_height = ImGui::GetContentRegionAvail().y * 0.5f;
|
||||||
|
ImGui::Text("Piano Roll");
|
||||||
|
ImGui::Separator();
|
||||||
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
// Draw the staff lines
|
||||||
|
ImVec2 canvas_p0 =
|
||||||
|
ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
|
||||||
|
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + ImGui::GetContentRegionAvail().y);
|
||||||
|
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(200, 200, 200, 255));
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.f, 0.f));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.f);
|
||||||
|
for (int i = 0; i < NUM_KEYS; i++) {
|
||||||
|
// Calculate the position and size of the key
|
||||||
|
ImVec2 key_pos = ImVec2(i * key_width, 0.0f);
|
||||||
|
ImVec2 key_size;
|
||||||
|
ImVec4 key_color;
|
||||||
|
ImVec4 text_color;
|
||||||
|
if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 ||
|
||||||
|
i % 12 == 10) {
|
||||||
|
// This is a black key
|
||||||
|
key_size = ImVec2(key_width * 0.6f, black_key_height);
|
||||||
|
key_color = ImVec4(0, 0, 0, 255);
|
||||||
|
text_color = ImVec4(255, 255, 255, 255);
|
||||||
|
} else {
|
||||||
|
// This is a white key
|
||||||
|
key_size = ImVec2(key_width, white_key_height);
|
||||||
|
key_color = ImVec4(255, 255, 255, 255);
|
||||||
|
text_color = ImVec4(0, 0, 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushID(i);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, key_color);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
|
||||||
|
if (ImGui::Button(kSongNotes[i].data(), key_size)) {
|
||||||
|
keys[i] ^= 1;
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
ImVec2 button_pos = ImGui::GetItemRectMin();
|
||||||
|
ImVec2 button_size = ImGui::GetItemRectSize();
|
||||||
|
if (keys[i]) {
|
||||||
|
ImVec2 dest;
|
||||||
|
dest.x = button_pos.x + button_size.x;
|
||||||
|
dest.y = button_pos.y + button_size.y;
|
||||||
|
ImGui::GetWindowDrawList()->AddRectFilled(button_pos, dest,
|
||||||
|
IM_COL32(200, 200, 255, 200));
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawSongToolset() {
|
||||||
|
if (ImGui::BeginTable("DWToolset", 8, toolset_table_flags_, ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("#1");
|
||||||
|
ImGui::TableSetupColumn("#play");
|
||||||
|
ImGui::TableSetupColumn("#rewind");
|
||||||
|
ImGui::TableSetupColumn("#fastforward");
|
||||||
|
ImGui::TableSetupColumn("volumeController");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawToolset() {
|
||||||
|
static bool is_playing = false;
|
||||||
|
static int selected_option = 0;
|
||||||
|
static int current_volume = 0;
|
||||||
|
static bool has_loaded_song = false;
|
||||||
|
const int MAX_VOLUME = 100;
|
||||||
|
|
||||||
|
if (is_playing) {
|
||||||
|
if (!has_loaded_song) {
|
||||||
|
// PlaySPC();
|
||||||
|
// current_song_ = Mix_LoadMUS("out.wav");
|
||||||
|
// Mix_PlayMusic(current_song_, -1);
|
||||||
|
// has_loaded_song = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // If there is no music playing
|
||||||
|
// if (Mix_PlayingMusic() == 0) {
|
||||||
|
// Mix_PlayMusic(current_song_, -1);
|
||||||
|
// }
|
||||||
|
// // If music is being played
|
||||||
|
// else {
|
||||||
|
// // If the music is paused
|
||||||
|
// if (Mix_PausedMusic() == 1) {
|
||||||
|
// // Resume the music
|
||||||
|
// Mix_ResumeMusic();
|
||||||
|
// }
|
||||||
|
// // If the music is playing
|
||||||
|
// else {
|
||||||
|
// // Pause the music
|
||||||
|
// Mix_PauseMusic();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
|
||||||
|
ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
|
||||||
|
|
||||||
|
gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
|
||||||
|
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();
|
||||||
|
if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
|
||||||
|
if (is_playing) {
|
||||||
|
Mix_HaltMusic();
|
||||||
|
has_loaded_song = false;
|
||||||
|
}
|
||||||
|
is_playing = !is_playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int SONG_DURATION = 120; // duration of the song in seconds
|
||||||
|
static int current_time = 0; // current time in the song in seconds
|
||||||
|
|
||||||
|
// Display the current time in the song
|
||||||
|
gui::ItemLabel("Current Time: ", gui::ItemLabelFlags::Left);
|
||||||
|
ImGui::Text("%d:%02d", current_time / 60, current_time % 60);
|
||||||
|
ImGui::SameLine();
|
||||||
|
// Display the song duration/progress using a progress bar
|
||||||
|
ImGui::ProgressBar((float)current_time / SONG_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
90
src/app/editor/modules/music_editor.h
Normal file
90
src/app/editor/modules/music_editor.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||||
|
|
||||||
|
#include <SDL_mixer.h>
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.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 {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
static const char* kGameSongs[] = {"Title",
|
||||||
|
"Light World",
|
||||||
|
"Beginning",
|
||||||
|
"Rabbit",
|
||||||
|
"Forest",
|
||||||
|
"Intro",
|
||||||
|
"Town",
|
||||||
|
"Warp",
|
||||||
|
"Dark world",
|
||||||
|
"Master sword",
|
||||||
|
"File select",
|
||||||
|
"Soldier",
|
||||||
|
"Mountain",
|
||||||
|
"Shop",
|
||||||
|
"Fanfare",
|
||||||
|
"Castle",
|
||||||
|
"Palace (Pendant)",
|
||||||
|
"Cave (Same as Secret Way)",
|
||||||
|
"Clear (Dungeon end)",
|
||||||
|
"Church",
|
||||||
|
"Boss",
|
||||||
|
"Dungeon (Crystal)",
|
||||||
|
"Psychic",
|
||||||
|
"Secret Way (Same as Cave)",
|
||||||
|
"Rescue",
|
||||||
|
"Crystal",
|
||||||
|
"Fountain",
|
||||||
|
"Pyramid",
|
||||||
|
"Kill Agahnim",
|
||||||
|
"Ganon Room",
|
||||||
|
"Last Boss"};
|
||||||
|
|
||||||
|
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 : public SharedROM {
|
||||||
|
public:
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawChannels();
|
||||||
|
void DrawPianoStaff();
|
||||||
|
void DrawPianoRoll();
|
||||||
|
void DrawSongToolset();
|
||||||
|
void DrawToolset();
|
||||||
|
|
||||||
|
zelda3::Tracker music_tracker_;
|
||||||
|
|
||||||
|
// Mix_Music* current_song_ = NULL;
|
||||||
|
|
||||||
|
AssemblyEditor assembly_editor_;
|
||||||
|
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
||||||
|
ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit |
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_Reorderable;
|
||||||
|
|
||||||
|
ImGuiTableFlags channel_table_flags_ =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable |
|
||||||
|
ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV |
|
||||||
|
ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
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
|
||||||
1944
src/app/editor/overworld_editor.cc
Normal file
1944
src/app/editor/overworld_editor.cc
Normal file
File diff suppressed because it is too large
Load Diff
254
src/app/editor/overworld_editor.h
Normal file
254
src/app/editor/overworld_editor.h
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||||
|
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
#include <imgui/imgui_internal.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "absl/container/flat_hash_map.h"
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_format.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"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
static constexpr uint k4BPP = 4;
|
||||||
|
static constexpr uint kByteSize = 3;
|
||||||
|
static constexpr uint kMessageIdSize = 5;
|
||||||
|
static constexpr uint kNumSheetsToLoad = 223;
|
||||||
|
static constexpr uint kTile8DisplayHeight = 64;
|
||||||
|
static constexpr float kInputFieldSize = 30.f;
|
||||||
|
|
||||||
|
static constexpr absl::string_view kToolsetColumnNames[] = {
|
||||||
|
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
|
||||||
|
"#zoomInTool", "#separator", "#drawTool", "#history",
|
||||||
|
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
||||||
|
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
|
||||||
|
"propertiesTool"};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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() 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 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 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] = "";
|
||||||
|
char spr_palette_[3] = "";
|
||||||
|
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;
|
||||||
|
|
||||||
|
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::vector<Bytes> tile16_individual_data_;
|
||||||
|
std::vector<gfx::Bitmap> tile16_individual_;
|
||||||
|
|
||||||
|
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_;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
435
src/app/editor/screen_editor.cc
Normal file
435
src/app/editor/screen_editor.cc
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
#include "app/editor/screen_editor.h"
|
||||||
|
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
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();
|
||||||
|
END_TAB_BAR()
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawInventoryMenuEditor() {
|
||||||
|
TAB_ITEM("Inventory Menu")
|
||||||
|
|
||||||
|
static bool create = false;
|
||||||
|
if (!create && rom()->is_loaded()) {
|
||||||
|
inventory_.Create();
|
||||||
|
palette_ = inventory_.Palette();
|
||||||
|
create = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawInventoryToolset();
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
|
||||||
|
ImGui::TableSetupColumn("Canvas");
|
||||||
|
ImGui::TableSetupColumn("Tiles");
|
||||||
|
ImGui::TableSetupColumn("Palette");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
screen_canvas_.DrawBackground();
|
||||||
|
screen_canvas_.DrawContextMenu();
|
||||||
|
screen_canvas_.DrawBitmap(inventory_.Bitmap(), 2, create);
|
||||||
|
screen_canvas_.DrawGrid(32.0f);
|
||||||
|
screen_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
|
||||||
|
tilesheet_canvas_.DrawContextMenu();
|
||||||
|
tilesheet_canvas_.DrawBitmap(inventory_.Tilesheet(), 2, create);
|
||||||
|
tilesheet_canvas_.DrawGrid(16.0f);
|
||||||
|
tilesheet_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
gui::DisplayPalette(palette_, create);
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawInventoryToolset() {
|
||||||
|
if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("#drawTool");
|
||||||
|
ImGui::TableSetupColumn("#sep1");
|
||||||
|
ImGui::TableSetupColumn("#zoomOut");
|
||||||
|
ImGui::TableSetupColumn("#zoomIN");
|
||||||
|
ImGui::TableSetupColumn("#sep2");
|
||||||
|
ImGui::TableSetupColumn("#bg2Tool");
|
||||||
|
ImGui::TableSetupColumn("#bg3Tool");
|
||||||
|
ImGui::TableSetupColumn("#itemTool");
|
||||||
|
|
||||||
|
BUTTON_COLUMN(ICON_MD_UNDO)
|
||||||
|
BUTTON_COLUMN(ICON_MD_REDO)
|
||||||
|
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_ZOOM_OUT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_ZOOM_IN)
|
||||||
|
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_DRAW)
|
||||||
|
BUTTON_COLUMN(ICON_MD_BUILD)
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
78
src/app/editor/screen_editor.h
Normal file
78
src/app/editor/screen_editor.h
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
|
#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/screen/dungeon_map.h"
|
||||||
|
#include "app/zelda3/screen/inventory.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
class ScreenEditor : public SharedROM {
|
||||||
|
public:
|
||||||
|
ScreenEditor();
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
absl::Status SaveDungeonMaps();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawTitleScreenEditor();
|
||||||
|
void DrawNamingScreenEditor();
|
||||||
|
void DrawOverworldMapEditor();
|
||||||
|
|
||||||
|
void DrawInventoryMenuEditor();
|
||||||
|
void DrawToolset();
|
||||||
|
void DrawInventoryToolset();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
Bytes all_gfx_;
|
||||||
|
zelda3::Inventory inventory_;
|
||||||
|
gfx::SnesPalette palette_;
|
||||||
|
gui::Canvas screen_canvas_;
|
||||||
|
gui::Canvas tilesheet_canvas_;
|
||||||
|
gui::Canvas tilemap_canvas_;
|
||||||
|
|
||||||
|
gfx::BitmapTable sheets_;
|
||||||
|
|
||||||
|
gfx::Tilesheet tile16_sheet_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
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