2 Commits

Author SHA1 Message Date
scawful
e32ac75b9c backend-infra-engineer: Release v0.3.0 snapshot 2025-09-27 00:25:45 -04:00
scawful
8ce29e1436 backend-infra-engineer: Release 0.2.2 snapshot 2024-12-31 21:00:27 -05:00
372 changed files with 62567 additions and 14572 deletions

83
.clang-format Normal file
View File

@@ -0,0 +1,83 @@
# Google C/C++ Code Style settings
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# Author: Kehan Xue, kehan.xue (at) gmail.com
Language: Cpp
BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never # To avoid conflict, set this "Never" and each "if statement" should include brace when coding
AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterStruct: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 80
CompactNamespaces: false
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false # Make sure the * or & align on the left
EmptyLineBeforeAccessModifier: LogicalBlock
FixNamespaceComments: true
IncludeBlocks: Preserve
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Left
ReflowComments: false
# SeparateDefinitionBlocks: Always # Only support since clang-format 14
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++11
TabWidth: 4
UseTab: Never

53
.clang-tidy Normal file
View File

@@ -0,0 +1,53 @@
# YAZE clang-tidy configuration
# More lenient configuration for easier compliance
Checks: >
-*-,
clang-analyzer-*,
-clang-analyzer-alpha*,
performance-*,
-performance-unnecessary-value-param,
readability-*,
-readability-magic-numbers,
-readability-braces-around-statements,
-readability-named-parameter,
-readability-function-cognitive-complexity,
-readability-avoid-const-params-in-decls,
modernize-*,
-modernize-use-trailing-return-type,
-modernize-use-auto,
-modernize-avoid-c-arrays,
-modernize-use-default-member-init,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape,
-bugprone-narrowing-conversions,
-bugprone-implicit-widening-of-multiplication-result,
misc-*,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-const-correctness
CheckOptions:
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.MacroCase
value: UPPER_CASE
- key: readability-function-size.LineThreshold
value: 150
- key: readability-function-size.StatementThreshold
value: 100
- key: performance-unnecessary-value-param.AllowedTypes
value: 'std::function;std::unique_ptr;std::shared_ptr'
WarningsAsErrors: ''
HeaderFilterRegex: '(src|test)\/.*\.(h|hpp|hxx)$'
FormatStyle: google

30
.clangd Normal file
View File

@@ -0,0 +1,30 @@
CompileFlags:
Add:
- -std=c++23
- -Wall
- -Wextra
Remove:
- -mllvm
- -xclang
Index:
Background: Build
StandardLibrary: Yes
InlayHints:
Enabled: Yes
ParameterNames: Yes
DeducedTypes: Yes
Hover:
ShowAKA: Yes
Diagnostics:
ClangTidy:
Add:
- readability-*
- modernize-*
- performance-*
Remove:
- modernize-use-trailing-return-type
- readability-braces-around-statements

428
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,428 @@
name: CI/CD Pipeline
on:
push:
branches: [ "master", "develop" ]
paths:
- 'src/**'
- 'test/**'
- 'cmake/**'
- 'CMakeLists.txt'
- '.github/workflows/**'
pull_request:
branches: [ "master", "develop" ]
paths:
- 'src/**'
- 'test/**'
- 'cmake/**'
- 'CMakeLists.txt'
- '.github/workflows/**'
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: RelWithDebInfo
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
jobs:
build-and-test:
strategy:
fail-fast: false
matrix:
include:
- name: "Ubuntu 22.04 (GCC-12)"
os: ubuntu-22.04
cc: gcc-12
cxx: g++-12
vcpkg_triplet: x64-linux
- name: "Ubuntu 22.04 (Clang)"
os: ubuntu-22.04
cc: clang-15
cxx: clang++-15
vcpkg_triplet: x64-linux
- name: "macOS 13 (Clang)"
os: macos-13
cc: clang
cxx: clang++
vcpkg_triplet: x64-osx
- name: "macOS 14 (Clang)"
os: macos-14
cc: clang
cxx: clang++
vcpkg_triplet: arm64-osx
- name: "Windows 2022 (MSVC x64)"
os: windows-2022
cc: cl
cxx: cl
vcpkg_triplet: x64-windows
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
- name: "Windows 2022 (MSVC x86)"
os: windows-2022
cc: cl
cxx: cl
vcpkg_triplet: x86-windows
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: Win32
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Set up vcpkg cache
if: runner.os == 'Windows'
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/vcpkg
${{ github.workspace }}/vcpkg_installed
key: vcpkg-${{ matrix.vcpkg_triplet }}-${{ hashFiles('vcpkg.json') }}
restore-keys: |
vcpkg-${{ matrix.vcpkg_triplet }}-
# Linux-specific setup
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
ninja-build \
pkg-config \
libglew-dev \
libxext-dev \
libwavpack-dev \
libabsl-dev \
libboost-all-dev \
libboost-python-dev \
libpng-dev \
python3-dev \
libpython3-dev \
libasound2-dev \
libpulse-dev \
libaudio-dev \
libx11-dev \
libxrandr-dev \
libxcursor-dev \
libxinerama-dev \
libxi-dev \
libxss-dev \
libxxf86vm-dev \
libxkbcommon-dev \
libwayland-dev \
libdecor-0-dev \
libgtk-3-dev \
libdbus-1-dev \
gcc-12 \
g++-12 \
clang-15
- name: Set up Linux compilers
if: runner.os == 'Linux'
run: |
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/${{ matrix.cc }} 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/${{ matrix.cxx }} 100
# macOS-specific setup
- name: Install macOS dependencies
if: runner.os == 'macOS'
run: |
# Install Homebrew dependencies if needed
# brew install pkg-config libpng boost abseil
# Windows-specific setup (skip vcpkg for CI builds)
- name: Set up vcpkg (non-CI builds only)
if: runner.os == 'Windows' && github.event_name != 'push' && github.event_name != 'pull_request'
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: 'c8696863d371ab7f46e213d8f5ca923c4aef2a00'
runVcpkgInstall: true
vcpkgJsonGlob: '**/vcpkg.json'
vcpkgDirectory: '${{ github.workspace }}/vcpkg'
- name: Configure CMake (Linux/macOS)
if: runner.os != 'Windows'
run: |
cmake -B ${{ github.workspace }}/build \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_C_COMPILER=${{ matrix.cc }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
-DCMAKE_POLICY_VERSION_MINIMUM=3.16 \
-DYAZE_MINIMAL_BUILD=ON \
-DYAZE_ENABLE_ROM_TESTS=OFF \
-DYAZE_ENABLE_EXPERIMENTAL_TESTS=OFF \
-DYAZE_ENABLE_UI_TESTS=OFF \
-Wno-dev \
-GNinja
- name: Configure CMake (Windows)
if: runner.os == 'Windows'
shell: cmd
run: |
cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_POLICY_VERSION_MINIMUM=3.16 -DYAZE_MINIMAL_BUILD=ON -DYAZE_ENABLE_ROM_TESTS=OFF -DYAZE_ENABLE_EXPERIMENTAL_TESTS=OFF -DYAZE_ENABLE_UI_TESTS=OFF -Wno-dev -G "${{ matrix.cmake_generator }}" -A ${{ matrix.cmake_generator_platform }}
# Build
- name: Build
run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} --parallel
# Test (stable core functionality only for CI)
- name: Run Core Tests
working-directory: ${{ github.workspace }}/build
run: ctest --build-config ${{ env.BUILD_TYPE }} --output-on-failure -j1 -R "AsarWrapperTest|SnesTileTest|CompressionTest|SnesPaletteTest|HexTest"
# Run experimental tests separately (allowed to fail for information only)
- name: Run Additional Tests (Informational)
working-directory: ${{ github.workspace }}/build
continue-on-error: true
run: ctest --build-config ${{ env.BUILD_TYPE }} --output-on-failure --parallel -E "AsarWrapperTest|SnesTileTest|CompressionTest|SnesPaletteTest|HexTest|CpuTest|Spc700Test|ApuTest|MessageTest|.*IntegrationTest"
# Package (only on successful builds)
- name: Package artifacts
if: success()
run: |
cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} --target package
# Upload artifacts
- name: Upload build artifacts
if: success()
uses: actions/upload-artifact@v4
with:
name: yaze-${{ matrix.name }}-${{ github.sha }}
path: |
${{ github.workspace }}/build/bin/
${{ github.workspace }}/build/lib/
retention-days: 7
# Upload packages for release candidates
- name: Upload package artifacts
if: success() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))
uses: actions/upload-artifact@v4
with:
name: yaze-package-${{ matrix.name }}-${{ github.sha }}
path: |
${{ github.workspace }}/build/*.tar.gz
${{ github.workspace }}/build/*.zip
${{ github.workspace }}/build/*.dmg
${{ github.workspace }}/build/*.msi
retention-days: 30
code-quality:
name: Code Quality Checks
runs-on: ubuntu-22.04
# Relaxed requirements for releases and master branch:
# - Formatting errors become warnings
# - Fewer cppcheck categories enabled
# - Reduced clang-tidy file count
# - Job failure won't block releases
continue-on-error: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
clang-format-14 \
clang-tidy-14 \
cppcheck
- name: Check code formatting
run: |
# Relaxed formatting check for releases and master branch
if [[ "${{ github.ref }}" == "refs/heads/master" ]] || [[ "${{ github.ref }}" == refs/tags/* ]] || [[ "${{ github.event_name }}" == "pull_request" && "${{ github.base_ref }}" == "master" ]]; then
echo "🔄 Running relaxed formatting check for release/master branch..."
find src test -name "*.cc" -o -name "*.h" | \
xargs clang-format-14 --dry-run --Werror || {
echo "⚠️ Code formatting issues found, but allowing for release builds"
echo "📝 Consider running 'make format' to fix formatting before next release"
exit 0
}
else
echo "🔍 Running strict formatting check for development..."
find src test -name "*.cc" -o -name "*.h" | \
xargs clang-format-14 --dry-run --Werror
fi
- name: Run cppcheck
run: |
if [[ "${{ github.ref }}" == "refs/heads/master" ]] || [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "🔄 Running relaxed cppcheck for release/master branch..."
cppcheck --enable=warning \
--error-exitcode=0 \
--suppress=missingIncludeSystem \
--suppress=unusedFunction \
--suppress=unmatchedSuppression \
--suppress=variableScope \
--suppress=cstyleCast \
--suppress=unreadVariable \
--suppress=unusedStructMember \
--suppress=constParameter \
--suppress=constVariable \
--suppress=useStlAlgorithm \
--suppress=noExplicitConstructor \
--suppress=passedByValue \
--suppress=functionStatic \
src/ || echo "Cppcheck completed (non-blocking for releases)"
else
echo "🔍 Running standard cppcheck for development..."
cppcheck --enable=warning,style,performance \
--error-exitcode=0 \
--suppress=missingIncludeSystem \
--suppress=unusedFunction \
--suppress=unmatchedSuppression \
--suppress=variableScope \
--suppress=cstyleCast \
--suppress=unreadVariable \
--suppress=unusedStructMember \
--suppress=constParameter \
--suppress=constVariable \
--suppress=useStlAlgorithm \
--inconclusive \
src/ || echo "Cppcheck completed with warnings (non-blocking)"
fi
- name: Run clang-tidy (lenient)
run: |
if [[ "${{ github.ref }}" == "refs/heads/master" ]] || [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "🔄 Running minimal clang-tidy for release/master branch..."
# Only check a small subset of critical files for releases
find src -name "*.cc" -not -path "*/lib/*" -not -path "*/gui/*" | head -10 | \
xargs clang-tidy-14 --config-file=.clang-tidy \
--header-filter='src/.*\.(h|hpp)$' || echo "Clang-tidy completed (non-blocking for releases)"
else
echo "🔍 Running standard clang-tidy for development..."
# Run clang-tidy on a subset of files to avoid overwhelming output
find src -name "*.cc" -not -path "*/lib/*" | head -20 | \
xargs clang-tidy-14 --config-file=.clang-tidy \
--header-filter='src/.*\.(h|hpp)$' || echo "Clang-tidy completed with warnings (non-blocking)"
fi
memory-sanitizer:
name: Memory Sanitizer (Linux)
runs-on: ubuntu-22.04
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
ninja-build \
clang-14 \
libc++-14-dev \
libc++abi-14-dev \
libglew-dev \
libxext-dev \
libwavpack-dev \
libpng-dev \
libgtk-3-dev \
libdbus-1-dev
- name: Configure with AddressSanitizer
run: |
cmake -B ${{ github.workspace }}/build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang-14 \
-DCMAKE_CXX_COMPILER=clang++-14 \
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
-DYAZE_MINIMAL_BUILD=ON \
-DYAZE_ENABLE_ROM_TESTS=OFF \
-DYAZE_ENABLE_EXPERIMENTAL_TESTS=OFF \
-GNinja
- name: Build
run: cmake --build ${{ github.workspace }}/build --parallel
- name: Test with AddressSanitizer
working-directory: ${{ github.workspace }}/build
env:
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1
run: ctest --output-on-failure
coverage:
name: Code Coverage
runs-on: ubuntu-22.04
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
ninja-build \
gcov \
lcov \
libglew-dev \
libxext-dev \
libwavpack-dev \
libpng-dev \
libgtk-3-dev \
libdbus-1-dev
- name: Configure with coverage
run: |
cmake -B ${{ github.workspace }}/build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="--coverage" \
-DCMAKE_C_FLAGS="--coverage" \
-DCMAKE_EXE_LINKER_FLAGS="--coverage" \
-DYAZE_MINIMAL_BUILD=ON \
-DYAZE_ENABLE_ROM_TESTS=OFF \
-DYAZE_ENABLE_EXPERIMENTAL_TESTS=OFF \
-GNinja
- name: Build
run: cmake --build ${{ github.workspace }}/build --parallel
- name: Test
working-directory: ${{ github.workspace }}/build
run: ctest --output-on-failure
- name: Generate coverage report
run: |
lcov --capture --directory ${{ github.workspace }}/build --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage.info
lcov --remove coverage.info '**/test/**' --output-file coverage.info
lcov --remove coverage.info '**/lib/**' --output-file coverage.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false

View File

@@ -1,59 +0,0 @@
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: Install Abseil-cpp
run: sudo apt install libabsl-dev
- name: Install Boost and Boost Python
run: sudo apt install libboost-all-dev libboost-python-dev
- name: Install CPython headers
run: sudo apt install python3-dev libpython3-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

View File

@@ -1,45 +1,81 @@
name: Doxygen Action name: Doxygen Documentation
# Controls when the action will run. Triggers the workflow on push or pull request # Only run when documentation-related files are modified
# events but only for the master branch
on: on:
push: push:
branches: [ master ] branches: [ master ]
paths:
- 'src/**/*.h'
- 'src/**/*.cc'
- 'src/**/*.cpp'
- 'docs/**'
- 'Doxyfile'
- '.github/workflows/doxy.yml'
pull_request:
branches: [ master ]
paths:
- 'src/**/*.h'
- 'src/**/*.cc'
- 'src/**/*.cpp'
- 'docs/**'
- 'Doxyfile'
- '.github/workflows/doxy.yml'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel # A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs: jobs:
# This workflow contains a single job called "build" generate-docs:
build: name: Generate Documentation
# The type of runner that the job will run on
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps: steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout repository
- uses: actions/checkout@v2 uses: actions/checkout@v4
with:
fetch-depth: 0
# Delete the html directory if it exists - name: Install dependencies
- name: Delete html directory run: |
sudo apt-get update
sudo apt-get install -y doxygen graphviz
- name: Check if documentation build is needed
id: changes
run: |
# Check if this is the first commit or if docs-related files changed
if git show --name-only HEAD | grep -E '\.(h|cc|cpp|md)$|Doxyfile'; then
echo "docs_changed=true" >> $GITHUB_OUTPUT
echo "📝 Documentation-related files have changed"
else
echo "docs_changed=false" >> $GITHUB_OUTPUT
echo " No documentation changes detected"
fi
- name: Clean previous build
if: steps.changes.outputs.docs_changed == 'true'
run: rm -rf html run: rm -rf html
# Installs graphviz for DOT graphs - name: Generate Doxygen documentation
- name: Install graphviz if: steps.changes.outputs.docs_changed == 'true'
run: sudo apt-get install graphviz uses: mattnotmitt/doxygen-action@v1.9.8
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@v1.1.0
with: with:
# Path to Doxyfile doxyfile-path: "./Doxyfile"
doxyfile-path: "./Doxyfile" # default is ./Doxyfile working-directory: "."
# Working directory
working-directory: "." # default is .
- name: Deploy - name: Deploy to GitHub Pages
if: steps.changes.outputs.docs_changed == 'true'
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
# Default Doxyfile build documentation to html directory.
# Change the directory if changes in Doxyfile
publish_dir: ./html publish_dir: ./html
commit_message: 'docs: update API documentation'
- name: Summary
run: |
if [[ "${{ steps.changes.outputs.docs_changed }}" == "true" ]]; then
echo "✅ Documentation generated and deployed successfully"
echo "📖 View at: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}"
else
echo "⏭️ Documentation build skipped - no relevant changes detected"
fi

310
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,310 @@
name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-*'
workflow_dispatch:
inputs:
tag:
description: 'Release tag (must start with v and follow semantic versioning)'
required: true
default: 'v0.3.0'
type: string
env:
BUILD_TYPE: Release
jobs:
validate-and-prepare:
name: Validate Release
runs-on: ubuntu-latest
outputs:
tag_name: ${{ env.VALIDATED_TAG }}
release_notes: ${{ steps.notes.outputs.content }}
steps:
- name: Validate tag format
run: |
# Debug information
echo "Event name: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "Ref name: ${{ github.ref_name }}"
echo "Ref type: ${{ github.ref_type }}"
# Determine the tag based on trigger type
if [[ "${{ github.event_name }}" == "push" ]]; then
if [[ "${{ github.ref_type }}" != "tag" ]]; then
echo "❌ Error: Release workflow triggered by push to ${{ github.ref_type }} '${{ github.ref_name }}'"
echo "This workflow should only be triggered by pushing version tags (v1.2.3)"
echo "Use: git tag v0.3.0 && git push origin v0.3.0"
exit 1
fi
TAG="${{ github.ref_name }}"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="${{ github.event.inputs.tag }}"
if [[ -z "$TAG" ]]; then
echo "❌ Error: No tag specified for manual workflow dispatch"
exit 1
fi
else
echo "❌ Error: Unsupported event type: ${{ github.event_name }}"
exit 1
fi
echo "Validating tag: $TAG"
# Check if tag follows semantic versioning pattern
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then
echo "❌ Error: Tag '$TAG' does not follow semantic versioning format (v1.2.3 or v1.2.3-beta)"
echo "Valid examples: v0.3.0, v1.0.0, v2.1.3-beta, v1.0.0-rc1"
echo ""
echo "To create a proper release:"
echo "1. Use the helper script: ./scripts/create_release.sh 0.3.0"
echo "2. Or manually: git tag v0.3.0 && git push origin v0.3.0"
exit 1
fi
echo "✅ Tag format is valid: $TAG"
echo "VALIDATED_TAG=$TAG" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate release notes
id: release_notes
run: |
# Extract release version from validated tag
VERSION="${VALIDATED_TAG}"
VERSION_NUM=$(echo "$VERSION" | sed 's/^v//')
# Generate release notes using the dedicated script
echo "Extracting changelog for version: $VERSION_NUM"
if python3 scripts/extract_changelog.py "$VERSION_NUM" > release_notes.md; then
echo "Changelog extracted successfully"
echo "Release notes content:"
cat release_notes.md
else
echo "Failed to extract changelog, creating default release notes"
echo "# Yaze $VERSION Release Notes\n\nPlease see the full changelog at docs/C1-changelog.md" > release_notes.md
fi
- name: Store release notes
id: notes
run: |
# Store release notes content for later use
echo "content<<EOF" >> $GITHUB_OUTPUT
cat release_notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
build-release:
name: Build Release
needs: validate-and-prepare
strategy:
matrix:
include:
- name: "Windows x64"
os: windows-2022
vcpkg_triplet: x64-windows
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
artifact_name: "yaze-windows-x64"
artifact_path: "build/bin/Release/"
package_cmd: |
mkdir package
cp -r build/bin/Release/* package/
cp -r assets package/
cp LICENSE package/
cp README.md package/
cd package && 7z a ../yaze-windows-x64.zip *
- name: "Windows x86"
os: windows-2022
vcpkg_triplet: x86-windows
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: Win32
artifact_name: "yaze-windows-x86"
artifact_path: "build/bin/Release/"
package_cmd: |
mkdir package
cp -r build/bin/Release/* package/
cp -r assets package/
cp LICENSE package/
cp README.md package/
cd package && 7z a ../yaze-windows-x86.zip *
- name: "macOS Universal"
os: macos-14
vcpkg_triplet: arm64-osx
artifact_name: "yaze-macos"
artifact_path: "build/bin/"
package_cmd: |
# Debug: List what was actually built
echo "Contents of build/bin/:"
ls -la build/bin/ || echo "build/bin/ does not exist"
# Check if we have a bundle or standalone executable
if [ -d "build/bin/yaze.app" ]; then
echo "Found macOS bundle, using it directly"
# Use the existing bundle and just update it
cp -r build/bin/yaze.app ./Yaze.app
# Add additional resources to the bundle
cp -r assets "Yaze.app/Contents/Resources/"
# Update Info.plist if needed
if [ -f "cmake/yaze.plist.in" ]; then
cp cmake/yaze.plist.in "Yaze.app/Contents/Info.plist"
fi
else
echo "No bundle found, creating manual bundle"
# Create bundle structure manually
mkdir -p "Yaze.app/Contents/MacOS"
mkdir -p "Yaze.app/Contents/Resources"
cp build/bin/yaze "Yaze.app/Contents/MacOS/"
cp -r assets "Yaze.app/Contents/Resources/"
cp cmake/yaze.plist.in "Yaze.app/Contents/Info.plist"
fi
# Create DMG
mkdir dmg_staging
cp -r Yaze.app dmg_staging/
cp LICENSE dmg_staging/
cp README.md dmg_staging/
cp -r docs dmg_staging/
hdiutil create -srcfolder dmg_staging -format UDZO -volname "Yaze ${{ needs.validate-and-prepare.outputs.tag_name }}" yaze-macos.dmg
- name: "Linux x64"
os: ubuntu-22.04
artifact_name: "yaze-linux-x64"
artifact_path: "build/bin/"
package_cmd: |
mkdir package
cp build/bin/yaze package/
cp -r assets package/
cp -r docs package/
cp LICENSE package/
cp README.md package/
tar -czf yaze-linux-x64.tar.gz -C package .
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
# Platform-specific dependency installation
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
ninja-build \
pkg-config \
libglew-dev \
libxext-dev \
libwavpack-dev \
libabsl-dev \
libboost-all-dev \
libpng-dev \
python3-dev \
libpython3-dev \
libasound2-dev \
libpulse-dev \
libx11-dev \
libxrandr-dev \
libxcursor-dev \
libxinerama-dev \
libxi-dev
- name: Install macOS dependencies
if: runner.os == 'macOS'
run: |
# Install Homebrew dependencies needed for UI tests and full builds
brew install pkg-config libpng boost abseil ninja
- name: Setup build environment
run: |
echo "Using streamlined release build configuration for all platforms"
echo "Linux/macOS: UI tests enabled with full dependency support"
echo "Windows: Minimal build to avoid vcpkg issues, UI tests disabled"
echo "All platforms: Emulator and developer tools disabled for clean releases"
# Configure CMake
- name: Configure CMake (Linux/macOS)
if: runner.os != 'Windows'
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_POLICY_VERSION_MINIMUM=3.16 \
-DYAZE_BUILD_TESTS=OFF \
-DYAZE_BUILD_EMU=OFF \
-DYAZE_BUILD_Z3ED=OFF \
-DYAZE_ENABLE_UI_TESTS=ON \
-DYAZE_ENABLE_ROM_TESTS=OFF \
-DYAZE_ENABLE_EXPERIMENTAL_TESTS=OFF \
-DYAZE_INSTALL_LIB=OFF \
-GNinja
- name: Configure CMake (Windows)
if: runner.os == 'Windows'
shell: cmd
run: |
cmake -B build ^
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} ^
-DCMAKE_POLICY_VERSION_MINIMUM=3.16 ^
-DYAZE_BUILD_TESTS=OFF ^
-DYAZE_BUILD_EMU=OFF ^
-DYAZE_BUILD_Z3ED=OFF ^
-DYAZE_ENABLE_ROM_TESTS=OFF ^
-DYAZE_ENABLE_EXPERIMENTAL_TESTS=OFF ^
-DYAZE_INSTALL_LIB=OFF ^
-DYAZE_MINIMAL_BUILD=ON ^
-G "${{ matrix.cmake_generator }}" ^
-A ${{ matrix.cmake_generator_platform }}
# Build
- name: Build
run: cmake --build build --config ${{ env.BUILD_TYPE }} --parallel
# Package
- name: Package
shell: bash
run: ${{ matrix.package_cmd }}
# Create release with artifacts (will create release if it doesn't exist)
- name: Upload to Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ needs.validate-and-prepare.outputs.tag_name }}
name: Yaze ${{ needs.validate-and-prepare.outputs.tag_name }}
body: ${{ needs.validate-and-prepare.outputs.release_notes }}
draft: false
prerelease: ${{ contains(needs.validate-and-prepare.outputs.tag_name, 'beta') || contains(needs.validate-and-prepare.outputs.tag_name, 'alpha') || contains(needs.validate-and-prepare.outputs.tag_name, 'rc') }}
files: |
${{ matrix.artifact_name }}.*
fail_on_unmatched_files: true
publish-packages:
name: Publish Packages
needs: [validate-and-prepare, build-release]
runs-on: ubuntu-latest
if: success()
steps:
- name: Update release status
run: |
echo "Release has been published successfully"
echo "All build artifacts have been uploaded"
- name: Announce release
run: |
echo "🎉 Yaze ${{ needs.validate-and-prepare.outputs.tag_name }} has been released!"
echo "📦 Packages are now available for download"
echo "🔗 Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate-and-prepare.outputs.tag_name }}"

15
.gitignore vendored
View File

@@ -4,19 +4,7 @@ build/
.vscode/ .vscode/
disasm/ disasm/
src/etc src/etc
src/lib/SDL2 src/lib/
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 checks.json
assets/lib/libasar.dll assets/lib/libasar.dll
cmake/yaze.plist.in cmake/yaze.plist.in
@@ -30,3 +18,4 @@ src/app/emu/cpu/internal/old_cpu.cc
build-windows build-windows
src/lib/libpng src/lib/libpng
src/lib/zlib src/lib/zlib
assets/layouts/ow_toolset.zeml

12
.gitmodules vendored
View File

@@ -1,12 +1,6 @@
[submodule "src/lib/imgui"] [submodule "src/lib/imgui"]
path = src/lib/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 "assets/asm/alttp-hacker-workspace"] [submodule "assets/asm/alttp-hacker-workspace"]
path = assets/asm/alttp-hacker-workspace path = assets/asm/alttp-hacker-workspace
url = https://github.com/scawful/alttp-hacker-workspace.git url = https://github.com/scawful/alttp-hacker-workspace.git
@@ -22,3 +16,9 @@
[submodule "src/lib/imgui_test_engine"] [submodule "src/lib/imgui_test_engine"]
path = src/lib/imgui_test_engine path = src/lib/imgui_test_engine
url = https://github.com/ocornut/imgui_test_engine.git url = https://github.com/ocornut/imgui_test_engine.git
[submodule "src/lib/nativefiledialog-extended"]
path = src/lib/nativefiledialog-extended
url = https://github.com/btzy/nativefiledialog-extended.git
[submodule "assets/asm/usdasm"]
path = assets/asm/usdasm
url = https://github.com/spannerisms/usdasm.git

View File

@@ -1,36 +1,108 @@
# Yet Another Zelda3 Editor # Yet Another Zelda3 Editor
# by scawful # by scawful
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.16)
project(yaze VERSION 0.2.0
# Set policy version to handle compatibility issues
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
project(yaze VERSION 0.3.0
DESCRIPTION "Yet Another Zelda3 Editor" DESCRIPTION "Yet Another Zelda3 Editor"
LANGUAGES CXX) LANGUAGES CXX C)
# Set project metadata
set(YAZE_VERSION_MAJOR 0)
set(YAZE_VERSION_MINOR 3)
set(YAZE_VERSION_PATCH 0)
configure_file(src/yaze_config.h.in yaze_config.h @ONLY)
# Build Flags # Build Flags
set(YAZE_BUILD_APP ON) set(YAZE_BUILD_APP ON)
set(YAZE_BUILD_LIB ON) set(YAZE_BUILD_LIB ON)
set(YAZE_BUILD_EMU ON) set(YAZE_BUILD_EMU ON)
set(YAZE_BUILD_Z3ED ON) set(YAZE_BUILD_Z3ED ON)
set(YAZE_BUILD_PYTHON OFF)
set(YAZE_BUILD_TESTS ON) set(YAZE_BUILD_TESTS ON)
set(YAZE_INSTALL_LIB OFF) set(YAZE_INSTALL_LIB OFF)
# libpng features in bitmap.cc # Testing and CI Configuration
add_definitions("-DYAZE_LIB_PNG=1") option(YAZE_ENABLE_ROM_TESTS "Enable tests that require ROM files" OFF)
option(YAZE_ENABLE_EXPERIMENTAL_TESTS "Enable experimental/unstable tests" ON)
option(YAZE_ENABLE_UI_TESTS "Enable ImGui Test Engine UI testing" ON)
option(YAZE_MINIMAL_BUILD "Minimal build for CI (disable optional features)" OFF)
# C++ Standard and CMake Specifications # Configure minimal builds for CI/CD
set(CMAKE_CXX_STANDARD 20) if(YAZE_MINIMAL_BUILD)
set(YAZE_ENABLE_UI_TESTS OFF CACHE BOOL "Disabled for minimal build" FORCE)
set(YAZE_BUILD_Z3ED OFF CACHE BOOL "Disabled for minimal build" FORCE)
# Keep EMU and LIB enabled for comprehensive testing
set(YAZE_BUILD_EMU ON CACHE BOOL "Required for test suite" FORCE)
set(YAZE_BUILD_LIB ON CACHE BOOL "Required for test suite" FORCE)
set(YAZE_INSTALL_LIB OFF CACHE BOOL "Disabled for minimal build" FORCE)
endif()
set(YAZE_TEST_ROM_PATH "${CMAKE_BINARY_DIR}/bin/zelda3.sfc" CACHE STRING "Path to test ROM file")
# libpng features in bitmap.cc - conditional for minimal builds
if(PNG_FOUND)
add_definitions("-DYAZE_LIB_PNG=1")
else()
add_definitions("-DYAZE_LIB_PNG=0")
message(STATUS "Building without PNG support for minimal build")
endif()
# Modern CMake standards
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_C_STANDARD 99)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_C_STANDARD_REQUIRED ON)
# Output directories
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF)
set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_FRAMEWORK LAST)
set(CMAKE_SHARED_MODULE_PREFIX "")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dlinux -Dstricmp=strcasecmp") # Platform detection
if (MACOS) if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(CMAKE_INSTALL_PREFIX /usr/local) set(YAZE_PLATFORM_MACOS ON)
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(YAZE_PLATFORM_LINUX ON)
elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(YAZE_PLATFORM_WINDOWS ON)
endif()
# Create a common interface target for shared settings
add_library(yaze_common INTERFACE)
target_compile_features(yaze_common INTERFACE cxx_std_23)
# Platform-specific configurations
if(YAZE_PLATFORM_LINUX)
target_compile_definitions(yaze_common INTERFACE linux stricmp=strcasecmp)
elseif(YAZE_PLATFORM_MACOS)
set(CMAKE_INSTALL_PREFIX /usr/local)
target_compile_definitions(yaze_common INTERFACE MACOS)
elseif(YAZE_PLATFORM_WINDOWS)
include(cmake/vcpkg.cmake)
target_compile_definitions(yaze_common INTERFACE WINDOWS)
endif()
# Compiler-specific settings
if(MSVC)
target_compile_options(yaze_common INTERFACE /W4 /permissive-)
target_compile_definitions(yaze_common INTERFACE
_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_WARNINGS
strncasecmp=_strnicmp
strcasecmp=_stricmp
)
else()
target_compile_options(yaze_common INTERFACE -Wall -Wextra -Wpedantic)
endif() endif()
# Abseil Standard Specifications # Abseil Standard Specifications
@@ -42,8 +114,49 @@ include(cmake/sdl2.cmake)
# Asar # Asar
include(cmake/asar.cmake) include(cmake/asar.cmake)
# ImGui # Google Test (if needed for main app integration)
if (YAZE_BUILD_TESTS)
include(cmake/gtest.cmake)
endif()
# ImGui (after minimal build flags are set)
include(cmake/imgui.cmake) include(cmake/imgui.cmake)
# Project Files # Project Files
# Copy theme files to build directory (for development)
file(GLOB THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme")
file(COPY ${THEME_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/themes/")
# IMPORTANT: Also ensure themes are included in macOS bundles
# This is handled in src/CMakeLists.txt via YAZE_RESOURCE_FILES
add_subdirectory(src) add_subdirectory(src)
# Tests
if (YAZE_BUILD_TESTS)
add_subdirectory(test)
endif()
# Code quality targets
find_program(CLANG_FORMAT NAMES clang-format clang-format-14 clang-format-15 clang-format-16 clang-format-17 clang-format-18)
if(CLANG_FORMAT)
file(GLOB_RECURSE ALL_SOURCE_FILES
"${CMAKE_SOURCE_DIR}/src/*.cc"
"${CMAKE_SOURCE_DIR}/src/*.h"
"${CMAKE_SOURCE_DIR}/test/*.cc"
"${CMAKE_SOURCE_DIR}/test/*.h")
add_custom_target(format
COMMAND ${CLANG_FORMAT} -i --style=Google ${ALL_SOURCE_FILES}
COMMENT "Running clang-format on source files"
)
add_custom_target(format-check
COMMAND ${CLANG_FORMAT} --dry-run --Werror --style=Google ${ALL_SOURCE_FILES}
COMMENT "Checking code format"
)
endif()
# Packaging configuration
include(cmake/packaging.cmake)

416
CMakePresets.json Normal file
View File

@@ -0,0 +1,416 @@
{
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 16,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"displayName": "Default Config",
"description": "Default build configuration",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"YAZE_BUILD_TESTS": "ON",
"YAZE_BUILD_APP": "ON",
"YAZE_BUILD_LIB": "ON",
"YAZE_BUILD_EMU": "ON",
"YAZE_BUILD_Z3ED": "ON"
}
},
{
"name": "debug",
"displayName": "Debug",
"description": "Debug build with full debugging symbols",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS_DEBUG": "-g -O0 -DDEBUG",
"CMAKE_C_FLAGS_DEBUG": "-g -O0 -DDEBUG",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "release",
"displayName": "Release",
"description": "Optimized release build",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"YAZE_BUILD_TESTS": "OFF"
}
},
{
"name": "dev",
"displayName": "Development",
"description": "Development build with ROM testing enabled",
"inherits": "debug",
"cacheVariables": {
"YAZE_ENABLE_ROM_TESTS": "ON",
"YAZE_TEST_ROM_PATH": "${sourceDir}/build/bin/zelda3.sfc"
}
},
{
"name": "macos-dev",
"displayName": "macOS Development (ARM64)",
"description": "macOS ARM64 development build with ROM testing",
"inherits": "macos-debug",
"cacheVariables": {
"YAZE_ENABLE_ROM_TESTS": "ON",
"YAZE_TEST_ROM_PATH": "${sourceDir}/build/bin/zelda3.sfc"
}
},
{
"name": "ci",
"displayName": "Continuous Integration",
"description": "CI build without ROM-dependent tests",
"inherits": "default",
"cacheVariables": {
"YAZE_ENABLE_ROM_TESTS": "OFF",
"YAZE_BUILD_TESTS": "ON"
}
},
{
"name": "macos-debug",
"displayName": "macOS Debug (ARM64)",
"description": "macOS ARM64-specific debug configuration",
"inherits": "debug",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"cacheVariables": {
"CMAKE_OSX_DEPLOYMENT_TARGET": "11.0",
"CMAKE_OSX_ARCHITECTURES": "arm64"
}
},
{
"name": "macos-release",
"displayName": "macOS Release (ARM64)",
"description": "macOS ARM64-specific release configuration",
"inherits": "release",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"cacheVariables": {
"CMAKE_OSX_DEPLOYMENT_TARGET": "11.0",
"CMAKE_OSX_ARCHITECTURES": "arm64"
}
},
{
"name": "macos-debug-universal",
"displayName": "macOS Debug (Universal)",
"description": "macOS universal binary debug configuration",
"inherits": "debug",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"cacheVariables": {
"CMAKE_OSX_DEPLOYMENT_TARGET": "10.15",
"CMAKE_OSX_ARCHITECTURES": "x86_64;arm64"
}
},
{
"name": "macos-release-universal",
"displayName": "macOS Release (Universal)",
"description": "macOS universal binary release configuration",
"inherits": "release",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"cacheVariables": {
"CMAKE_OSX_DEPLOYMENT_TARGET": "10.15",
"CMAKE_OSX_ARCHITECTURES": "x86_64;arm64"
}
},
{
"name": "linux-debug",
"displayName": "Linux Debug",
"description": "Linux-specific debug configuration",
"inherits": "debug",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"cacheVariables": {
"CMAKE_CXX_COMPILER": "g++",
"CMAKE_C_COMPILER": "gcc"
}
},
{
"name": "linux-clang",
"displayName": "Linux Clang",
"description": "Linux build with Clang",
"inherits": "debug",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_C_COMPILER": "clang"
}
},
{
"name": "windows-debug",
"displayName": "Windows Debug",
"description": "Windows-specific debug configuration",
"inherits": "debug",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
"VCPKG_TARGET_TRIPLET": "x64-windows"
}
},
{
"name": "asan",
"displayName": "AddressSanitizer",
"description": "Debug build with AddressSanitizer",
"inherits": "debug",
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-fsanitize=address -fno-omit-frame-pointer -g",
"CMAKE_C_FLAGS": "-fsanitize=address -fno-omit-frame-pointer -g",
"CMAKE_EXE_LINKER_FLAGS": "-fsanitize=address",
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address"
}
},
{
"name": "coverage",
"displayName": "Code Coverage",
"description": "Debug build with code coverage",
"inherits": "debug",
"cacheVariables": {
"CMAKE_CXX_FLAGS": "--coverage -g -O0",
"CMAKE_C_FLAGS": "--coverage -g -O0",
"CMAKE_EXE_LINKER_FLAGS": "--coverage"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default",
"displayName": "Default Build"
},
{
"name": "debug",
"configurePreset": "debug",
"displayName": "Debug Build"
},
{
"name": "release",
"configurePreset": "release",
"displayName": "Release Build"
},
{
"name": "dev",
"configurePreset": "dev",
"displayName": "Development Build"
},
{
"name": "macos-dev",
"configurePreset": "macos-dev",
"displayName": "macOS Development Build (ARM64)"
},
{
"name": "ci",
"configurePreset": "ci",
"displayName": "CI Build"
},
{
"name": "macos-debug",
"configurePreset": "macos-debug",
"displayName": "macOS Debug Build"
},
{
"name": "macos-release",
"configurePreset": "macos-release",
"displayName": "macOS Release Build (ARM64)"
},
{
"name": "macos-debug-universal",
"configurePreset": "macos-debug-universal",
"displayName": "macOS Debug Build (Universal)"
},
{
"name": "macos-release-universal",
"configurePreset": "macos-release-universal",
"displayName": "macOS Release Build (Universal)"
},
{
"name": "fast",
"configurePreset": "debug",
"displayName": "Fast Debug Build",
"jobs": 0
}
],
"testPresets": [
{
"name": "stable",
"configurePreset": "default",
"displayName": "Stable Tests (Release Ready)",
"execution": {
"noTestsAction": "error",
"stopOnFailure": true
},
"filter": {
"include": {
"label": "STABLE"
}
}
},
{
"name": "dev",
"configurePreset": "dev",
"displayName": "Development Tests (with ROM)",
"execution": {
"noTestsAction": "error",
"stopOnFailure": false
},
"filter": {
"exclude": {
"label": "EXPERIMENTAL"
}
}
},
{
"name": "ci",
"configurePreset": "ci",
"displayName": "CI Tests (stable only)",
"execution": {
"noTestsAction": "error",
"stopOnFailure": true
},
"filter": {
"include": {
"label": "STABLE"
}
}
},
{
"name": "experimental",
"configurePreset": "debug",
"displayName": "Experimental Tests",
"execution": {
"noTestsAction": "ignore",
"stopOnFailure": false
},
"filter": {
"include": {
"label": "EXPERIMENTAL"
}
}
},
{
"name": "asar-only",
"configurePreset": "default",
"displayName": "Asar Tests Only",
"filter": {
"include": {
"name": "*Asar*"
}
}
},
{
"name": "unit-only",
"configurePreset": "default",
"displayName": "Unit Tests Only",
"filter": {
"include": {
"label": "UNIT_TEST"
}
}
}
],
"packagePresets": [
{
"name": "default",
"configurePreset": "release",
"displayName": "Default Package"
},
{
"name": "macos",
"configurePreset": "macos-release",
"displayName": "macOS Package (ARM64)"
},
{
"name": "macos-universal",
"configurePreset": "macos-release-universal",
"displayName": "macOS Package (Universal)"
}
],
"workflowPresets": [
{
"name": "dev-workflow",
"displayName": "Development Workflow",
"steps": [
{
"type": "configure",
"name": "dev"
},
{
"type": "build",
"name": "dev"
},
{
"type": "test",
"name": "dev"
}
]
},
{
"name": "ci-workflow",
"displayName": "CI Workflow",
"steps": [
{
"type": "configure",
"name": "ci"
},
{
"type": "build",
"name": "ci"
},
{
"type": "test",
"name": "ci"
}
]
},
{
"name": "release-workflow",
"displayName": "Release Workflow",
"steps": [
{
"type": "configure",
"name": "macos-release"
},
{
"type": "build",
"name": "macos-release"
},
{
"type": "package",
"name": "macos"
}
]
}
]
}

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = "yaze"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = "0.2.0" PROJECT_NUMBER = "0.3.0"
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
@@ -1284,7 +1284,7 @@ ALPHABETICAL_INDEX = YES
# after removing the prefix. # after removing the prefix.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX = IGNORE_PREFIX = googletest
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to the HTML output # Configuration options related to the HTML output
@@ -2798,7 +2798,7 @@ PLANTUML_INCLUDE_PATH =
# Minimum value: 0, maximum value: 10000, default value: 50. # Minimum value: 0, maximum value: 10000, default value: 50.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_GRAPH_MAX_NODES = 25 DOT_GRAPH_MAX_NODES = 5
# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
# generated by dot. A depth value of 3 means that only nodes reachable from the # generated by dot. A depth value of 3 means that only nodes reachable from the

149
README.md
View File

@@ -1,59 +1,134 @@
# Yet Another Zelda3 Editor # YAZE - Yet Another Zelda3 Editor
- Platform: Windows, macOS, iOS, GNU/Linux A modern, cross-platform editor for The Legend of Zelda: A Link to the Past ROM hacking, built with C++23 and featuring complete Asar 65816 assembler integration.
- Dependencies: SDL2, ImGui, abseil-cpp
## Description [![Build Status](https://github.com/scawful/yaze/workflows/CI/badge.svg)](https://github.com/scawful/yaze/actions)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo. ## Version 0.3.0 - Stable Release
Provides bindings in C and Python for building custom tools and utilities. #### Asar 65816 Assembler Integration
- **Cross-platform ROM patching** with assembly code support
- **Symbol extraction** with addresses and opcodes from assembly files
- **Assembly validation** with comprehensive error reporting
- **Modern C++ API** with safe memory management
Takes heavy inspiration from ALTTP community efforts such as [Hyrule Magic](https://www.romhacking.net/utilities/200/) and [ZScream](https://github.com/Zarby89/ZScreamDungeon) #### ZSCustomOverworld v3
- **Enhanced overworld editing** capabilities
- **Advanced map properties** and metadata support
- **Custom graphics support** and tile management
- **Improved compatibility** with existing projects
Building and installation #### Advanced Features
------------------------- - **Theme Management**: Complete theme system with 5+ built-in themes and custom theme editor
[CMake](http://www.cmake.org "CMake") is required to build yaze - **Multi-Session Support**: Work with multiple ROMs simultaneously in docked workspace
- **Enhanced Welcome Screen**: Themed interface with quick access to all editors
- **Message Editing**: Enhanced text editing interface with real-time preview
- **GUI Docking**: Flexible workspace management with customizable layouts
- **Modern CLI**: Enhanced z3ed tool with interactive TUI and subcommands
- **Cross-Platform**: Full support for Windows, macOS, and Linux
1. Clone the repository ### 🛠️ Technical Improvements
2. Create the build directory and configuration - **Modern CMake 3.16+**: Target-based configuration and build system
3. Build and run the application - **CMakePresets**: Development workflow presets for better productivity
4. (Optional) Run the tests - **Cross-platform CI/CD**: Automated builds and testing for all platforms
- **Professional packaging**: NSIS, DMG, and DEB/RPM installers
- **Enhanced testing**: ROM-dependent test separation for CI compatibility
``` ## Quick Start
git clone --recurse-submodules https://github.com/scawful/yaze.git
cmake -S . -B build ### Build
cmake --build build ```bash
# Clone with submodules
git clone --recursive https://github.com/scawful/yaze.git
cd yaze
# Build with CMake
cmake --preset debug # macOS
cmake -B build && cmake --build build # Linux/Windows
``` ```
By default this will build all targets. ### Applications
- **yaze**: Complete GUI editor for Zelda 3 ROM hacking
- **z3ed**: Command-line tool with interactive interface
- **yaze_test**: Comprehensive test suite for development
- **yaze**: Editor Application ## Usage
- **yaze_c**: C Library
- **yaze_emu**: SNES Emulator
- **yaze_py**: Python Module
- **yaze_test**: Unit Tests
- **z3ed**: Command Line Interface
Dependencies are included as submodules and will be built automatically. For those who want to reduce compile times, consider installing the dependencies on your system. See [build-instructions.md](docs/build-instructions.md) for more information. ### GUI Editor
Launch the main application to edit Zelda 3 ROMs:
- Load ROM files using native file dialogs
- Edit overworld maps, dungeons, sprites, and graphics
- Apply assembly patches with integrated Asar support
- Export modifications as patches or modified ROMs
### Command Line Tool
```bash
# Apply assembly patch
z3ed asar patch.asm --rom=zelda3.sfc
# Extract symbols from assembly
z3ed extract patch.asm
# Interactive mode
z3ed --tui
```
### C++ API
```cpp
#include "yaze.h"
// Load ROM and apply patch
yaze_project_t* project = yaze_load_project("zelda3.sfc");
yaze_apply_asar_patch(project, "patch.asm");
yaze_save_project(project, "modified.sfc");
```
## Documentation ## Documentation
- For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze. - [Getting Started](docs/01-getting-started.md) - Setup and basic usage
- For developers, please refer to the [documentation](https://scawful.github.io/yaze/index.html) for information on the project's infrastructure. - [Build Instructions](docs/02-build-instructions.md) - Building from source
- [API Reference](docs/04-api-reference.md) - Programming interface
- [Contributing](docs/B1-contributing.md) - Development guidelines
License **[Complete Documentation](docs/index.md)**
--------
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. ## Supported Platforms
Screenshots - **Windows** (MSVC 2019+, MinGW)
-------- - **macOS** (Intel and Apple Silicon)
![image](https://github.com/scawful/yaze/assets/47263509/8b62b142-1de4-4ca4-8c49-d50c08ba4c8e) - **Linux** (GCC 13+, Clang 16+)
## ROM Compatibility
![image](https://github.com/scawful/yaze/assets/47263509/d8f0039d-d2e4-47d7-b420-554b20ac626f) - Original Zelda 3 ROMs (US/Japan versions)
- ZSCustomOverworld v2/v3 enhanced overworld features
- Community ROM hacks and modifications
![image](https://github.com/scawful/yaze/assets/47263509/34b36666-cbea-420b-af90-626099470ae4) ## Contributing
See [Contributing Guide](docs/B1-contributing.md) for development guidelines.
**Community**: [Oracle of Secrets Discord](https://discord.gg/MBFkMTPEmk)
## License
GNU GPL v3 - See [LICENSE](LICENSE) for details.
## 🙏 Acknowledgments
Takes inspiration from:
- [Hyrule Magic](https://www.romhacking.net/utilities/200/) - Original Zelda 3 editor
- [ZScream](https://github.com/Zarby89/ZScreamDungeon) - Dungeon editing capabilities
- [Asar](https://github.com/RPGHacker/asar) - 65816 assembler integration
## 📸 Screenshots
![YAZE GUI Editor](https://github.com/scawful/yaze/assets/47263509/8b62b142-1de4-4ca4-8c49-d50c08ba4c8e)
![Dungeon Editor](https://github.com/scawful/yaze/assets/47263509/d8f0039d-d2e4-47d7-b420-554b20ac626f)
![Overworld Editor](https://github.com/scawful/yaze/assets/47263509/34b36666-cbea-420b-af90-626099470ae4)
---
**Ready to hack Zelda 3? [Get started now!](docs/01-getting-started.md)**

File diff suppressed because it is too large Load Diff

1
assets/asm/usdasm Submodule

Submodule assets/asm/usdasm added at d53311a54a

View File

@@ -15,7 +15,7 @@ endif
!ZS_CUSTOM_OVERWORLD = 1 !ZS_CUSTOM_OVERWORLD = 1
if !ZS_CUSTOM_OVERWORLD != 0 if !ZS_CUSTOM_OVERWORLD != 0
incsrc "ZSCustomOverworld.asm" incsrc "ZSCustomOverworld_v3.asm"
endif endif
} }

View File

@@ -0,0 +1,62 @@
# Cyberpunk Theme
# Neon-inspired futuristic theme
name=Cyberpunk
description=Neon-inspired futuristic theme
author=YAZE Team
version=1.0
[colors]
# Primary colors (neon cyberpunk)
primary=255,20,147,255
secondary=0,255,255,255
accent=255,0,128,255
background=10,10,20,255
surface=20,20,40,255
# Status colors
error=255,50,100,255
warning=255,255,0,255
success=0,255,100,255
info=100,200,255,255
# Text colors
text_primary=255,255,255,255
text_secondary=200,200,255,255
text_disabled=100,100,150,255
# Window colors
window_bg=15,15,30,240
child_bg=10,10,25,200
popup_bg=20,20,40,250
# Interactive elements
button=40,20,60,255
button_hovered=120,20,120,255
button_active=160,40,160,255
frame_bg=30,30,50,255
frame_bg_hovered=40,40,70,255
frame_bg_active=60,20,80,255
# Navigation
header=30,10,50,255
header_hovered=80,20,100,255
header_active=120,40,140,255
tab=25,15,45,255
tab_hovered=60,30,80,255
tab_active=100,20,120,255
menu_bar_bg=20,10,40,255
title_bg=25,15,45,255
title_bg_active=30,10,50,255
title_bg_collapsed=25,15,45,255
[style]
window_rounding=10.0
frame_rounding=8.0
scrollbar_rounding=10.0
grab_rounding=6.0
tab_rounding=6.0
window_border_size=1.0
frame_border_size=1.0
enable_animations=true
enable_glow_effects=true
animation_speed=1.5

View File

@@ -0,0 +1,73 @@
# Forest Theme
# Enhanced forest theme with better readability
name=Forest
description=Deep forest theme with enhanced readability
author=YAZE Team
version=1.0
[colors]
# Primary colors (enhanced forest with better contrast)
primary=100,180,120,255 # Brighter forest green
secondary=70,130,85,255 # Mid forest green
accent=130,220,150,255 # Light accent green
background=15,25,15,255 # Darker background for contrast
surface=25,35,25,255
# Status colors
error=255,120,120,255
warning=255,220,120,255
success=100,180,120,255
info=120,200,180,255
# Text colors (enhanced for readability)
text_primary=250,255,250,255 # Very light for contrast
text_secondary=220,240,220,255 # Light green tint
text_disabled=150,170,150,255 # Brighter disabled text
# Window colors (better contrast)
window_bg=20,30,20,240
child_bg=15,25,15,200
popup_bg=25,35,25,250
# Interactive elements (better visibility)
button=70,110,80,255
button_hovered=100,150,115,255
button_active=130,180,145,255
frame_bg=40,60,45,200
frame_bg_hovered=55,80,60,220
frame_bg_active=70,110,80,240
# Navigation (better contrast)
header=55,85,60,255
header_hovered=100,150,115,255
header_active=130,180,145,255
tab=45,75,50,255
tab_hovered=70,110,80,255
tab_active=100,150,115,255
menu_bar_bg=40,70,45,255
title_bg=50,80,55,255
title_bg_active=55,85,60,255
title_bg_collapsed=45,75,50,255
# Separators (better visibility)
separator=120,160,130,180
separator_hovered=150,200,160,220
separator_active=180,240,190,255
# Scrollbars (better visibility)
scrollbar_bg=40,60,45,180
scrollbar_grab=80,120,90,200
scrollbar_grab_hovered=100,150,115,230
scrollbar_grab_active=130,180,145,255
[style]
window_rounding=6.0
frame_rounding=4.0
scrollbar_rounding=6.0
grab_rounding=3.0
tab_rounding=3.0
window_border_size=0.0
frame_border_size=0.0
enable_animations=true
enable_glow_effects=false
animation_speed=1.0

View File

@@ -0,0 +1,73 @@
# Midnight Theme
# Enhanced midnight theme with better readability
name=Midnight
description=Deep blue midnight theme with enhanced readability
author=YAZE Team
version=1.0
[colors]
# Primary colors (enhanced midnight with better contrast)
primary=100,160,230,255 # Brighter blue
secondary=70,120,180,255 # Mid blue
accent=140,200,255,255 # Light blue accent
background=10,15,25,255 # Darker background for contrast
surface=20,25,35,255
# Status colors
error=255,120,120,255
warning=255,220,120,255
success=120,255,180,255
info=140,200,255,255
# Text colors (enhanced for readability)
text_primary=245,250,255,255 # Very light blue-white
text_secondary=200,220,240,255 # Light blue tint
text_disabled=140,160,180,255 # Brighter disabled text
# Window colors (better contrast)
window_bg=15,20,30,240
child_bg=10,15,25,200
popup_bg=20,25,35,250
# Interactive elements (better visibility)
button=50,80,120,255
button_hovered=80,120,160,255
button_active=110,150,190,255
frame_bg=30,45,65,200
frame_bg_hovered=40,60,85,220
frame_bg_active=60,90,130,240
# Navigation (better contrast)
header=40,65,100,255
header_hovered=70,110,150,255
header_active=100,140,180,255
tab=30,55,90,255
tab_hovered=50,80,120,255
tab_active=70,110,150,255
menu_bar_bg=25,50,85,255
title_bg=35,60,95,255
title_bg_active=40,65,100,255
title_bg_collapsed=30,55,90,255
# Separators (better visibility)
separator=100,140,180,180
separator_hovered=130,170,210,220
separator_active=160,200,240,255
# Scrollbars (better visibility)
scrollbar_bg=30,45,65,180
scrollbar_grab=70,110,150,200
scrollbar_grab_hovered=100,140,180,230
scrollbar_grab_active=130,170,210,255
[style]
window_rounding=8.0
frame_rounding=6.0
scrollbar_rounding=8.0
grab_rounding=4.0
tab_rounding=6.0
window_border_size=0.0
frame_border_size=0.0
enable_animations=true
enable_glow_effects=true
animation_speed=1.2

View File

@@ -0,0 +1,62 @@
# Sunset Theme
# Warm orange and purple sunset theme
name=Sunset
description=Warm orange and purple sunset theme
author=YAZE Team
version=1.0
[colors]
# Primary colors (sunset)
primary=255,140,60,255
secondary=200,100,150,255
accent=255,180,100,255
background=30,20,35,255
surface=40,30,45,255
# Status colors
error=255,100,120,255
warning=255,200,100,255
success=150,255,150,255
info=150,200,255,255
# Text colors
text_primary=255,245,235,255
text_secondary=220,200,180,255
text_disabled=150,130,120,255
# Window colors
window_bg=35,25,40,240
child_bg=30,20,35,200
popup_bg=40,30,45,250
# Interactive elements
button=60,40,70,255
button_hovered=120,80,100,255
button_active=150,100,120,255
frame_bg=45,35,50,255
frame_bg_hovered=55,45,60,255
frame_bg_active=80,60,90,255
# Navigation
header=50,35,60,255
header_hovered=100,70,90,255
header_active=130,90,110,255
tab=40,30,50,255
tab_hovered=70,50,70,255
tab_active=100,70,90,255
menu_bar_bg=35,25,45,255
title_bg=40,30,50,255
title_bg_active=50,35,60,255
title_bg_collapsed=40,30,50,255
[style]
window_rounding=12.0
frame_rounding=8.0
scrollbar_rounding=12.0
grab_rounding=6.0
tab_rounding=8.0
window_border_size=0.0
frame_border_size=0.0
enable_animations=true
enable_glow_effects=false
animation_speed=1.0

View File

@@ -0,0 +1,95 @@
# YAZE Tre Theme - Enhanced Edition
# Premium theme resource edition with improved colors and contrast
name=YAZE Tre
description=Enhanced YAZE theme with improved readability and modern colors
author=YAZE Team
version=2.0
[colors]
# Primary colors (enhanced ALTTP colors with better contrast)
primary=105,135,105,255 # Brighter green for better visibility
secondary=85,110,85,255 # Mid-tone green with more saturation
accent=110,145,110,255 # Vibrant accent green for highlights
background=12,12,15,255 # Slightly blue-tinted dark background
surface=18,18,22,255 # Warmer surface color
# Status colors (enhanced for better visibility)
error=235,75,75,255 # Brighter red for better visibility
warning=255,200,50,255 # Warmer yellow-orange
success=105,135,105,255 # Match primary green
info=70,170,255,255 # Brighter blue
# Text colors (enhanced contrast)
text_primary=245,245,245,255 # Brighter white for better readability
text_secondary=200,200,200,255 # Higher contrast secondary text
text_disabled=140,140,140,255 # Slightly brighter disabled text
# Window colors (enhanced backgrounds)
window_bg=12,12,15,230 # Slightly blue-tinted with transparency
child_bg=0,0,0,0 # Transparent child backgrounds
popup_bg=20,20,25,240 # Warmer popup background
# Interactive elements (enhanced for better UX)
button=85,110,85,255 # Enhanced mid-green for better visibility
button_hovered=135,160,135,255 # Brighter hover state
button_active=105,135,105,255 # Active state matches primary
frame_bg=25,25,30,150 # Darker frames with transparency
frame_bg_hovered=85,110,85,120 # Green tint on hover
frame_bg_active=105,135,105,180 # Primary green when active
# Navigation (enhanced contrast)
header=55,75,55,255 # Slightly brighter header
header_hovered=105,135,105,255 # Primary green on hover
header_active=85,110,85,255 # Secondary green when active
tab=45,60,45,255 # Darker tab background
tab_hovered=85,110,85,255 # Secondary green on hover
tab_active=110,145,110,255 # Accent green for active tab
menu_bar_bg=55,75,55,255 # Match header background
title_bg=85,110,85,255 # Secondary green
title_bg_active=55,75,55,255 # Darker when active
title_bg_collapsed=85,110,85,255 # Secondary green when collapsed
# Borders and separators (exact from original)
border=92,115,92,255 # allttpLightGreen
border_shadow=0,0,0,0 # No shadow in original
separator=127,127,127,153 # 0.50f, 0.50f, 0.50f, 0.60f
separator_hovered=153,153,178,255 # 0.60f, 0.60f, 0.70f
separator_active=178,178,230,255 # 0.70f, 0.70f, 0.90f
# Scrollbars (exact from original)
scrollbar_bg=92,115,92,153 # 0.36f, 0.45f, 0.36f, 0.60f
scrollbar_grab=92,115,92,76 # 0.36f, 0.45f, 0.36f, 0.30f (exact)
scrollbar_grab_hovered=92,115,92,102 # 0.36f, 0.45f, 0.36f, 0.40f
scrollbar_grab_active=92,115,92,153 # 0.36f, 0.45f, 0.36f, 0.60f
# Resize grips (exact from original - these are light blue, not white!)
resize_grip=255,255,255,26 # 1.00f, 1.00f, 1.00f, 0.10f
resize_grip_hovered=199,209,255,153 # 0.78f, 0.82f, 1.00f, 0.60f (light blue!)
resize_grip_active=199,209,255,230 # 0.78f, 0.82f, 1.00f, 0.90f (light blue!)
# Additional controls (enhanced)
check_mark=245,245,245,200 # Brighter check marks for visibility
slider_grab=180,180,180,120 # More visible slider grab
slider_grab_active=110,145,110,200 # Accent color when active
# Table colors (enhanced)
table_header_bg=55,75,55,255 # Slightly brighter header
table_border_strong=85,110,85,255 # Secondary green borders
table_border_light=70,70,75,255 # Better contrast light borders
table_row_bg=0,0,0,0 # Transparent
table_row_bg_alt=255,255,255,25 # Slightly more visible alternating rows
# Link colors (high contrast for better visibility)
text_link=120,200,255,255 # Bright blue for links - high contrast against dark backgrounds
[style]
window_rounding=0.0
frame_rounding=5.0
scrollbar_rounding=5.0
grab_rounding=3.0
tab_rounding=0.0
window_border_size=0.0
frame_border_size=0.0
enable_animations=true
enable_glow_effects=false
animation_speed=1.0

BIN
assets/yaze.icns Normal file

Binary file not shown.

View File

@@ -1,15 +1,24 @@
if (MINGW) if (MINGW OR WIN32)
add_subdirectory(src/lib/abseil-cpp)
elseif(YAZE_MINIMAL_BUILD)
# For CI builds, always use submodule to avoid dependency issues
add_subdirectory(src/lib/abseil-cpp) add_subdirectory(src/lib/abseil-cpp)
else() else()
find_package(absl) # Try system package first, fallback to submodule
find_package(absl QUIET)
if(NOT absl_FOUND)
message(STATUS "System Abseil not found, using submodule")
add_subdirectory(src/lib/abseil-cpp)
endif()
endif() endif()
set(ABSL_PROPAGATE_CXX_STD ON) set(ABSL_PROPAGATE_CXX_STD ON)
set(ABSL_CXX_STANDARD 17) set(ABSL_CXX_STANDARD 23)
set(ABSL_USE_GOOGLETEST_HEAD ON) set(ABSL_USE_GOOGLETEST_HEAD ON)
set(ABSL_ENABLE_INSTALL ON) set(ABSL_ENABLE_INSTALL ON)
set( set(
ABSL_TARGETS ABSL_TARGETS
absl::strings absl::strings
absl::str_format
absl::flags absl::flags
absl::status absl::status
absl::statusor absl::statusor
@@ -18,7 +27,20 @@ set(
absl::base absl::base
absl::config absl::config
absl::core_headers absl::core_headers
absl::raw_logging_internal
absl::failure_signal_handler absl::failure_signal_handler
absl::flat_hash_map absl::flat_hash_map
absl::cord
absl::hash
absl::synchronization
absl::time
absl::symbolize
absl::flags_commandlineflag
absl::flags_marshalling
absl::flags_private_handle_accessor
absl::flags_program_name
absl::flags_config
absl::flags_reflection
absl::container_memory
absl::memory
absl::utility
) )

View File

@@ -1,39 +1,94 @@
# Asar Assembler for 65816 SNES Assembly # Modern Asar 65816 Assembler Integration
add_subdirectory(src/lib/asar/src) # Improved cross-platform support for macOS, Linux, and Windows
set(ASAR_GEN_EXE OFF) # Configure Asar build options
set(ASAR_GEN_DLL ON) set(ASAR_GEN_EXE OFF CACHE BOOL "Build Asar standalone executable")
set(ASAR_GEN_LIB ON) set(ASAR_GEN_DLL ON CACHE BOOL "Build Asar shared library")
set(ASAR_GEN_EXE_TEST OFF) set(ASAR_GEN_LIB ON CACHE BOOL "Build Asar static library")
set(ASAR_GEN_DLL_TEST OFF) set(ASAR_GEN_EXE_TEST OFF CACHE BOOL "Build Asar executable tests")
set(ASAR_STATIC_SRC_DIR "${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar") set(ASAR_GEN_DLL_TEST OFF CACHE BOOL "Build Asar DLL tests")
get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES) # Set Asar source directory
list(APPEND ASAR_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/src/lib/asar/src") set(ASAR_SRC_DIR "${CMAKE_SOURCE_DIR}/src/lib/asar/src")
target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR})
set(ASAR_STATIC_SRC # Add Asar as subdirectory
"${ASAR_STATIC_SRC_DIR}/interface-lib.cpp" add_subdirectory(${ASAR_SRC_DIR} EXCLUDE_FROM_ALL)
"${ASAR_STATIC_SRC_DIR}/addr2line.cpp"
"${ASAR_STATIC_SRC_DIR}/arch-65816.cpp"
"${ASAR_STATIC_SRC_DIR}/arch-spc700.cpp"
"${ASAR_STATIC_SRC_DIR}/arch-superfx.cpp"
"${ASAR_STATIC_SRC_DIR}/assembleblock.cpp"
"${ASAR_STATIC_SRC_DIR}/crc32.cpp"
"${ASAR_STATIC_SRC_DIR}/libcon.cpp"
"${ASAR_STATIC_SRC_DIR}/libsmw.cpp"
"${ASAR_STATIC_SRC_DIR}/libstr.cpp"
"${ASAR_STATIC_SRC_DIR}/macro.cpp"
"${ASAR_STATIC_SRC_DIR}/main.cpp"
"${ASAR_STATIC_SRC_DIR}/asar_math.cpp"
"${ASAR_STATIC_SRC_DIR}/virtualfile.cpp"
"${ASAR_STATIC_SRC_DIR}/warnings.cpp"
"${ASAR_STATIC_SRC_DIR}/errors.cpp"
"${ASAR_STATIC_SRC_DIR}/platform/file-helpers.cpp"
)
if(WIN32 OR MINGW) # Create modern CMake target for Asar integration
list(APPEND ASAR_STATIC_SRC "${ASAR_STATIC_SRC_DIR}/platform/windows/file-helpers-win32.cpp") if(TARGET asar-static)
# Ensure asar-static is available and properly configured
set_target_properties(asar-static PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
)
# Set platform-specific definitions for Asar
if(WIN32)
target_compile_definitions(asar-static PRIVATE
windows
strncasecmp=_strnicmp
strcasecmp=_stricmp
_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_WARNINGS
)
elseif(UNIX AND NOT APPLE)
target_compile_definitions(asar-static PRIVATE
linux
stricmp=strcasecmp
)
elseif(APPLE)
target_compile_definitions(asar-static PRIVATE
MACOS
stricmp=strcasecmp
)
endif()
# Add include directories
target_include_directories(asar-static PUBLIC
$<BUILD_INTERFACE:${ASAR_SRC_DIR}>
$<BUILD_INTERFACE:${ASAR_SRC_DIR}/asar>
$<BUILD_INTERFACE:${ASAR_SRC_DIR}/asar-dll-bindings/c>
)
# Create alias for easier linking
add_library(yaze::asar ALIAS asar-static)
# Export Asar variables for use in other parts of the build
set(ASAR_FOUND TRUE CACHE BOOL "Asar library found")
set(ASAR_LIBRARIES asar-static CACHE STRING "Asar library target")
set(ASAR_INCLUDE_DIRS
"${ASAR_SRC_DIR}"
"${ASAR_SRC_DIR}/asar"
"${ASAR_SRC_DIR}/asar-dll-bindings/c"
CACHE STRING "Asar include directories"
)
message(STATUS "Asar 65816 assembler integration configured successfully")
else() else()
list(APPEND ASAR_STATIC_SRC "${ASAR_STATIC_SRC_DIR}/platform/linux/file-helpers-linux.cpp") message(WARNING "Failed to configure Asar static library target")
set(ASAR_FOUND FALSE CACHE BOOL "Asar library found")
endif() endif()
# Function to add Asar patching capabilities to a target
function(yaze_add_asar_support target_name)
if(ASAR_FOUND)
target_link_libraries(${target_name} PRIVATE yaze::asar)
target_include_directories(${target_name} PRIVATE ${ASAR_INCLUDE_DIRS})
target_compile_definitions(${target_name} PRIVATE YAZE_ENABLE_ASAR=1)
else()
message(WARNING "Asar not available for target ${target_name}")
endif()
endfunction()
# Create function for ROM patching utilities
function(yaze_create_asar_patch_tool tool_name patch_file rom_file)
if(ASAR_FOUND)
add_custom_target(${tool_name}
COMMAND ${CMAKE_COMMAND} -E echo "Patching ROM with Asar..."
COMMAND $<TARGET_FILE:asar-standalone> ${patch_file} ${rom_file}
DEPENDS asar-standalone
COMMENT "Applying Asar patch ${patch_file} to ${rom_file}"
)
endif()
endfunction()

View File

@@ -1,27 +1,43 @@
# gui libraries --------------------------------------------------------------- # gui libraries ---------------------------------------------------------------
set(IMGUI_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui) set(IMGUI_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui)
file(GLOB IMGUI_SOURCES ${IMGUI_PATH}/*.cpp) file(GLOB IMGUI_SOURCES ${IMGUI_PATH}/*.cpp)
add_library("ImGui" STATIC ${IMGUI_SOURCES}) set(IMGUI_BACKEND_SOURCES
target_include_directories("ImGui" PUBLIC ${IMGUI_PATH}) ${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp
${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
)
add_library("ImGui" STATIC ${IMGUI_SOURCES} ${IMGUI_BACKEND_SOURCES})
target_include_directories("ImGui" PUBLIC ${IMGUI_PATH} ${IMGUI_PATH}/backends)
target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR}) target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR})
target_compile_definitions(ImGui PUBLIC target_compile_definitions(ImGui PUBLIC
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1) IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
set(IMGUI_FILE_DLG_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiFileDialog) # Set up ImGui Test Engine sources and target conditionally
file(GLOB IMGUI_FILE_DLG_SOURCES ${IMGUI_FILE_DLG_PATH}/*.cpp) if(YAZE_ENABLE_UI_TESTS)
add_library("ImGuiFileDialog" STATIC ${IMGUI_FILE_DLG_SOURCES}) set(IMGUI_TEST_ENGINE_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine/imgui_test_engine)
target_include_directories(ImGuiFileDialog PUBLIC ${IMGUI_PATH} ${CMAKE_SOURCE_DIR}/src/lib) file(GLOB IMGUI_TEST_ENGINE_SOURCES ${IMGUI_TEST_ENGINE_PATH}/*.cpp)
add_library("ImGuiTestEngine" STATIC ${IMGUI_TEST_ENGINE_SOURCES})
target_include_directories(ImGuiTestEngine PUBLIC ${IMGUI_PATH} ${CMAKE_SOURCE_DIR}/src/lib)
target_link_libraries(ImGuiTestEngine PUBLIC ImGui)
set(IMGUI_COLOR_TEXT_EDIT_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiColorTextEdit) # Enable test engine definitions only when UI tests are enabled
file(GLOB IMGUI_COLOR_TEXT_EDIT_SOURCES ${IMGUI_COLOR_TEXT_EDIT_PATH}/*.cpp) target_compile_definitions(ImGuiTestEngine PUBLIC
add_library("ImGuiColorTextEdit" STATIC ${IMGUI_COLOR_TEXT_EDIT_SOURCES}) IMGUI_ENABLE_TEST_ENGINE=1
target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH} ${CMAKE_SOURCE_DIR}/src/lib) IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1)
set(IMGUI_TEST_ENGINE_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine/imgui_test_engine) # Also define for targets that link to ImGuiTestEngine
file(GLOB IMGUI_TEST_ENGINE_SOURCES ${IMGUI_TEST_ENGINE_PATH}/*.cpp) set(IMGUI_TEST_ENGINE_DEFINITIONS
add_library("ImGuiTestEngine" STATIC ${IMGUI_TEST_ENGINE_SOURCES}) IMGUI_ENABLE_TEST_ENGINE=1
target_include_directories(ImGuiTestEngine PUBLIC ${IMGUI_PATH} ${CMAKE_SOURCE_DIR}/src/lib) IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1)
target_link_libraries(ImGuiTestEngine PUBLIC ImGui)
# Make ImGuiTestEngine target available
set(IMGUI_TEST_ENGINE_TARGET ImGuiTestEngine)
else()
# Create empty variables when UI tests are disabled
set(IMGUI_TEST_ENGINE_SOURCES "")
set(IMGUI_TEST_ENGINE_TARGET "")
set(IMGUI_TEST_ENGINE_DEFINITIONS "")
endif()
set( set(
IMGUI_SRC IMGUI_SRC
@@ -32,9 +48,5 @@ set(
${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp ${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp ${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp
${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp ${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
${IMGUI_FILE_DLG_PATH}/ImGuiFileDialog.cpp
${IMGUI_COLOR_TEXT_EDIT_PATH}/TextEditor.cpp
) )
# For integration test
add_definitions("-DIMGUI_ENABLE_TEST_ENGINE -DIMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1")

222
cmake/packaging.cmake Normal file
View File

@@ -0,0 +1,222 @@
# Modern packaging configuration for Yaze
# Supports Windows (NSIS), macOS (DMG), and Linux (DEB/RPM)
include(InstallRequiredSystemLibraries)
# Basic package information
set(CPACK_PACKAGE_NAME "yaze")
set(CPACK_PACKAGE_VENDOR "scawful")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Yet Another Zelda3 Editor")
set(CPACK_PACKAGE_DESCRIPTION "A comprehensive editor for The Legend of Zelda: A Link to the Past ROM hacking")
set(CPACK_PACKAGE_VERSION_MAJOR ${YAZE_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${YAZE_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${YAZE_VERSION_PATCH})
set(CPACK_PACKAGE_VERSION "${YAZE_VERSION_MAJOR}.${YAZE_VERSION_MINOR}.${YAZE_VERSION_PATCH}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "Yaze")
set(CPACK_PACKAGE_CONTACT "scawful@github.com")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/scawful/yaze")
# Resource files
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")
# Package icon
if(EXISTS "${CMAKE_SOURCE_DIR}/assets/yaze.png")
set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/assets/yaze.png")
endif()
# Platform-specific configuration
if(WIN32)
# Windows packaging configuration (conditional based on environment)
if(DEFINED ENV{GITHUB_ACTIONS})
# CI/CD build - use only ZIP (NSIS not available)
set(CPACK_GENERATOR "ZIP")
else()
# Local build - use both NSIS installer and ZIP
set(CPACK_GENERATOR "NSIS;ZIP")
endif()
# NSIS-specific configuration (only for local builds with NSIS available)
if(NOT DEFINED ENV{GITHUB_ACTIONS})
set(CPACK_NSIS_DISPLAY_NAME "Yaze - Zelda3 Editor")
set(CPACK_NSIS_PACKAGE_NAME "Yaze")
set(CPACK_NSIS_CONTACT "scawful@github.com")
set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/scawful/yaze")
set(CPACK_NSIS_HELP_LINK "https://github.com/scawful/yaze/issues")
set(CPACK_NSIS_MENU_LINKS
"bin/yaze.exe" "Yaze Editor"
"https://github.com/scawful/yaze" "Yaze Homepage"
)
set(CPACK_NSIS_CREATE_ICONS_EXTRA
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Yaze.lnk' '$INSTDIR\\\\bin\\\\yaze.exe'"
"CreateShortCut '$DESKTOP\\\\Yaze.lnk' '$INSTDIR\\\\bin\\\\yaze.exe'"
)
set(CPACK_NSIS_DELETE_ICONS_EXTRA
"Delete '$SMPROGRAMS\\\\$START_MENU\\\\Yaze.lnk'"
"Delete '$DESKTOP\\\\Yaze.lnk'"
)
endif()
# Windows architecture detection
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
if(DEFINED ENV{GITHUB_ACTIONS})
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-windows-x64")
else()
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-win64")
endif()
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
else()
if(DEFINED ENV{GITHUB_ACTIONS})
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-windows-x86")
else()
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-win32")
endif()
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
endif()
elseif(APPLE)
# macOS DMG configuration
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_DMG_VOLUME_NAME "Yaze ${CPACK_PACKAGE_VERSION}")
set(CPACK_DMG_FORMAT "UDZO")
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-macos")
# macOS app bundle configuration
if(EXISTS "${CMAKE_SOURCE_DIR}/assets/dmg_background.png")
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/assets/dmg_background.png")
endif()
if(EXISTS "${CMAKE_SOURCE_DIR}/cmake/dmg_setup.scpt")
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_SOURCE_DIR}/cmake/dmg_setup.scpt")
endif()
elseif(UNIX)
# Linux DEB/RPM configuration
set(CPACK_GENERATOR "DEB;RPM;TGZ")
# DEB package configuration
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "scawful <scawful@github.com>")
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"libsdl2-2.0-0, libpng16-16, libgl1-mesa-glx, libabsl20210324")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "git")
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "asar")
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
# RPM package configuration
set(CPACK_RPM_PACKAGE_SUMMARY "Zelda3 ROM Editor")
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_REQUIRES
"SDL2 >= 2.0.0, libpng >= 1.6.0, mesa-libGL, abseil-cpp")
set(CPACK_RPM_PACKAGE_SUGGESTS "asar")
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
# Architecture detection
execute_process(
COMMAND uname -m
OUTPUT_VARIABLE CPACK_SYSTEM_ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-linux-${CPACK_SYSTEM_ARCH}")
endif()
# Component configuration for advanced packaging
set(CPACK_COMPONENTS_ALL applications libraries headers documentation)
set(CPACK_COMPONENT_APPLICATIONS_DISPLAY_NAME "Yaze Application")
set(CPACK_COMPONENT_APPLICATIONS_DESCRIPTION "Main Yaze editor application")
set(CPACK_COMPONENT_APPLICATIONS_REQUIRED TRUE)
set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Development Libraries")
set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION "Yaze development libraries")
set(CPACK_COMPONENT_LIBRARIES_REQUIRED FALSE)
set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "Development Headers")
set(CPACK_COMPONENT_HEADERS_DESCRIPTION "Header files for Yaze development")
set(CPACK_COMPONENT_HEADERS_REQUIRED FALSE)
set(CPACK_COMPONENT_HEADERS_DEPENDS libraries)
set(CPACK_COMPONENT_DOCUMENTATION_DISPLAY_NAME "Documentation")
set(CPACK_COMPONENT_DOCUMENTATION_DESCRIPTION "User and developer documentation")
set(CPACK_COMPONENT_DOCUMENTATION_REQUIRED FALSE)
# Installation components
if(APPLE)
install(TARGETS yaze
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT applications
)
else()
install(TARGETS yaze
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT applications
)
endif()
# Install assets
install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets/
DESTINATION ${CMAKE_INSTALL_DATADIR}/yaze/assets
COMPONENT applications
PATTERN "*.png"
PATTERN "*.ttf"
PATTERN "*.asm"
PATTERN "*.zeml"
)
# Install documentation
install(FILES
${CMAKE_SOURCE_DIR}/README.md
${CMAKE_SOURCE_DIR}/LICENSE
DESTINATION ${CMAKE_INSTALL_DOCDIR}
COMPONENT documentation
)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/docs/
DESTINATION ${CMAKE_INSTALL_DOCDIR}
COMPONENT documentation
PATTERN "*.md"
PATTERN "*.html"
)
# Install headers and libraries if building library components
if(YAZE_INSTALL_LIB)
install(TARGETS yaze_c
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT libraries
)
install(FILES ${CMAKE_SOURCE_DIR}/incl/yaze.h ${CMAKE_SOURCE_DIR}/incl/zelda.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/yaze
COMPONENT headers
)
endif()
# Desktop integration for Linux
if(UNIX AND NOT APPLE)
# Desktop file
configure_file(
${CMAKE_SOURCE_DIR}/cmake/yaze.desktop.in
${CMAKE_BINARY_DIR}/yaze.desktop
@ONLY
)
install(FILES ${CMAKE_BINARY_DIR}/yaze.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
COMPONENT applications
)
# Icon
if(EXISTS "${CMAKE_SOURCE_DIR}/assets/yaze.png")
install(FILES ${CMAKE_SOURCE_DIR}/assets/yaze.png
DESTINATION ${CMAKE_INSTALL_DATADIR}/pixmaps
RENAME yaze.png
COMPONENT applications
)
endif()
endif()
# Include CPack
include(CPack)

View File

@@ -1,27 +1,42 @@
# SDL2 # SDL2
if (UNIX OR MINGW) if (UNIX OR MINGW OR WIN32)
add_subdirectory(src/lib/SDL) add_subdirectory(src/lib/SDL)
# When using bundled SDL, use the static target and set include directories
set(SDL_TARGETS SDL2-static)
set(SDL2_INCLUDE_DIR
${CMAKE_SOURCE_DIR}/src/lib/SDL/include
${CMAKE_BINARY_DIR}/src/lib/SDL/include
${CMAKE_BINARY_DIR}/src/lib/SDL/include-config-${CMAKE_BUILD_TYPE}
)
# Also set for consistency with bundled SDL
set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})
else() else()
find_package(SDL2) find_package(SDL2)
endif() # When using system SDL, use the imported targets
set(SDL_TARGETS SDL2::SDL2)
set(SDL_TARGETS SDL2::SDL2) if(WIN32)
if(WIN32 OR MINGW)
list(PREPEND SDL_TARGETS SDL2::SDL2main ws2_32) list(PREPEND SDL_TARGETS SDL2::SDL2main ws2_32)
add_definitions(-DSDL_MAIN_HANDLED) add_definitions("-DSDL_MAIN_HANDLED")
endif()
endif() endif()
# libpng # libpng and ZLIB dependencies
if (MINGW) if(WIN32 AND NOT YAZE_MINIMAL_BUILD)
set(ZLIB_ROOT ${CMAKE_SOURCE_DIR}/build-windows/src/lib/zlib) # Use vcpkg on Windows
set(ZLIB_LIBRARY ${CMAKE_SOURCE_DIR}/build-windows/lib/libzlib.dll.a) find_package(ZLIB REQUIRED)
include_directories(${CMAKE_SOURCE_DIR}/src/lib/zlib ${CMAKE_SOURCE_DIR}/src/lib/libpng) find_package(PNG REQUIRED)
set(ZLIB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/lib/zlib) elseif(YAZE_MINIMAL_BUILD)
set(YAZE_BUILD_PYTHON OFF) # For CI builds, try to find but don't require
set(YAZE_BUILD_EXTENSIONS OFF) find_package(ZLIB QUIET)
add_subdirectory(src/lib/zlib) find_package(PNG QUIET)
add_subdirectory(src/lib/libpng) if(NOT ZLIB_FOUND OR NOT PNG_FOUND)
message(STATUS "PNG/ZLIB not found in minimal build, some features may be disabled")
set(PNG_FOUND FALSE)
set(PNG_LIBRARIES "")
set(PNG_INCLUDE_DIRS "")
endif()
else() else()
# Regular builds require these dependencies
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
endif() endif()

6
cmake/vcpkg.cmake Normal file
View File

@@ -0,0 +1,6 @@
add_definitions("-DMICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS=0")
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)

13
cmake/yaze.desktop.in Normal file
View File

@@ -0,0 +1,13 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=Yaze
Comment=Yet Another Zelda3 Editor
Comment[en]=ROM editor for The Legend of Zelda: A Link to the Past
Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/yaze
Icon=yaze
Terminal=false
Categories=Game;Development;
Keywords=zelda;snes;rom;editor;hacking;
StartupNotify=true
MimeType=application/x-snes-rom;application/x-sfc;application/x-smc;

View File

@@ -0,0 +1,56 @@
# Getting Started
This software allows you to modify "The Legend of Zelda: A Link to the Past" (US or JP) ROMs. Built for compatibility with ZScream projects and designed to be cross-platform.
## Quick Start
1. **Download** the latest release for your platform
2. **Load ROM** via File > Open ROM
3. **Select Editor** from the toolbar (Overworld, Dungeon, Graphics, etc.)
4. **Make Changes** and save your project
## General Tips
- **Experiment Flags**: Enable/disable features in File > Options > Experiment Flags
- **Backup Files**: Enabled by default - each save creates a timestamped backup
- **Extensions**: Load custom tools via the Extensions menu (C library and Python module support)
## Supported Features
| Feature | Status | Details |
|---------|--------|---------|
| Overworld Maps | ✅ Complete | Edit and save tile32 data |
| OW Map Properties | ✅ Complete | Edit and save map properties |
| OW Entrances | ✅ Complete | Edit and save entrance data |
| OW Exits | ✅ Complete | Edit and save exit data |
| OW Sprites | 🔄 In Progress | Edit sprite positions, add/remove sprites |
| Dungeon Editor | 🔄 In Progress | View room metadata and edit room data |
| Palette Editor | 🔄 In Progress | Edit and save palettes, palette groups |
| Graphics Sheets | 🔄 In Progress | Edit and save graphics sheets |
| Graphics Groups | ✅ Complete | Edit and save graphics groups |
| Hex Editor | ✅ Complete | View and edit ROM data in hex |
| Asar Patching | ✅ Complete | Apply Asar 65816 assembly patches to ROM |
## Command Line Interface
The `z3ed` CLI tool provides ROM operations:
```bash
# Apply Asar assembly patch
z3ed asar patch.asm --rom=zelda3.sfc
# Extract symbols from assembly
z3ed extract patch.asm
# Validate assembly syntax
z3ed validate patch.asm
# Launch interactive TUI
z3ed --tui
```
## Extending Functionality
YAZE provides a pure C library interface and Python module for building extensions and custom sprites without assembly. Load these under the Extensions menu.
This feature is still in development and not fully documented yet.

View File

@@ -0,0 +1,153 @@
# Build Instructions
YAZE uses CMake 3.16+ with modern target-based configuration. For VSCode users, install the CMake extensions:
- https://marketplace.visualstudio.com/items?itemName=twxs.cmake
- https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
## Quick Start
### macOS (Apple Silicon)
```bash
cmake --preset debug
cmake --build build
```
### Linux / Windows
```bash
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
```
### Minimal Build
```bash
cmake -B build -DYAZE_MINIMAL_BUILD=ON
cmake --build build
```
## Dependencies
### Required
- CMake 3.16+
- C++23 compiler (GCC 13+, Clang 16+, MSVC 2019+)
- Git with submodule support
### Bundled Libraries
- SDL2, ImGui, Abseil, Asar, GoogleTest
- Native File Dialog Extended (NFD)
- All dependencies included in repository
## Platform Setup
### macOS
```bash
# Install Xcode Command Line Tools
xcode-select --install
# Optional: Install Homebrew dependencies (auto-detected)
brew install cmake pkg-config
```
### Linux (Ubuntu/Debian)
```bash
sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build pkg-config \
libgtk-3-dev libdbus-1-dev
```
### Windows
**Option 1 - Minimal (Recommended for CI):**
- Visual Studio 2019+ with C++ CMake tools
- No additional dependencies needed (all bundled)
**Option 2 - Full Development:**
- Install vcpkg and dependencies from `vcpkg.json`
## Build Targets
### Applications
- **yaze**: Main GUI editor application
- **z3ed**: Command-line interface tool
### Libraries
- **yaze_c**: C API library for extensions
- **asar-static**: 65816 assembler library
### Development (Debug Builds Only)
- **yaze_emu**: Standalone SNES emulator
- **yaze_test**: Comprehensive test suite
## Build Configurations
### Debug (Full Features)
```bash
cmake --preset debug # macOS
# OR
cmake -B build -DCMAKE_BUILD_TYPE=Debug # All platforms
```
**Includes**: NFD, ImGuiTestEngine, PNG support, emulator, all tools
### Minimal (CI/Fast Builds)
```bash
cmake -B build -DYAZE_MINIMAL_BUILD=ON
```
**Excludes**: Emulator, CLI tools, UI tests, optional dependencies
### Release
```bash
cmake --preset release # macOS
# OR
cmake -B build -DCMAKE_BUILD_TYPE=Release # All platforms
```
## IDE Integration
### VS Code
1. Install CMake Tools extension
2. Open project, select "Debug" preset
3. Language server uses `compile_commands.json` automatically
### CLion
- Opens CMake projects directly
- Select Debug configuration
### Xcode (macOS)
```bash
cmake --preset debug -G Xcode
open build/yaze.xcodeproj
```
## Features by Build Type
| Feature | Debug | Release | Minimal (CI) |
|---------|-------|---------|--------------|
| GUI Editor | ✅ | ✅ | ✅ |
| Native File Dialogs | ✅ | ✅ | ❌ |
| PNG Support | ✅ | ✅ | ❌ |
| Emulator | ✅ | ✅ | ❌ |
| CLI Tools | ✅ | ✅ | ❌ |
| Test Suite | ✅ | ❌ | ✅ (limited) |
| UI Testing | ✅ | ❌ | ❌ |
## Troubleshooting
### Architecture Errors (macOS)
```bash
# Clean and use ARM64-only preset
rm -rf build
cmake --preset debug # Uses arm64 only
```
### Missing Headers (Language Server)
```bash
# Regenerate compile commands
cmake --preset debug
cp build/compile_commands.json .
# Restart VS Code
```
### CI Build Failures
Use minimal build configuration that matches CI:
```bash
cmake -B build -DYAZE_MINIMAL_BUILD=ON -DYAZE_ENABLE_UI_TESTS=OFF
cmake --build build
```

141
docs/03-asar-integration.md Normal file
View File

@@ -0,0 +1,141 @@
# Asar 65816 Assembler Integration
Complete cross-platform ROM patching with assembly code support, symbol extraction, and validation.
## Quick Examples
### Command Line
```bash
# Apply assembly patch to ROM
z3ed asar my_patch.asm --rom=zelda3.sfc
# Extract symbols without patching
z3ed extract my_patch.asm
# Validate assembly syntax
z3ed validate my_patch.asm
```
### C++ API
```cpp
#include "app/core/asar_wrapper.h"
yaze::app::core::AsarWrapper wrapper;
wrapper.Initialize();
// Apply patch to ROM
auto result = wrapper.ApplyPatch("patch.asm", rom_data);
if (result.ok() && result->success) {
for (const auto& symbol : result->symbols) {
std::cout << symbol.name << " @ $" << std::hex << symbol.address << std::endl;
}
}
```
## Assembly Patch Examples
### Basic Hook
```assembly
org $008000
custom_hook:
sei ; Disable interrupts
rep #$30 ; 16-bit A and X/Y
; Your custom code
lda #$1234
sta $7E0000
rts
custom_data:
db "YAZE", $00
dw $1234, $5678
```
### Advanced Features
```assembly
!player_health = $7EF36C
!custom_ram = $7E2000
macro save_context()
pha : phx : phy
endmacro
org $008000
advanced_hook:
%save_context()
sep #$20
lda #$A0 ; Full health
sta !player_health
%save_context()
rtl
```
## API Reference
### AsarWrapper Class
```cpp
class AsarWrapper {
public:
absl::Status Initialize();
absl::StatusOr<AsarPatchResult> ApplyPatch(
const std::string& patch_path,
std::vector<uint8_t>& rom_data);
absl::StatusOr<std::vector<AsarSymbol>> ExtractSymbols(
const std::string& asm_path);
absl::Status ValidateAssembly(const std::string& asm_path);
};
```
### Data Structures
```cpp
struct AsarSymbol {
std::string name; // Symbol name
uint32_t address; // Memory address
std::string opcode; // Associated opcode
std::string file; // Source file
int line; // Line number
};
struct AsarPatchResult {
bool success; // Whether patch succeeded
std::vector<std::string> errors; // Error messages
std::vector<AsarSymbol> symbols; // Extracted symbols
uint32_t rom_size; // Final ROM size
};
```
## Testing
### ROM-Dependent Tests
```cpp
YAZE_ROM_TEST(AsarIntegration, RealRomPatching) {
auto rom_data = TestRomManager::LoadTestRom();
AsarWrapper wrapper;
wrapper.Initialize();
auto result = wrapper.ApplyPatch("test.asm", rom_data);
EXPECT_TRUE(result.ok());
}
```
ROM tests are automatically skipped in CI with `--label-exclude ROM_DEPENDENT`.
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| `Unknown command` | Invalid opcode | Check 65816 instruction reference |
| `Label not found` | Undefined label | Define the label or check spelling |
| `Invalid hex value` | Bad hex format | Use `$1234` format |
| `Buffer too small` | ROM needs expansion | Check if ROM needs to be larger |
## Development Workflow
1. **Write assembly patch**
2. **Validate syntax**: `z3ed validate patch.asm`
3. **Extract symbols**: `z3ed extract patch.asm`
4. **Apply to test ROM**: `z3ed asar patch.asm --rom=test.sfc`
5. **Test in emulator**

208
docs/04-api-reference.md Normal file
View File

@@ -0,0 +1,208 @@
# API Reference
Comprehensive reference for the YAZE C API and C++ interfaces.
## C API (`incl/yaze.h`, `incl/zelda.h`)
### Core Library Functions
```c
// Initialization
yaze_status yaze_library_init(void);
void yaze_library_shutdown(void);
// Version management
const char* yaze_get_version_string(void);
int yaze_get_version_number(void);
bool yaze_check_version_compatibility(const char* expected_version);
// Status utilities
const char* yaze_status_to_string(yaze_status status);
```
### ROM Operations
```c
// ROM loading and management
zelda3_rom* yaze_load_rom(const char* filename);
void yaze_unload_rom(zelda3_rom* rom);
yaze_status yaze_save_rom(zelda3_rom* rom, const char* filename);
bool yaze_is_rom_modified(const zelda3_rom* rom);
```
### Graphics Operations
```c
// SNES color management
snes_color yaze_rgb_to_snes_color(uint8_t r, uint8_t g, uint8_t b);
void yaze_snes_color_to_rgb(snes_color color, uint8_t* r, uint8_t* g, uint8_t* b);
// Bitmap operations
yaze_bitmap* yaze_create_bitmap(int width, int height, uint8_t bpp);
void yaze_free_bitmap(yaze_bitmap* bitmap);
```
### Palette System
```c
// Palette creation and management
snes_palette* yaze_create_palette(uint8_t id, uint8_t size);
void yaze_free_palette(snes_palette* palette);
snes_palette* yaze_load_palette_from_rom(const zelda3_rom* rom, uint8_t palette_id);
```
### Message System
```c
// Message handling
zelda3_message* yaze_load_message(const zelda3_rom* rom, uint16_t message_id);
void yaze_free_message(zelda3_message* message);
yaze_status yaze_save_message(zelda3_rom* rom, const zelda3_message* message);
```
## C++ API
### AsarWrapper (`src/app/core/asar_wrapper.h`)
```cpp
namespace yaze::app::core {
class AsarWrapper {
public:
// Initialization
absl::Status Initialize();
void Shutdown();
bool IsInitialized() const;
// Core functionality
absl::StatusOr<AsarPatchResult> ApplyPatch(
const std::string& patch_path,
std::vector<uint8_t>& rom_data,
const std::vector<std::string>& include_paths = {});
absl::StatusOr<std::vector<AsarSymbol>> ExtractSymbols(
const std::string& asm_path,
const std::vector<std::string>& include_paths = {});
// Symbol management
std::optional<AsarSymbol> FindSymbol(const std::string& name);
std::vector<AsarSymbol> GetSymbolsAtAddress(uint32_t address);
std::map<std::string, AsarSymbol> GetSymbolTable();
// Utility functions
absl::Status ValidateAssembly(const std::string& asm_path);
std::string GetVersion();
void Reset();
};
}
```
### Data Structures
#### ROM Version Support
```c
typedef enum zelda3_version {
ZELDA3_VERSION_US = 1,
ZELDA3_VERSION_JP = 2,
ZELDA3_VERSION_SD = 3,
ZELDA3_VERSION_RANDO = 4,
// Legacy aliases maintained for compatibility
US = ZELDA3_VERSION_US,
JP = ZELDA3_VERSION_JP,
SD = ZELDA3_VERSION_SD,
RANDO = ZELDA3_VERSION_RANDO,
} zelda3_version;
```
#### SNES Graphics
```c
typedef struct snes_color {
uint16_t raw; // Raw 15-bit SNES color
uint8_t red; // Red component (0-31)
uint8_t green; // Green component (0-31)
uint8_t blue; // Blue component (0-31)
} snes_color;
typedef struct snes_palette {
uint8_t id; // Palette ID
uint8_t size; // Number of colors
snes_color* colors; // Color array
} snes_palette;
```
#### Message System
```c
typedef struct zelda3_message {
uint16_t id; // Message ID (0-65535)
uint32_t rom_address; // Address in ROM
uint16_t length; // Length in bytes
uint8_t* raw_data; // Raw ROM data
char* parsed_text; // Decoded UTF-8 text
bool is_compressed; // Compression flag
uint8_t encoding_type; // Encoding type
} zelda3_message;
```
## Error Handling
### Status Codes
```c
typedef enum yaze_status {
YAZE_OK = 0, // Success
YAZE_ERROR_UNKNOWN = -1, // Unknown error
YAZE_ERROR_INVALID_ARG = 1, // Invalid argument
YAZE_ERROR_FILE_NOT_FOUND = 2, // File not found
YAZE_ERROR_MEMORY = 3, // Memory allocation failed
YAZE_ERROR_IO = 4, // I/O operation failed
YAZE_ERROR_CORRUPTION = 5, // Data corruption detected
YAZE_ERROR_NOT_INITIALIZED = 6, // Component not initialized
} yaze_status;
```
### Error Handling Pattern
```c
yaze_status status = yaze_library_init();
if (status != YAZE_OK) {
printf("Failed to initialize YAZE: %s\n", yaze_status_to_string(status));
return 1;
}
zelda3_rom* rom = yaze_load_rom("zelda3.sfc");
if (rom == nullptr) {
printf("Failed to load ROM file\n");
return 1;
}
// Use ROM...
yaze_unload_rom(rom);
yaze_library_shutdown();
```
## Extension System
### Plugin Architecture
```c
typedef struct yaze_extension {
const char* name; // Extension name
const char* version; // Version string
const char* description; // Description
const char* author; // Author
int api_version; // Required API version
yaze_status (*initialize)(yaze_editor_context* context);
void (*cleanup)(void);
uint32_t (*get_capabilities)(void);
} yaze_extension;
```
### Capability Flags
```c
#define YAZE_EXT_CAP_ROM_EDITING 0x0001 // ROM modification
#define YAZE_EXT_CAP_GRAPHICS 0x0002 // Graphics operations
#define YAZE_EXT_CAP_AUDIO 0x0004 // Audio processing
#define YAZE_EXT_CAP_SCRIPTING 0x0008 // Scripting support
#define YAZE_EXT_CAP_IMPORT_EXPORT 0x0010 // Data import/export
```
## Backward Compatibility
All existing code continues to work without modification due to:
- Legacy enum aliases (`US`, `JP`, `SD`, `RANDO`)
- Original struct field names maintained
- Duplicate field definitions for old/new naming conventions
- Typedef aliases for renamed types

173
docs/A1-testing-guide.md Normal file
View File

@@ -0,0 +1,173 @@
# Testing Guide
Comprehensive testing framework with efficient CI/CD integration and ROM-dependent test separation.
## Test Categories
### Stable Tests (STABLE)
**Always run in CI/CD - Required for releases**
- **AsarWrapperTest**: Core Asar functionality tests
- **SnesTileTest**: SNES tile format handling
- **CompressionTest**: Data compression/decompression
- **SnesPaletteTest**: SNES palette operations
- **HexTest**: Hexadecimal utilities
- **AsarIntegrationTest**: Asar integration without ROM dependencies
**Characteristics:**
- Fast execution (< 30 seconds total)
- No external dependencies (ROMs, complex setup)
- High reliability and deterministic results
### ROM-Dependent Tests (ROM_DEPENDENT)
**Only run in development with available ROM files**
- **AsarRomIntegrationTest**: Real ROM patching and symbol extraction
- **ROM-based integration tests**: Tests requiring actual game ROM files
**Characteristics:**
- Require specific ROM files to be present
- Test real-world functionality
- Automatically skipped in CI if ROM files unavailable
### Experimental Tests (EXPERIMENTAL)
**Run separately, allowed to fail**
- **CpuTest**: 65816 CPU emulation tests
- **Spc700Test**: SPC700 audio processor tests
- **ApuTest**: Audio Processing Unit tests
- **PpuTest**: Picture Processing Unit tests
**Characteristics:**
- May be unstable due to emulation complexity
- Test advanced/experimental features
- Allowed to fail without blocking releases
## Command Line Usage
```bash
# Run only stable tests (release-ready)
ctest --test-dir build --label-regex "STABLE"
# Run experimental tests (allowed to fail)
ctest --test-dir build --label-regex "EXPERIMENTAL"
# Run Asar-specific tests
ctest --test-dir build -R "*Asar*"
# Run tests excluding ROM-dependent ones
ctest --test-dir build --label-exclude "ROM_DEPENDENT"
# Run with specific preset
ctest --preset stable
ctest --preset experimental
```
## CMake Presets
```bash
# Development workflow
cmake --preset dev
cmake --build --preset dev
ctest --preset dev
# CI workflow
cmake --preset ci
cmake --build --preset ci
ctest --preset ci
# Release workflow
cmake --preset release
cmake --build --preset release
ctest --preset stable
```
## Writing Tests
### Stable Tests
```cpp
TEST(SnesTileTest, UnpackBppTile) {
std::vector<uint8_t> tile_data = {0xAA, 0x55, 0xAA, 0x55};
std::vector<uint8_t> result = UnpackBppTile(tile_data, 2);
EXPECT_EQ(result.size(), 64);
// Test specific pixel values...
}
```
### ROM-Dependent Tests
```cpp
YAZE_ROM_TEST(AsarIntegration, RealRomPatching) {
auto rom_data = TestRomManager::LoadTestRom();
if (!rom_data.has_value()) {
GTEST_SKIP() << "ROM file not available";
}
AsarWrapper wrapper;
wrapper.Initialize();
auto result = wrapper.ApplyPatch("test.asm", *rom_data);
EXPECT_TRUE(result.ok());
}
```
### Experimental Tests
```cpp
TEST(CpuTest, InstructionExecution) {
// Complex emulation tests
// May be timing-sensitive or platform-dependent
}
```
## CI/CD Integration
### GitHub Actions
```yaml
# Main CI pipeline
- name: Run Stable Tests
run: ctest --label-regex "STABLE"
# Experimental tests (allowed to fail)
- name: Run Experimental Tests
run: ctest --label-regex "EXPERIMENTAL"
continue-on-error: true
```
### Test Execution Strategy
1. **Stable tests run first** - Quick feedback for developers
2. **Experimental tests run in parallel** - Don't block on unstable tests
3. **ROM tests skipped** - No dependency on external files
4. **Selective test execution** - Only run relevant tests for changes
## Test Development Guidelines
### Writing Stable Tests
- **Fast execution**: Aim for < 1 second per test
- **No external dependencies**: Self-contained test data
- **Deterministic**: Same results every run
- **Core functionality**: Test essential features only
### Writing ROM-Dependent Tests
- **Use TestRomManager**: Proper ROM file handling
- **Graceful skipping**: Skip if ROM not available
- **Real-world scenarios**: Test with actual game data
- **Label appropriately**: Always include ROM_DEPENDENT label
### Writing Experimental Tests
- **Complex scenarios**: Multi-component integration
- **Advanced features**: Emulation, complex algorithms
- **Performance tests**: May vary by system
- **GUI components**: May require display context
## Performance and Maintenance
### Regular Review
- **Monthly review** of experimental test failures
- **Promote stable experimental tests** to stable category
- **Deprecate obsolete tests** that no longer provide value
- **Update test categorization** as features mature
### Performance Monitoring
- **Track test execution times** for CI efficiency
- **Identify slow tests** for optimization or recategorization
- **Monitor CI resource usage** and adjust parallelism
- **Benchmark critical path tests** for performance regression

239
docs/B1-contributing.md Normal file
View File

@@ -0,0 +1,239 @@
# Contributing
Guidelines for contributing to the YAZE project.
## Development Setup
### Prerequisites
- **CMake 3.16+**: Modern build system
- **C++23 Compiler**: GCC 13+, Clang 16+, MSVC 2022 17.8+
- **Git**: Version control with submodules
### Quick Start
```bash
# Clone with submodules
git clone --recursive https://github.com/scawful/yaze.git
cd yaze
# Build with presets
cmake --preset dev
cmake --build --preset dev
# Run tests
ctest --preset stable
```
## Code Style
### C++ Standards
- **C++23**: Use modern language features
- **Google C++ Style**: Follow Google C++ style guide
- **Naming**: Use descriptive names, avoid abbreviations
### File Organization
```cpp
// Header guards
#pragma once
// Includes (system, third-party, local)
#include <vector>
#include "absl/status/status.h"
#include "app/core/asar_wrapper.h"
// Namespace usage
namespace yaze::app::editor {
class ExampleClass {
public:
// Public interface
absl::Status Initialize();
private:
// Private implementation
std::vector<uint8_t> data_;
};
}
```
### Error Handling
```cpp
// Use absl::Status for error handling
absl::Status LoadRom(const std::string& filename) {
if (filename.empty()) {
return absl::InvalidArgumentError("Filename cannot be empty");
}
// ... implementation
return absl::OkStatus();
}
// Use absl::StatusOr for operations that return values
absl::StatusOr<std::vector<uint8_t>> ReadFile(const std::string& filename);
```
## Testing Requirements
### Test Categories
- **Stable Tests**: Fast, reliable, no external dependencies
- **ROM-Dependent Tests**: Require ROM files, skip in CI
- **Experimental Tests**: Complex, may be unstable
### Writing Tests
```cpp
// Stable test example
TEST(SnesTileTest, UnpackBppTile) {
std::vector<uint8_t> tile_data = {0xAA, 0x55};
auto result = UnpackBppTile(tile_data, 2);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result->size(), 64);
}
// ROM-dependent test example
YAZE_ROM_TEST(AsarIntegration, RealRomPatching) {
auto rom_data = TestRomManager::LoadTestRom();
if (!rom_data.has_value()) {
GTEST_SKIP() << "ROM file not available";
}
// ... test implementation
}
```
### Test Execution
```bash
# Run stable tests (required)
ctest --label-regex "STABLE"
# Run experimental tests (optional)
ctest --label-regex "EXPERIMENTAL"
# Run specific test
ctest -R "AsarWrapperTest"
```
## Pull Request Process
### Before Submitting
1. **Run tests**: Ensure all stable tests pass
2. **Check formatting**: Use clang-format
3. **Update documentation**: Update relevant docs if needed
4. **Test on multiple platforms**: Verify cross-platform compatibility
### Pull Request Template
```markdown
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Stable tests pass
- [ ] Manual testing completed
- [ ] Cross-platform testing (if applicable)
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] Tests added/updated
```
## Development Workflow
### Branch Strategy
- **main**: Stable, release-ready code
- **feature/**: New features and enhancements
- **fix/**: Bug fixes
- **docs/**: Documentation updates
### Commit Messages
```
type(scope): brief description
Detailed explanation of changes, including:
- What was changed
- Why it was changed
- Any breaking changes
Fixes #issue_number
```
### Types
- **feat**: New features
- **fix**: Bug fixes
- **docs**: Documentation changes
- **style**: Code style changes
- **refactor**: Code refactoring
- **test**: Test additions/changes
- **chore**: Build/tooling changes
## Architecture Guidelines
### Component Design
- **Single Responsibility**: Each class should have one clear purpose
- **Dependency Injection**: Use dependency injection for testability
- **Interface Segregation**: Keep interfaces focused and minimal
### Memory Management
- **RAII**: Use RAII for resource management
- **Smart Pointers**: Prefer unique_ptr and shared_ptr
- **Avoid Raw Pointers**: Use smart pointers or references
### Performance
- **Profile Before Optimizing**: Measure before optimizing
- **Use Modern C++**: Leverage C++23 features for performance
- **Avoid Premature Optimization**: Focus on correctness first
## Documentation
### Code Documentation
- **Doxygen Comments**: Use Doxygen format for public APIs
- **Inline Comments**: Explain complex logic
- **README Updates**: Update relevant README files
### API Documentation
```cpp
/**
* @brief Applies an assembly patch to ROM data
* @param patch_path Path to the assembly patch file
* @param rom_data ROM data to patch (modified in place)
* @param include_paths Optional include paths for assembly
* @return Result containing success status and extracted symbols
* @throws std::invalid_argument if patch_path is empty
*/
absl::StatusOr<AsarPatchResult> ApplyPatch(
const std::string& patch_path,
std::vector<uint8_t>& rom_data,
const std::vector<std::string>& include_paths = {});
```
## Community Guidelines
### Communication
- **Be Respectful**: Treat all contributors with respect
- **Be Constructive**: Provide helpful feedback
- **Be Patient**: Remember that everyone is learning
### Getting Help
- **GitHub Issues**: Report bugs and request features
- **Discussions**: Ask questions and discuss ideas
- **Discord**: [Oracle of Secrets Discord](https://discord.gg/MBFkMTPEmk)
## Release Process
### Version Numbering
- **Semantic Versioning**: MAJOR.MINOR.PATCH
- **v0.3.0**: Current stable release
- **Pre-release**: v0.4.0-alpha, v0.4.0-beta
### Release Checklist
- [ ] All stable tests pass
- [ ] Documentation updated
- [ ] Changelog updated
- [ ] Cross-platform builds verified
- [ ] Release notes prepared

55
docs/B2-ci-cd-fixes.md Normal file
View File

@@ -0,0 +1,55 @@
# Platform Compatibility Improvements
Recent improvements to ensure YAZE works reliably across all supported platforms.
## Native File Dialog Support
YAZE now features native file dialogs on all platforms:
- **macOS**: Cocoa-based file selection with proper sandboxing support
- **Windows**: Windows Explorer integration with COM APIs
- **Linux**: GTK3 dialogs that match system appearance
- **Fallback**: Bespoke implementation when native dialogs unavailable
## Cross-Platform Build Reliability
Enhanced build system ensures consistent compilation:
- **Windows**: Resolved MSVC compatibility issues and dependency conflicts
- **Linux**: Fixed standard library compatibility for older distributions
- **macOS**: Proper support for both Intel and Apple Silicon architectures
- **All Platforms**: Bundled dependencies eliminate external package requirements
## Build Configuration Options
YAZE supports different build configurations for various use cases:
### Full Build (Development)
Includes all features: emulator, CLI tools, UI testing framework, and optional libraries.
### Minimal Build
Streamlined build excluding complex components, optimized for automated testing and CI environments.
## Implementation Details
The build system automatically detects platform capabilities and adjusts feature sets accordingly:
- **File Dialogs**: Uses native platform dialogs when available, with cross-platform fallbacks
- **Dependencies**: Bundles all required libraries to eliminate external package requirements
- **Testing**: Separates ROM-dependent tests from unit tests for CI compatibility
- **Architecture**: Supports both Intel and Apple Silicon on macOS without conflicts
## Platform-Specific Adaptations
### Windows
- Complete COM-based file dialog implementation
- MSVC compatibility improvements for modern C++ features
- Resource file handling for proper application integration
### macOS
- Cocoa-based native file dialogs with sandboxing support
- Universal binary support for Intel and Apple Silicon
- Proper bundle configuration for macOS applications
### Linux
- GTK3 integration for native file dialogs
- Package manager integration for system dependencies
- Support for multiple compiler toolchains (GCC, Clang)

View File

@@ -0,0 +1,55 @@
# Platform Compatibility Improvements
Recent improvements to ensure YAZE works reliably across all supported platforms.
## Native File Dialog Support
YAZE now features native file dialogs on all platforms:
- **macOS**: Cocoa-based file selection with proper sandboxing support
- **Windows**: Windows Explorer integration with COM APIs
- **Linux**: GTK3 dialogs that match system appearance
- **Fallback**: Bespoke implementation when native dialogs unavailable
## Cross-Platform Build Reliability
Enhanced build system ensures consistent compilation:
- **Windows**: Resolved MSVC compatibility issues and dependency conflicts
- **Linux**: Fixed standard library compatibility for older distributions
- **macOS**: Proper support for both Intel and Apple Silicon architectures
- **All Platforms**: Bundled dependencies eliminate external package requirements
## Build Configuration Options
YAZE supports different build configurations for various use cases:
### Full Build (Development)
Includes all features: emulator, CLI tools, UI testing framework, and optional libraries.
### Minimal Build
Streamlined build excluding complex components, optimized for automated testing and CI environments.
## Implementation Details
The build system automatically detects platform capabilities and adjusts feature sets accordingly:
- **File Dialogs**: Uses native platform dialogs when available, with cross-platform fallbacks
- **Dependencies**: Bundles all required libraries to eliminate external package requirements
- **Testing**: Separates ROM-dependent tests from unit tests for CI compatibility
- **Architecture**: Supports both Intel and Apple Silicon on macOS without conflicts
## Platform-Specific Adaptations
### Windows
- Complete COM-based file dialog implementation
- MSVC compatibility improvements for modern C++ features
- Resource file handling for proper application integration
### macOS
- Cocoa-based native file dialogs with sandboxing support
- Universal binary support for Intel and Apple Silicon
- Proper bundle configuration for macOS applications
### Linux
- GTK3 integration for native file dialogs
- Package manager integration for system dependencies
- Support for multiple compiler toolchains (GCC, Clang)

109
docs/B3-build-presets.md Normal file
View File

@@ -0,0 +1,109 @@
# Build Presets Guide
CMake presets for development workflow and architecture-specific builds.
## 🍎 macOS ARM64 Presets (Recommended for Apple Silicon)
### For Development Work:
```bash
# ARM64-only development build with ROM testing
cmake --preset macos-dev
cmake --build --preset macos-dev
# ARM64-only debug build
cmake --preset macos-debug
cmake --build --preset macos-debug
# ARM64-only release build
cmake --preset macos-release
cmake --build --preset macos-release
```
### For Distribution:
```bash
# Universal binary (ARM64 + x86_64) - use only when needed for distribution
cmake --preset macos-debug-universal
cmake --build --preset macos-debug-universal
cmake --preset macos-release-universal
cmake --build --preset macos-release-universal
```
## 🔧 Why This Fixes Architecture Errors
**Problem**: The original presets used `CMAKE_OSX_ARCHITECTURES: "x86_64;arm64"` which forced CMake to build universal binaries. This caused issues because:
- Dependencies like Abseil tried to use x86 SSE instructions (`-msse4.1`)
- These instructions don't exist on ARM64 processors
- Build failed with "unsupported option '-msse4.1' for target 'arm64-apple-darwin'"
**Solution**: The new ARM64-only presets use `CMAKE_OSX_ARCHITECTURES: "arm64"` which:
- ✅ Only targets ARM64 architecture
- ✅ Prevents x86-specific instruction usage
- ✅ Uses ARM64 optimizations instead
- ✅ Builds much faster (no cross-compilation)
## 📋 Available Presets
| Preset Name | Architecture | Purpose | ROM Tests |
|-------------|-------------|---------|-----------|
| `macos-dev` | ARM64 only | Development | ✅ Enabled |
| `macos-debug` | ARM64 only | Debug builds | ❌ Disabled |
| `macos-release` | ARM64 only | Release builds | ❌ Disabled |
| `macos-debug-universal` | Universal | Distribution debug | ❌ Disabled |
| `macos-release-universal` | Universal | Distribution release | ❌ Disabled |
## 🚀 Quick Start
For most development work on Apple Silicon:
```bash
# Clean build
rm -rf build
# Configure for ARM64 development
cmake --preset macos-dev
# Build
cmake --build --preset macos-dev
# Run tests
cmake --build --preset macos-dev --target test
```
## 🛠️ IDE Integration
### VS Code with CMake Tools:
1. Open Command Palette (`Cmd+Shift+P`)
2. Run "CMake: Select Configure Preset"
3. Choose `macos-dev` or `macos-debug`
### CLion:
1. Go to Settings → Build, Execution, Deployment → CMake
2. Add new profile with preset `macos-dev`
### Xcode:
```bash
# Generate Xcode project
cmake --preset macos-debug -G Xcode
open build/yaze.xcodeproj
```
## 🔍 Troubleshooting
If you still get architecture errors:
1. **Clean completely**: `rm -rf build`
2. **Check preset**: Ensure you're using an ARM64 preset (not universal)
3. **Verify configuration**: Check that `CMAKE_OSX_ARCHITECTURES` shows only `arm64`
```bash
# Verify architecture setting
cmake --preset macos-debug
grep -A 5 -B 5 "CMAKE_OSX_ARCHITECTURES" build/CMakeCache.txt
```
## 📝 Notes
- **ARM64 presets**: Fast builds, no architecture conflicts
- **Universal presets**: Slower builds, for distribution only
- **Deployment target**: ARM64 presets use macOS 11.0+ (when Apple Silicon was introduced)
- **Universal presets**: Still support macOS 10.15+ for backward compatibility

98
docs/C1-changelog.md Normal file
View File

@@ -0,0 +1,98 @@
# Changelog
## 0.3.0 (September 2025)
### Major Features
- **Complete Theme Management System**: 5+ built-in themes with custom theme creation and editing
- **Multi-Session Workspace**: Work with multiple ROMs simultaneously in enhanced docked interface
- **Enhanced Welcome Screen**: Themed interface with quick access to all editors and features
- **Asar 65816 Assembler Integration**: Complete cross-platform ROM patching with assembly code
- **ZSCustomOverworld v3**: Full integration with enhanced overworld editing capabilities
- **Advanced Message Editing**: Enhanced text editing interface with improved parsing and real-time preview
- **GUI Docking System**: Improved docking and workspace management for better user workflow
- **Symbol Extraction**: Extract symbol names and opcodes from assembly files
- **Modernized Build System**: Upgraded to CMake 3.16+ with target-based configuration
### User Interface & Theming
- **Built-in Themes**: Classic YAZE, YAZE Tre, Cyberpunk, Sunset, Forest, and Midnight themes
- **Theme Editor**: Complete custom theme creation with save-to-file functionality
- **Animated Background Grid**: Optional moving grid with color breathing effects
- **Theme Import/Export**: Share custom themes with the community
- **Responsive UI**: All UI elements properly adapt to selected themes
### Enhancements
- **Enhanced CLI Tools**: Improved z3ed with modern command line interface and TUI
- **CMakePresets**: Added development workflow presets for better productivity
- **Cross-Platform CI/CD**: Multi-platform automated builds and testing with lenient code quality checks
- **Professional Packaging**: NSIS, DMG, and DEB/RPM installers
- **ROM-Dependent Testing**: Separated testing infrastructure for CI compatibility with 46+ core tests
- **Comprehensive Documentation**: Updated guides, help menus, and API documentation
### Technical Improvements
- **Modern C++23**: Latest language features for performance and safety
- **Memory Safety**: Enhanced memory management with RAII and smart pointers
- **Error Handling**: Improved error handling using absl::Status throughout
- **Cross-Platform**: Consistent experience across Windows, macOS, and Linux
- **Performance**: Optimized rendering and data processing
### Bug Fixes
- **Graphics Arena Crash**: Fixed double-free error during Arena singleton destruction
- **SNES Tile Format**: Corrected tile unpacking algorithm based on SnesLab documentation
- **Palette System**: Fixed color conversion functions (ImVec4 float to uint8_t conversion)
- **CI/CD**: Fixed missing cstring include for Ubuntu compilation
- **ROM Loading**: Fixed file path issues in tests
## 0.2.2 (December 2024)
- DungeonMap editing improvements
- ZSCustomOverworld support
- Cross platform file handling
## 0.2.1 (August 2024)
- Improved MessageEditor parsing
- Added integration test window
- Bitmap bug fixes
## 0.2.0 (July 2024)
- iOS app support
- Graphics Sheet Browser
- Project Files
## 0.1.0 (May 2024)
- Bitmap bug fixes
- Error handling improvements
## 0.0.9 (April 2024)
- Documentation updates
- Entrance tile types
- Emulator subsystem overhaul
## 0.0.8 (February 2024)
- Hyrule Magic Compression
- Dungeon Room Entrances
- PNG Export
## 0.0.7 (January 2024)
- OverworldEntities
- Entrances
- Exits
- Items
- Sprites
## 0.0.6 (November 2023)
- ScreenEditor DungeonMap
- Tile16 Editor
- Canvas updates
## 0.0.5 (November 2023)
- DungeonEditor
- DungeonObjectRenderer
## 0.0.4 (November 2023)
- Tile16Editor
- GfxGroupEditor
- Add GfxGroups functions to Rom
- Add Tile16Editor and GfxGroupEditor to OverworldEditor
## 0.0.3 (October 2023)
- Emulator subsystem
- SNES PPU and PPURegisters

62
docs/D1-roadmap.md Normal file
View File

@@ -0,0 +1,62 @@
# Roadmap
## 0.4.X (Next Major Release)
### Core Features
- **Overworld Sprites**: Complete sprite editing with add/remove functionality
- **Enhanced Dungeon Editing**: Advanced room object editing and manipulation
- **Tile16 Editing**: Enhanced editor for creating and modifying tile16 data
- **Plugin Architecture**: Framework for community extensions and custom tools
- **Graphics Sheets**: Complete editing, saving, and re-importing of sheets
- **Project Refactoring**: Clean up resource loading and memory usage
### Technical Improvements
- **Sprite Property Editor**: Add support for changing sprite behavior and attributes
- **Custom Sprites**: Support creating and editing custom sprites
- **Asar Patching**: Stabilize existing patching system for advanced modifications
## 0.5.X
### Advanced Features
- **SCAD Format**: Polish and finalize the scad file integration
- **Hex Editing Improvements**: Enhance user interface for direct ROM manipulation
- **Music Editing**: Add an interface to edit and manage music data
## 0.6.X
### Platform & Integration
- **Cross-Platform Stability**: Test and refine builds across Windows, macOS, iOS, and Linux
- **Plugin/Integration Framework**: Provide hooks or scripting for community add-ons
## 0.7.X
### Performance & Polish
- **Performance Optimizations**: Remove bottlenecks in rendering and data processing
- **Documentation Overhaul**: Update manuals, guides, and in-app tooltips
## 0.8.X
### Beta Preparation
- **Beta Release**: Code freeze on major features, focus on bug fixes and polish
- **User Interface Refinements**: Improve UI consistency, iconography, and layout
- **Internal Cleanup**: Remove deprecated code, finalize API calls
## 1.0.0
### Stable Release
- **Stable Release**: Final, production-ready version
- **Changelog**: Comprehensive summary of all changes since 0.0.0
## Current Focus Areas
### Immediate Priorities (v0.4.X)
1. **Dungeon Editor Refactoring**: Complete component-based architecture
2. **Sprite System**: Implement comprehensive sprite editing
3. **Graphics Pipeline**: Enhance graphics editing capabilities
4. **Plugin System**: Enable community extensions
### Long-term Vision
- **Community-Driven**: Robust plugin system for community contributions
- **Cross-Platform Excellence**: Seamless experience across all platforms
- **Performance**: Optimized for large ROMs and complex modifications
- **Accessibility**: User-friendly interface for both beginners and experts

View File

@@ -29,7 +29,6 @@ Example:
```asm ```asm
; ========================================================= ; =========================================================
; File: my_file.asm
; Purpose: [Brief description of the files functionality] ; Purpose: [Brief description of the files functionality]
; Author: [Your Name] ; Author: [Your Name]
; ========================================================= ; =========================================================
@@ -68,7 +67,6 @@ Sprite_Minecart_Main:
} }
``` ```
## Comments ## Comments
- **Purpose**: Comments should explain why the code exists and what it is intended to do, especially for complex logic. - **Purpose**: Comments should explain why the code exists and what it is intended to do, especially for complex logic.
@@ -83,22 +81,6 @@ Example:
LDA $22 : SEC : SBC $3F : STA $31 ; Adjust X position for camera movement LDA $22 : SEC : SBC $3F : STA $31 ; Adjust X position for camera movement
``` ```
## Directives
- **Organization**: Use `%macro`, `include`, and other Asar directives in a structured manner, keeping related directives grouped together.
- **Usage**: Ensure all directives are used consistently throughout the codebase, following the naming conventions and formatting rules established.
Example:
```asm
%macro InitMovement
LDA.b $22 : STA.b $3F
LDA.b $23 : STA.b $41
LDA.b $20 : STA.b $3E
LDA.b $21 : STA.b $40
endmacro
```
## Instructions ## Instructions
- **Single Line Instructions**: Combine multiple instructions on a single line using colons (`:`) where appropriate for related operations. - **Single Line Instructions**: Combine multiple instructions on a single line using colons (`:`) where appropriate for related operations.
@@ -121,11 +103,11 @@ Example:
```asm ```asm
%macro HandlePlayerCamera %macro HandlePlayerCamera
LDA $22 : SEC : SBC $3F : STA $31 LDA $22 : SEC : SBC $3F : STA $31
LDA $20 : SEC : SBC $3E : STA $30 LDA $20 : SEC : SBC $3E : STA $30
JSL Link_HandleMovingAnimation_FullLongEntry JSL Link_HandleMovingAnimation_FullLongEntry
JSL HandleIndoorCameraAndDoors JSL HandleIndoorCameraAndDoors
RTS RTS
endmacro endmacro
``` ```
@@ -137,12 +119,12 @@ endmacro
Example: Example:
```asm ```asm
.loop_start .loop_start
LDA $00 : CMP #$10 : BEQ .end_loop LDA $00 : CMP #$10 : BEQ .end_loop
INC $00 INC $00
BRA .loop_start BRA .loop_start
.end_loop .end_loop
RTS RTS
``` ```
## Data Structures ## Data Structures
@@ -155,10 +137,10 @@ Example:
```asm ```asm
.DirectionTileLookup .DirectionTileLookup
{ {
db $02, $00, $04, $00 ; North db $02, $00, $04, $00 ; North
db $00, $00, $03, $01 ; East db $00, $00, $03, $01 ; East
db $00, $02, $00, $04 ; South db $00, $02, $00, $04 ; South
db $03, $01, $00, $00 ; West db $03, $01, $00, $00 ; West
} }
``` ```
@@ -206,7 +188,6 @@ AncillaAdd_Hookshot:
- **Logical Grouping**: Organize code into logical sections, with related routines and macros grouped together. - **Logical Grouping**: Organize code into logical sections, with related routines and macros grouped together.
- **Separation of Concerns**: Ensure that each section of code is responsible for a specific task or set of related tasks, avoiding tightly coupled code. - **Separation of Concerns**: Ensure that each section of code is responsible for a specific task or set of related tasks, avoiding tightly coupled code.
- **Modularity**: Write code in a modular way, making it easier to reuse and maintain. - **Modularity**: Write code in a modular way, making it easier to reuse and maintain.
- **Status Registers and Stack Operations**: Indent code blocks when using status register operations (REP, SEP, PHX, PLX, etc.) to improve readability.
Example: Example:
@@ -216,16 +197,14 @@ Example:
; ========================================================= ; =========================================================
Sprite_Minecart_Main: Sprite_Minecart_Main:
{ {
JSR HandleTileDirections PHX
JSR HandleDynamicSwitchTileDirections JSR HandleMinecartMovement
PHX PLX
JSR HandleMinecartMovement
PLX
REP #$20 REP #$20
LDA !SpriteDirection : STA $00 LDA !SpriteDirection : STA $00
SEP #$20 SEP #$20
RTS RTS
} }
``` ```

View File

@@ -0,0 +1,360 @@
# Dungeon Editor Guide
## Overview
The Yaze Dungeon Editor is a comprehensive tool for editing Zelda 3: A Link to the Past dungeon rooms, objects, sprites, items, entrances, doors, and chests. It provides an integrated editing experience with real-time rendering, coordinate system management, and advanced features for dungeon modification.
## Architecture
### Core Components
#### 1. DungeonEditorSystem
- **Purpose**: Central coordinator for all dungeon editing operations
- **Location**: `src/app/zelda3/dungeon/dungeon_editor_system.h/cc`
- **Features**:
- Room management (loading, saving, creating, deleting)
- Sprite management (enemies, NPCs, interactive objects)
- Item management (keys, hearts, rupees, etc.)
- Entrance/exit management (room connections)
- Door management (locked doors, key requirements)
- Chest management (treasure placement)
- Undo/redo system
- Event callbacks for real-time updates
#### 2. DungeonObjectEditor
- **Purpose**: Specialized editor for room objects (walls, floors, decorations)
- **Location**: `src/app/zelda3/dungeon/dungeon_object_editor.h/cc`
- **Features**:
- Object placement and editing
- Layer management (BG1, BG2, BG3)
- Object size editing with scroll wheel
- Collision detection and validation
- Selection and multi-selection
- Grid snapping
- Real-time preview
#### 3. ObjectRenderer
- **Purpose**: High-performance rendering system for dungeon objects
- **Location**: `src/app/zelda3/dungeon/object_renderer.h/cc`
- **Features**:
- Graphics cache for performance optimization
- Memory pool management
- Performance monitoring and statistics
- Object parsing from ROM data
- Palette support and color management
- Batch rendering for efficiency
#### 4. DungeonEditor (UI Layer)
- **Purpose**: User interface and interaction handling
- **Location**: `src/app/editor/dungeon/dungeon_editor.h/cc`
- **Features**:
- Integrated tabbed interface
- Canvas-based room editing
- Coordinate system management
- Object preview system
- Real-time rendering
- Compact editing panels
## Coordinate System
### Room Coordinates vs Canvas Coordinates
The dungeon editor uses a two-tier coordinate system:
1. **Room Coordinates**: 16x16 tile units (as used in the ROM)
2. **Canvas Coordinates**: Pixel coordinates for rendering
#### Conversion Functions
```cpp
// Convert room coordinates to canvas coordinates
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const {
return {room_x * 16, room_y * 16};
}
// Convert canvas coordinates to room coordinates
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const {
return {canvas_x / 16, canvas_y / 16};
}
// Check if coordinates are within canvas bounds
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
```
### Coordinate System Features
- **Automatic Bounds Checking**: Objects outside visible canvas area are culled
- **Scrolling Support**: Canvas handles scrolling internally with proper coordinate transformation
- **Grid Alignment**: 16x16 pixel grid for precise object placement
- **Margin Support**: Configurable margins for partial object visibility
## Object Rendering System
### Object Types
The system supports three main object subtypes based on ROM structure:
1. **Subtype 1** (0x00-0xFF): Standard room objects (walls, floors, decorations)
2. **Subtype 2** (0x100-0x1FF): Interactive objects (doors, switches, chests)
3. **Subtype 3** (0x200+): Special objects (stairs, warps, bosses)
### Rendering Pipeline
1. **Object Loading**: Objects are loaded from ROM data using `LoadObjects()`
2. **Tile Parsing**: Object tiles are parsed using `ObjectParser`
3. **Graphics Caching**: Frequently used graphics are cached for performance
4. **Palette Application**: SNES palettes are applied to object graphics
5. **Canvas Rendering**: Objects are rendered to canvas with proper coordinate transformation
### Performance Optimizations
- **Graphics Cache**: Reduces redundant graphics sheet loading
- **Memory Pool**: Efficient memory allocation for rendering
- **Batch Rendering**: Multiple objects rendered in single pass
- **Bounds Culling**: Objects outside visible area are skipped
- **Cache Invalidation**: Smart cache management based on palette changes
## User Interface
### Integrated Editing Panels
The dungeon editor features a consolidated interface with:
#### Main Canvas
- **Room Visualization**: Real-time room rendering with background layers
- **Object Display**: Objects rendered with proper positioning and sizing
- **Interactive Editing**: Click-to-select, drag-to-move, scroll-to-resize
- **Grid Overlay**: Optional grid display for precise positioning
- **Coordinate Display**: Real-time coordinate information
#### Compact Editing Panels
1. **Object Editor**
- Mode selection (Select, Insert, Edit, Delete)
- Layer management (BG1, BG2, BG3)
- Object type selection
- Size editing with scroll wheel
- Configuration options (snap to grid, show grid)
2. **Sprite Editor**
- Sprite placement and management
- Enemy and NPC configuration
- Layer assignment
- Quick sprite addition
3. **Item Editor**
- Item placement (keys, hearts, rupees)
- Hidden item configuration
- Item type selection
- Room assignment
4. **Entrance Editor**
- Room connection management
- Bidirectional connection support
- Position configuration
- Connection validation
5. **Door Editor**
- Door placement and configuration
- Lock status management
- Key requirement setup
- Direction and target room assignment
6. **Chest Editor**
- Treasure chest placement
- Item and quantity configuration
- Big chest support
- Opened status tracking
7. **Properties Editor**
- Room metadata management
- Dungeon settings
- Music and ambient sound configuration
- Boss room and save room flags
### Object Preview System
- **Real-time Preview**: Objects are previewed in the canvas as they're selected
- **Centered Display**: Preview objects are centered in the canvas for optimal viewing
- **Palette Support**: Previews use current palette settings
- **Information Display**: Object properties are shown in preview window
## Integration with ZScream
The dungeon editor is designed to be compatible with ZScream C# patterns:
### Room Loading
- Uses same room loading patterns as ZScream
- Compatible with ZScream room data structures
- Supports ZScream room naming conventions
### Object Parsing
- Follows ZScream object parsing logic
- Compatible with ZScream object type definitions
- Supports ZScream size encoding
### Coordinate System
- Matches ZScream coordinate conventions
- Uses same tile size calculations
- Compatible with ZScream positioning logic
## Testing and Validation
### Integration Tests
The system includes comprehensive integration tests:
1. **Basic Object Rendering**: Tests fundamental object rendering functionality
2. **Multi-Palette Rendering**: Tests rendering with different palettes
3. **Real Room Object Rendering**: Tests with actual ROM room data
4. **Disassembly Room Validation**: Tests specific rooms from disassembly
5. **Performance Testing**: Measures rendering performance and memory usage
6. **Cache Effectiveness**: Tests graphics cache performance
7. **Error Handling**: Tests error conditions and edge cases
### Test Data
Tests use real ROM data from `build/bin/zelda3.sfc`:
- **Room 0x0000**: Ganon's room (from disassembly)
- **Room 0x0002, 0x0012**: Sewer rooms (from disassembly)
- **Room 0x0020**: Agahnim's tower (from disassembly)
- **Additional rooms**: 0x0001, 0x0010, 0x0033, 0x005A
### Performance Benchmarks
- **Rendering Time**: < 500ms for 100 objects
- **Memory Usage**: < 100MB for large object sets
- **Cache Hit Rate**: Optimized for frequent object access
- **Coordinate Conversion**: O(1) coordinate transformation
## Usage Examples
### Basic Object Editing
```cpp
// Load a room
auto room_result = dungeon_editor_system_->GetRoom(0x0000);
// Add an object
auto status = object_editor_->InsertObject(5, 5, 0x10, 0x12, 0);
// Parameters: x, y, object_type, size, layer
// Render objects
auto result = object_renderer_->RenderObjects(objects, palette);
```
### Coordinate Conversion
```cpp
// Convert room coordinates to canvas coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(room_x, room_y);
// Check if coordinates are within bounds
if (IsWithinCanvasBounds(canvas_x, canvas_y)) {
// Render object at this position
}
```
### Object Preview
```cpp
// Create preview object
auto preview_object = zelda3::RoomObject(id, 8, 8, 0x12, 0);
preview_object.set_rom(rom_);
preview_object.EnsureTilesLoaded();
// Render preview
auto result = object_renderer_->RenderObject(preview_object, palette);
```
## Configuration Options
### Editor Configuration
```cpp
struct EditorConfig {
bool snap_to_grid = true;
int grid_size = 16;
bool show_grid = true;
bool show_preview = true;
bool auto_save = false;
int auto_save_interval = 300;
bool validate_objects = true;
bool show_collision_bounds = false;
};
```
### Performance Configuration
```cpp
// Object renderer settings
object_renderer_->SetCacheSize(100);
object_renderer_->EnablePerformanceMonitoring(true);
// Canvas settings
canvas_.SetCanvasSize(ImVec2(512, 512));
canvas_.set_draggable(true);
```
## Troubleshooting
### Common Issues
1. **Objects Not Displaying**
- Check if ROM is loaded
- Verify object tiles are loaded with `EnsureTilesLoaded()`
- Check coordinate bounds with `IsWithinCanvasBounds()`
2. **Coordinate Misalignment**
- Use coordinate conversion functions
- Check canvas scrolling settings
- Verify grid alignment
3. **Performance Issues**
- Enable graphics caching
- Check memory usage with `GetMemoryUsage()`
- Monitor performance stats with `GetPerformanceStats()`
4. **Preview Not Showing**
- Verify object is within canvas bounds
- Check palette is properly set
- Ensure object has valid tiles
### Debug Information
The system provides comprehensive debug information:
- Object count and statistics
- Cache hit/miss rates
- Memory usage tracking
- Performance metrics
- Coordinate system validation
## Future Enhancements
### Planned Features
1. **Advanced Object Editing**
- Multi-object selection and manipulation
- Object grouping and layers
- Advanced collision detection
2. **Enhanced Rendering**
- Real-time lighting effects
- Animation support
- Advanced shader effects
3. **Improved UX**
- Keyboard shortcuts
- Context menus
- Undo/redo visualization
4. **Integration Features**
- ZScream project import/export
- Collaborative editing
- Version control integration
## Conclusion
The Yaze Dungeon Editor provides a comprehensive, high-performance solution for editing Zelda 3 dungeon rooms. With its integrated interface, robust coordinate system, and advanced rendering capabilities, it offers both novice and expert users the tools needed to create and modify dungeon content effectively.
The system's compatibility with ZScream patterns and comprehensive testing ensure reliability and consistency with existing tools, while its modern architecture provides a foundation for future enhancements and features.

View File

@@ -0,0 +1,364 @@
# Dungeon Editor Design Plan
## Overview
This document provides a comprehensive guide for future developers working on the Zelda 3 Dungeon Editor system. The dungeon editor has been refactored into a modular, component-based architecture that separates concerns and improves maintainability.
## Architecture Overview
### Core Components
The dungeon editor system consists of several key components:
1. **DungeonEditor** - Main orchestrator class that manages the overall editor state
2. **DungeonRoomSelector** - Handles room and entrance selection UI
3. **DungeonCanvasViewer** - Manages the main canvas rendering and room display
4. **DungeonObjectSelector** - Provides object selection, editing panels, and tile graphics
5. **ObjectRenderer** - Core rendering engine for dungeon objects
6. **DungeonEditorSystem** - High-level system for managing dungeon editing operations
### File Structure
```
src/app/editor/dungeon/
├── dungeon_editor.h/cc # Main editor orchestrator
├── dungeon_room_selector.h/cc # Room/entrance selection component
├── dungeon_canvas_viewer.h/cc # Canvas rendering component
├── dungeon_object_selector.h/cc # Object editing component
└── dungeon_editor_system.h/cc # Core editing system
src/app/zelda3/dungeon/
├── object_renderer.h/cc # Object rendering engine
├── dungeon_object_editor.h/cc # Object editing logic
├── room.h/cc # Room data structures
├── room_object.h/cc # Object data structures
└── room_entrance.h/cc # Entrance data structures
```
## Component Responsibilities
### DungeonEditor (Main Orchestrator)
**Responsibilities:**
- Manages overall editor state and ROM data
- Coordinates between UI components
- Handles data initialization and propagation
- Implements the 3-column layout (Room Selector | Canvas | Object Selector)
**Key Methods:**
- `UpdateDungeonRoomView()` - Main UI update loop
- `Load()` - Initialize editor with ROM data
- `set_rom()` - Update ROM reference across components
### DungeonRoomSelector
**Responsibilities:**
- Room selection and navigation
- Entrance selection and editing
- Active room management
- Room list display with names
**Key Methods:**
- `Draw()` - Main rendering method
- `DrawRoomSelector()` - Room list and selection
- `DrawEntranceSelector()` - Entrance editing interface
- `set_rom()`, `set_rooms()`, `set_entrances()` - Data access methods
### DungeonCanvasViewer
**Responsibilities:**
- Main canvas rendering and display
- Room graphics loading and management
- Object rendering with proper coordinates
- Background layer management
- Coordinate conversion (room ↔ canvas)
**Key Methods:**
- `Draw(int room_id)` - Main canvas rendering
- `LoadAndRenderRoomGraphics()` - Graphics loading
- `RenderObjectInCanvas()` - Object rendering
- `RoomToCanvasCoordinates()` - Coordinate conversion
- `RenderRoomBackgroundLayers()` - Background rendering
### DungeonObjectSelector
**Responsibilities:**
- Object selection and preview
- Tile graphics display
- Compact editing panels for all editor modes
- Object renderer integration
**Key Methods:**
- `Draw()` - Main rendering with tabbed interface
- `DrawRoomGraphics()` - Tile graphics display
- `DrawIntegratedEditingPanels()` - Editing interface
- `DrawCompactObjectEditor()` - Object editing controls
- `DrawCompactSpriteEditor()` - Sprite editing controls
- Similar methods for Items, Entrances, Doors, Chests, Properties
## Data Flow
### Initialization Flow
1. **ROM Loading**: `DungeonEditor::Load()` is called with ROM data
2. **Component Setup**: ROM and data pointers are propagated to all components
3. **Graphics Initialization**: Room graphics are loaded and cached
4. **UI State Setup**: Active rooms, palettes, and editor modes are initialized
### Runtime Data Flow
1. **User Interaction**: User selects rooms, objects, or editing modes
2. **State Updates**: Components update their internal state
3. **Data Propagation**: Changes are communicated between components
4. **Rendering**: All components re-render with updated data
### Key Data Structures
```cpp
// Main editor state
std::array<zelda3::Room, 0x128> rooms_;
std::array<zelda3::RoomEntrance, 0x8C> entrances_;
ImVector<int> active_rooms_;
gfx::PaletteGroup current_palette_group_;
// Component instances
DungeonRoomSelector room_selector_;
DungeonCanvasViewer canvas_viewer_;
DungeonObjectSelector object_selector_;
```
## Integration Patterns
### Component Communication
Components communicate through:
1. **Direct method calls** - Parent calls child methods
2. **Data sharing** - Shared pointers to common data structures
3. **Event propagation** - State changes trigger updates
### ROM Data Management
```cpp
// ROM propagation pattern
void DungeonEditor::set_rom(Rom* rom) {
rom_ = rom;
room_selector_.set_rom(rom);
canvas_viewer_.SetRom(rom);
object_selector_.SetRom(rom);
}
```
### State Synchronization
Components maintain their own state but receive updates from the main editor:
- Room selection state is managed by `DungeonRoomSelector`
- Canvas rendering state is managed by `DungeonCanvasViewer`
- Object editing state is managed by `DungeonObjectSelector`
## UI Layout Architecture
### 3-Column Layout
The main editor uses a 3-column ImGui table layout:
```cpp
if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, ImVec2(0, 0))) {
TableSetupColumn("Room/Entrance Selector", ImGuiTableColumnFlags_WidthFixed, 250);
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Object Selector/Editor", ImGuiTableColumnFlags_WidthFixed, 300);
// Column 1: Room Selector
TableNextColumn();
room_selector_.Draw();
// Column 2: Canvas
TableNextColumn();
canvas_viewer_.Draw(current_room);
// Column 3: Object Selector
TableNextColumn();
object_selector_.Draw();
}
```
### Component Internal Layout
Each component manages its own internal layout:
- **DungeonRoomSelector**: Tabbed interface (Rooms | Entrances)
- **DungeonCanvasViewer**: Canvas with controls and debug popup
- **DungeonObjectSelector**: Tabbed interface (Graphics | Editor)
## Coordinate System
### Room Coordinates vs Canvas Coordinates
- **Room Coordinates**: 16x16 tile units (0-15 for a standard room)
- **Canvas Coordinates**: Pixel coordinates for rendering
- **Conversion**: `RoomToCanvasCoordinates(x, y) = (x * 16, y * 16)`
### Bounds Checking
All rendering operations include bounds checking:
```cpp
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
```
## Error Handling & Validation
### ROM Validation
All components validate ROM state before operations:
```cpp
if (!rom_ || !rom_->is_loaded()) {
ImGui::Text("ROM not loaded");
return;
}
```
### Bounds Validation
Graphics operations include bounds checking:
```cpp
if (room_id < 0 || room_id >= rooms_->size()) {
return; // Skip invalid operations
}
```
## Performance Considerations
### Caching Strategy
- **Object Render Cache**: Cached rendered bitmaps to avoid re-rendering
- **Graphics Cache**: Cached graphics sheets for frequently accessed data
- **Memory Pool**: Efficient memory allocation for temporary objects
### Rendering Optimization
- **Viewport Culling**: Objects outside visible area are not rendered
- **Lazy Loading**: Graphics are loaded only when needed
- **Selective Updates**: Only changed components re-render
## Testing Strategy
### Integration Tests
The system includes comprehensive integration tests:
- `dungeon_object_renderer_integration_test.cc` - Core rendering tests
- `dungeon_editor_system_integration_test.cc` - System integration tests
- `dungeon_object_renderer_mock_test.cc` - Mock ROM testing
### Test Categories
1. **Real ROM Tests**: Tests with actual Zelda 3 ROM data
2. **Mock ROM Tests**: Tests with simulated ROM data
3. **Performance Tests**: Rendering performance benchmarks
4. **Error Handling Tests**: Validation and error recovery
## Future Development Guidelines
### Adding New Features
1. **Identify Component**: Determine which component should handle the feature
2. **Extend Interface**: Add necessary methods to component header
3. **Implement Logic**: Add implementation in component source file
4. **Update Integration**: Modify main editor to use new functionality
5. **Add Tests**: Create tests for new functionality
### Component Extension Patterns
```cpp
// Adding new data access method
void Component::SetNewData(const NewDataType& data) {
new_data_ = data;
}
// Adding new rendering method
void Component::DrawNewFeature() {
// Implementation
}
// Adding to main Draw method
void Component::Draw() {
// Existing code
DrawNewFeature();
}
```
### Data Flow Extension
When adding new data types:
1. Add to main editor state
2. Create setter methods in relevant components
3. Update initialization in `Load()` method
4. Add to `set_rom()` propagation if ROM-dependent
### UI Layout Extension
For new UI elements:
1. Determine placement (new tab, new panel, etc.)
2. Follow existing ImGui patterns
3. Maintain consistent spacing and styling
4. Add to appropriate component's Draw method
## Common Pitfalls & Solutions
### Memory Management
- **Issue**: Dangling pointers to ROM data
- **Solution**: Always validate ROM state before use
### Coordinate System
- **Issue**: Objects rendering at wrong positions
- **Solution**: Use coordinate conversion helper methods
### State Synchronization
- **Issue**: Components showing stale data
- **Solution**: Ensure data propagation in setter methods
### Performance Issues
- **Issue**: Slow rendering with many objects
- **Solution**: Implement viewport culling and caching
## Debugging Tools
### Debug Popup
The canvas viewer includes a comprehensive debug popup with:
- Object statistics and metadata
- Cache information
- Performance metrics
- Object type breakdowns
### Logging
Key operations include logging for debugging:
```cpp
std::cout << "Loading room graphics for room " << room_id << std::endl;
```
## Build Integration
### CMake Configuration
New components are automatically included via:
```cmake
# In CMakeLists.txt
file(GLOB YAZE_SRC_FILES "src/app/editor/dungeon/*.cc")
```
### Dependencies
Key dependencies:
- ImGui for UI rendering
- gfx library for graphics operations
- zelda3 library for ROM data structures
- absl for status handling
## Conclusion
This modular architecture provides a solid foundation for future dungeon editor development. The separation of concerns makes the codebase maintainable, testable, and extensible. Future developers should follow the established patterns and extend components rather than modifying the main orchestrator class.
For questions or clarifications, refer to the existing integration tests and component implementations as examples of proper usage patterns.

View File

@@ -0,0 +1,248 @@
# DungeonEditor Refactoring Plan
## Overview
This document outlines the comprehensive refactoring of the 1444-line `dungeon_editor.cc` file into focused, single-responsibility components.
## Component Structure
### ✅ Created Components
#### 1. DungeonToolset (`dungeon_toolset.h/cc`)
**Responsibility**: Toolbar UI management
- Background layer selection (All/BG1/BG2/BG3)
- Placement mode selection (Object/Sprite/Item/etc.)
- Undo/Redo buttons with callbacks
- **Replaces**: `DrawToolset()` method (~70 lines)
#### 2. DungeonObjectInteraction (`dungeon_object_interaction.h/cc`)
**Responsibility**: Object selection and placement
- Mouse interaction handling
- Object selection rectangle (like OverworldEditor)
- Drag and drop operations
- Coordinate conversion utilities
- **Replaces**: All mouse/selection methods (~400 lines)
#### 3. DungeonRenderer (`dungeon_renderer.h/cc`)
**Responsibility**: All rendering operations
- Object rendering with caching
- Background layer composition
- Layout object visualization
- Render cache management
- **Replaces**: All rendering methods (~200 lines)
#### 4. DungeonRoomLoader (`dungeon_room_loader.h/cc`)
**Responsibility**: ROM data loading
- Room loading from ROM
- Room size calculation
- Entrance loading
- Graphics loading coordination
- **Replaces**: Room loading methods (~150 lines)
#### 5. DungeonUsageTracker (`dungeon_usage_tracker.h/cc`)
**Responsibility**: Resource usage analysis
- Blockset/spriteset/palette usage tracking
- Usage statistics display
- Resource optimization insights
- **Replaces**: Usage statistics methods (~100 lines)
## Refactored DungeonEditor Structure
### Before Refactoring: 1444 lines
```cpp
class DungeonEditor {
// 30+ methods handling everything
// Mixed responsibilities
// Large data structures
// Complex dependencies
};
```
### After Refactoring: ~400 lines
```cpp
class DungeonEditor {
// Core editor interface (unchanged)
void Initialize() override;
absl::Status Load() override;
absl::Status Update() override;
absl::Status Save() override;
// High-level UI orchestration
absl::Status UpdateDungeonRoomView();
void DrawCanvasAndPropertiesPanel();
void DrawRoomPropertiesDebugPopup();
// Component coordination
void OnRoomSelected(int room_id);
private:
// Focused components
DungeonToolset toolset_;
DungeonObjectInteraction object_interaction_;
DungeonRenderer renderer_;
DungeonRoomLoader room_loader_;
DungeonUsageTracker usage_tracker_;
// Existing UI components
DungeonRoomSelector room_selector_;
DungeonCanvasViewer canvas_viewer_;
DungeonObjectSelector object_selector_;
// Core data and state
std::array<zelda3::Room, 0x128> rooms_;
bool is_loaded_ = false;
// etc.
};
```
## Method Migration Map
### Core Editor Methods (Keep in main file)
-`Initialize()` - Component initialization
-`Load()` - Delegates to room_loader_
-`Update()` - High-level update coordination
-`Save()`, `Undo()`, `Redo()` - Editor interface
-`UpdateDungeonRoomView()` - UI orchestration
### UI Methods (Keep for coordination)
-`DrawCanvasAndPropertiesPanel()` - Tab management
-`DrawRoomPropertiesDebugPopup()` - Debug popup
-`DrawDungeonTabView()` - Room tab management
-`DrawDungeonCanvas()` - Canvas coordination
-`OnRoomSelected()` - Room selection handling
### Methods Moved to Components
#### → DungeonToolset
-`DrawToolset()` - Toolbar rendering
#### → DungeonObjectInteraction
-`HandleCanvasMouseInput()` - Mouse handling
-`CheckForObjectSelection()` - Selection rectangle
-`DrawObjectSelectRect()` - Selection drawing
-`SelectObjectsInRect()` - Selection logic
-`PlaceObjectAtPosition()` - Object placement
-`DrawSelectBox()` - Selection visualization
-`DrawDragPreview()` - Drag preview
-`UpdateSelectedObjects()` - Selection updates
-`IsObjectInSelectBox()` - Selection testing
- ❌ Coordinate conversion helpers
#### → DungeonRenderer
-`RenderObjectInCanvas()` - Object rendering
-`DisplayObjectInfo()` - Object info overlay
-`RenderLayoutObjects()` - Layout rendering
-`RenderRoomBackgroundLayers()` - Background rendering
-`RefreshGraphics()` - Graphics refresh
- ❌ Object cache management
#### → DungeonRoomLoader
-`LoadDungeonRoomSize()` - Room size calculation
-`LoadAndRenderRoomGraphics()` - Graphics loading
-`ReloadAllRoomGraphics()` - Bulk reload
- ❌ Room size and address management
#### → DungeonUsageTracker
-`CalculateUsageStats()` - Usage calculation
-`DrawUsageStats()` - Usage display
-`DrawUsageGrid()` - Usage visualization
-`RenderSetUsage()` - Set usage rendering
## Component Communication
### Callback System
```cpp
// Object placement callback
object_interaction_.SetObjectPlacedCallback([this](const auto& object) {
renderer_.ClearObjectCache();
});
// Toolset callbacks
toolset_.SetUndoCallback([this]() { Undo(); });
toolset_.SetPaletteToggleCallback([this]() { palette_showing_ = !palette_showing_; });
// Object selection callback
object_selector_.SetObjectSelectedCallback([this](const auto& object) {
object_interaction_.SetPreviewObject(object, true);
toolset_.set_placement_type(DungeonToolset::kObject);
});
```
### Data Sharing
```cpp
// Update components with current room
void OnRoomSelected(int room_id) {
current_room_id_ = room_id;
object_interaction_.SetCurrentRoom(&rooms_, room_id);
// etc.
}
```
## Benefits of Refactoring
### 1. **Reduced Complexity**
- Main file: 1444 → ~400 lines (72% reduction)
- Single responsibility per component
- Clear separation of concerns
### 2. **Improved Testability**
- Individual components can be unit tested
- Mocking becomes easier
- Isolated functionality testing
### 3. **Better Maintainability**
- Changes isolated to relevant components
- Easier to locate and fix bugs
- Cleaner code reviews
### 4. **Enhanced Extensibility**
- New features added to appropriate components
- Component interfaces allow easy replacement
- Plugin-style architecture possible
### 5. **Cleaner Dependencies**
- UI separate from data manipulation
- Rendering separate from business logic
- Clear component boundaries
## Implementation Status
### ✅ Completed
- Created all component header files
- Created component implementation stubs
- Updated DungeonEditor header with components
- Basic component integration
### 🔄 In Progress
- Method migration from main file to components
- Component callback setup
- Legacy method removal
### ⏳ Pending
- Full method implementation in components
- Complete integration testing
- Documentation updates
- Build system updates
## Migration Strategy
### Phase 1: Create Components ✅
- Define component interfaces
- Create header and implementation files
- Set up basic structure
### Phase 2: Integrate Components 🔄
- Add components to DungeonEditor
- Set up callback systems
- Begin method delegation
### Phase 3: Move Methods
- Systematically move methods to components
- Update method calls to use components
- Remove old implementations
### Phase 4: Cleanup
- Remove unused member variables
- Clean up includes
- Update documentation
This refactoring transforms the monolithic DungeonEditor into a well-organized, component-based architecture that's easier to maintain, test, and extend.

View File

@@ -0,0 +1,271 @@
# Dungeon Object System
## Overview
The Dungeon Object System provides a comprehensive framework for editing and managing dungeon rooms, objects, and layouts in The Legend of Zelda: A Link to the Past. This system combines real-time visual editing with precise data manipulation to create a powerful dungeon creation and modification toolkit.
## Architecture
### Core Components
The dungeon system is built around several key components that work together to provide a seamless editing experience:
#### 1. DungeonEditor (`src/app/editor/dungeon/dungeon_editor.h`)
The main interface that orchestrates all dungeon editing functionality. It provides:
- **Windowed Canvas System**: Fixed-size canvas that prevents UI layout disruption
- **Tabbed Room Interface**: Multiple rooms can be open simultaneously for easy comparison and editing
- **Integrated Object Placement**: Direct object placement from selector to canvas
- **Real-time Preview**: Live object preview follows mouse cursor during placement
#### 2. DungeonObjectSelector (`src/app/editor/dungeon/dungeon_object_selector.h`)
Combines object browsing and editing in a unified interface:
- **Object Browser**: Visual grid of all available objects with real-time previews
- **Object Editor**: Integrated editing panels for sprites, items, entrances, doors, and chests
- **Callback System**: Notifies main editor when objects are selected for placement
#### 3. DungeonCanvasViewer (`src/app/editor/dungeon/dungeon_canvas_viewer.h`)
Specialized canvas for rendering dungeon rooms:
- **Background Layer Rendering**: Proper BG1/BG2 layer composition
- **Object Rendering**: Cached object rendering with palette support
- **Coordinate System**: Seamless translation between room and canvas coordinates
#### 4. Room Management System (`src/app/zelda3/dungeon/room.h`)
Core data structures for room representation:
- **Room Objects**: Tile-based objects (walls, floors, decorations)
- **Room Layout**: Structural elements and collision data
- **Sprites**: Enemy and NPC placement
- **Entrances/Exits**: Room connections and transitions
## Object Types and Hierarchies
### Room Objects
Room objects are the fundamental building blocks of dungeon rooms. They follow a hierarchical structure:
#### Type 1 Objects (0x00-0xFF)
Basic structural elements:
- **0x10-0x1F**: Wall objects (various types and orientations)
- **0x20-0x2F**: Floor tiles (stone, wood, carpet, etc.)
- **0x30-0x3F**: Decorative elements (torches, statues, pillars)
- **0x40-0x4F**: Interactive elements (switches, blocks)
#### Type 2 Objects (0x100-0x1FF)
Complex multi-tile objects:
- **0x100-0x10F**: Large wall sections
- **0x110-0x11F**: Complex floor patterns
- **0x120-0x12F**: Multi-tile decorations
#### Type 3 Objects (0x200+)
Special dungeon-specific objects:
- **0x200-0x20F**: Boss room elements
- **0x210-0x21F**: Puzzle-specific objects
- **0xF9-0xFA**: Chests (small and large)
### Object Properties
Each object has several key properties:
```cpp
class RoomObject {
int id_; // Object type identifier
int x_, y_; // Position in room (16x16 tile units)
int size_; // Size modifier (affects rendering)
LayerType layer_; // Rendering layer (0=BG, 1=MID, 2=FG)
// ... additional properties
};
```
## How Object Placement Works
### Selection Process
1. **Object Browser**: User selects an object from the visual grid
2. **Preview Generation**: Object is rendered with current room palette
3. **Callback Trigger**: Selection notifies main editor via callback
4. **Preview Update**: Main editor receives object and enables placement mode
### Placement Process
1. **Mouse Tracking**: Preview object follows mouse cursor on canvas
2. **Coordinate Translation**: Mouse position converted to room coordinates
3. **Visual Feedback**: Semi-transparent preview shows placement position
4. **Click Placement**: Left-click places object at current position
5. **Room Update**: Object added to room data and cache cleared for redraw
### Code Flow
```cpp
// Object selection in DungeonObjectSelector
if (ImGui::Selectable("", is_selected)) {
preview_object_ = selected_object;
object_loaded_ = true;
// Notify main editor
if (object_selected_callback_) {
object_selected_callback_(preview_object_);
}
}
// Object placement in DungeonEditor
void PlaceObjectAtPosition(int room_x, int room_y) {
auto new_object = preview_object_;
new_object.x_ = room_x;
new_object.y_ = room_y;
new_object.set_rom(rom_);
new_object.EnsureTilesLoaded();
room.AddTileObject(new_object);
object_render_cache_.clear(); // Force redraw
}
```
## Rendering Pipeline
### Object Rendering
The system uses a sophisticated rendering pipeline:
1. **Tile Loading**: Object tiles loaded from ROM based on object ID
2. **Palette Application**: Room-specific palette applied to object
3. **Bitmap Generation**: Object rendered to bitmap with proper composition
4. **Caching**: Rendered objects cached for performance
5. **Canvas Drawing**: Bitmap drawn to canvas at correct position
### Performance Optimizations
- **Render Caching**: Objects cached based on ID, position, size, and palette hash
- **Bounds Checking**: Only objects within canvas bounds are rendered
- **Lazy Loading**: Graphics and objects loaded on-demand
- **Palette Hashing**: Efficient cache invalidation when palettes change
## User Interface Components
### Three-Column Layout
The dungeon editor uses a carefully designed three-column layout:
#### Column 1: Room Control Panel (280px fixed)
- **Room Selector**: Browse and select rooms
- **Debug Controls**: Room properties in table format
- **Object Statistics**: Live object counts and cache status
#### Column 2: Windowed Canvas (800px fixed)
- **Tabbed Interface**: Multiple rooms open simultaneously
- **Fixed Dimensions**: Prevents UI layout disruption
- **Real-time Preview**: Object placement preview follows cursor
- **Layer Visualization**: Proper background/foreground rendering
#### Column 3: Object Selector/Editor (stretch)
- **Object Browser Tab**: Visual grid of available objects
- **Object Editor Tab**: Integrated editing for sprites, items, etc.
- **Placement Tools**: Object property editing and placement controls
### Debug and Control Features
#### Room Properties Table
Real-time editing of room attributes:
```
Property | Value
------------|--------
Room ID | 0x001 (1)
Layout | [Hex Input]
Blockset | [Hex Input]
Spriteset | [Hex Input]
Palette | [Hex Input]
Floor 1 | [Hex Input]
Floor 2 | [Hex Input]
Message ID | [Hex Input]
```
#### Object Statistics
Live feedback on room contents:
- Total objects count
- Layout objects count
- Sprites count
- Chests count
- Cache status and controls
## Integration with ROM Data
### Data Sources
The system integrates with multiple ROM data sources:
#### Room Headers (`0x1F8000`)
- Room layout index
- Blockset and spriteset references
- Palette assignments
- Floor type definitions
#### Object Data
- Object definitions and tile mappings
- Size and layer information
- Interaction properties
#### Graphics Data
- Tile graphics (4bpp SNES format)
- Palette data (15-color palettes)
- Blockset compositions
### Assembly Integration
The system references the US disassembly (`assets/asm/usdasm/`) for:
- Room data structure validation
- Object type definitions
- Memory layout verification
- Data pointer validation
## Comparison with ZScream
### Architectural Differences
YAZE's approach differs from ZScream in several key ways:
#### Component-Based Architecture
- **YAZE**: Modular components with clear separation of concerns
- **ZScream**: More monolithic approach with integrated functionality
#### Real-time Rendering
- **YAZE**: Live object preview with mouse tracking
- **ZScream**: Static preview with separate placement step
#### UI Organization
- **YAZE**: Fixed-width columns prevent layout disruption
- **ZScream**: Resizable panels that can affect overall layout
#### Caching Strategy
- **YAZE**: Sophisticated object render caching with hash-based invalidation
- **ZScream**: Simpler caching approach
### Shared Concepts
Both systems share fundamental concepts:
- Object-based room construction
- Layer-based rendering
- ROM data integration
- Visual object browsing
## Best Practices
### Performance
1. **Use Render Caching**: Don't clear cache unnecessarily
2. **Bounds Checking**: Only render visible objects
3. **Lazy Loading**: Load graphics and objects on-demand
4. **Efficient Callbacks**: Minimize callback frequency
### Code Organization
1. **Separation of Concerns**: Keep UI, data, and rendering separate
2. **Clear Interfaces**: Use callbacks for component communication
3. **Error Handling**: Validate ROM data and handle errors gracefully
4. **Memory Management**: Clean up resources properly
### User Experience
1. **Visual Feedback**: Provide clear object placement preview
2. **Consistent Layout**: Use fixed dimensions for stable UI
3. **Contextual Information**: Show relevant object properties
4. **Efficient Workflow**: Minimize clicks for common operations
## Future Enhancements
### Planned Features
1. **Drag and Drop**: Direct object dragging from selector to canvas
2. **Multi-Selection**: Select and manipulate multiple objects
3. **Copy/Paste**: Copy object configurations between rooms
4. **Undo/Redo**: Full edit history management
5. **Template System**: Save and load room templates
### Technical Improvements
1. **GPU Acceleration**: Move rendering to GPU for better performance
2. **Advanced Caching**: Predictive loading and intelligent cache management
3. **Background Processing**: Asynchronous ROM data loading
4. **Memory Optimization**: Reduce memory footprint for large dungeons
This documentation provides a comprehensive understanding of how the YAZE dungeon object system works, from high-level architecture to low-level implementation details. The system is designed to be both powerful for advanced users and accessible for newcomers to dungeon editing.

View File

@@ -0,0 +1,492 @@
# Overworld Loading Guide
This document provides a comprehensive guide to understanding how overworld loading works in both ZScream (C#) and yaze (C++), including the differences between vanilla ROMs and ZSCustomOverworld v2/v3 ROMs.
## Table of Contents
1. [Overview](#overview)
2. [ROM Types and Versions](#rom-types-and-versions)
3. [Overworld Map Structure](#overworld-map-structure)
4. [Loading Process](#loading-process)
5. [ZScream Implementation](#zscream-implementation)
6. [Yaze Implementation](#yaze-implementation)
7. [Key Differences](#key-differences)
8. [Common Issues and Solutions](#common-issues-and-solutions)
## Overview
Both ZScream and yaze are Zelda 3 ROM editors that support editing overworld maps. They handle three main types of ROMs:
- **Vanilla ROMs**: Original Zelda 3 ROMs without modifications
- **ZSCustomOverworld v2**: ROMs with expanded overworld features
- **ZSCustomOverworld v3**: ROMs with additional features like overlays and custom background colors
## ROM Types and Versions
### Version Detection
Both editors detect the ROM version using the same constant:
```cpp
// Address: 0x140145
constexpr int OverworldCustomASMHasBeenApplied = 0x140145;
// Version values:
// 0xFF = Vanilla ROM
// 0x02 = ZSCustomOverworld v2
// 0x03 = ZSCustomOverworld v3
```
### Feature Support by Version
| Feature | Vanilla | v2 | v3 |
|---------|---------|----|----|
| Basic Overworld Maps | ✅ | ✅ | ✅ |
| Area Size Enum | ❌ | ❌ | ✅ |
| Main Palette | ❌ | ✅ | ✅ |
| Custom Background Colors | ❌ | ✅ | ✅ |
| Subscreen Overlays | ✅ | ✅ | ✅ |
| Animated GFX | ❌ | ❌ | ✅ |
| Custom Tile Graphics | ❌ | ❌ | ✅ |
| Vanilla Overlays | ✅ | ✅ | ✅ |
**Note:** Subscreen overlays are visual effects (fog, rain, backgrounds, etc.) that are shared between vanilla ROMs and ZSCustomOverworld. ZSCustomOverworld v2+ expands on this by adding support for custom overlay configurations and additional overlay types.
## Overworld Map Structure
### Core Properties
Each overworld map contains the following core properties:
```cpp
class OverworldMap {
// Basic properties
uint8_t index_; // Map index (0-159)
uint8_t parent_; // Parent map ID
uint8_t world_; // World type (0=LW, 1=DW, 2=SW)
uint8_t game_state_; // Game state (0=Beginning, 1=Zelda, 2=Agahnim)
// Graphics and palettes
uint8_t area_graphics_; // Area graphics ID
uint8_t area_palette_; // Area palette ID
uint8_t main_palette_; // Main palette ID (v2+)
std::array<uint8_t, 3> sprite_graphics_; // Sprite graphics IDs
std::array<uint8_t, 3> sprite_palette_; // Sprite palette IDs
// Map properties
uint16_t message_id_; // Message ID
bool mosaic_; // Mosaic effect enabled
bool large_map_; // Is large map (vanilla)
AreaSizeEnum area_size_; // Area size (v3)
// Custom features (v2/v3)
uint16_t area_specific_bg_color_; // Custom background color
uint16_t subscreen_overlay_; // Subscreen overlay ID (references special area maps)
uint8_t animated_gfx_; // Animated graphics ID
std::array<uint8_t, 8> custom_gfx_ids_; // Custom tile graphics
// Overlay support (vanilla and custom)
uint16_t vanilla_overlay_id_; // Vanilla overlay ID
bool has_vanilla_overlay_; // Has vanilla overlay data
std::vector<uint8_t> vanilla_overlay_data_; // Raw overlay data
};
```
## Overlays and Special Area Maps
### Understanding Overlays
Overlays in Zelda 3 are **visual effects** that are displayed over or behind the main overworld map. They include effects like fog, rain, canopy, backgrounds, and other atmospheric elements. Overlays are collections of tile positions and tile IDs that specify where to place specific graphics on the map.
### Special Area Maps (0x80-0x9F)
The special area maps (0x80-0x9F) contain the actual tile data for overlays. These maps store the graphics that overlays reference and use to create visual effects:
- **0x80-0x8F**: Various special area maps containing overlay graphics
- **0x90-0x9F**: Additional special area maps including more overlay graphics
### Overlay ID Mappings
Overlay IDs directly correspond to special area map indices. Common overlay mappings:
| Overlay ID | Special Area Map | Description |
|------------|------------------|-------------|
| 0x0093 | 0x93 | Triforce Room Curtain |
| 0x0094 | 0x94 | Under the Bridge |
| 0x0095 | 0x95 | Sky Background (LW Death Mountain) |
| 0x0096 | 0x96 | Pyramid Background |
| 0x0097 | 0x97 | First Fog Overlay (Master Sword Area) |
| 0x009C | 0x9C | Lava Background (DW Death Mountain) |
| 0x009D | 0x9D | Second Fog Overlay (Lost Woods/Skull Woods) |
| 0x009E | 0x9E | Tree Canopy (Forest) |
| 0x009F | 0x9F | Rain Effect (Misery Mire) |
### Drawing Order
Overlays are drawn in a specific order based on their type:
- **Background Overlays** (0x95, 0x96, 0x9C): Drawn behind the main map tiles
- **Foreground Overlays** (0x9D, 0x97, 0x93, 0x94, 0x9E, 0x9F): Drawn on top of the main map tiles with transparency
### Vanilla Overlay Loading
In vanilla ROMs, overlays are loaded by parsing SNES assembly-like commands that specify tile positions and IDs:
```cpp
absl::Status LoadVanillaOverlay() {
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
// Only load vanilla overlays for vanilla ROMs
if (asm_version != 0xFF) {
has_vanilla_overlay_ = false;
return absl::OkStatus();
}
// Load overlay pointer for this map
int address = (kOverlayPointersBank << 16) +
((*rom_)[kOverlayPointers + (index_ * 2) + 1] << 8) +
(*rom_)[kOverlayPointers + (index_ * 2)];
// Parse overlay commands:
// LDA #$xxxx - Load tile ID into accumulator
// LDX #$xxxx - Load position into X register
// STA $xxxx - Store tile at position
// STA $xxxx,x - Store tile at position + X
// INC A - Increment accumulator (for sequential tiles)
// JMP $xxxx - Jump to another overlay routine
// END (0x60) - End of overlay data
return absl::OkStatus();
}
```
### Special Area Graphics Loading
Special area maps require special handling for graphics loading:
```cpp
void LoadAreaInfo() {
if (parent_ >= kSpecialWorldMapIdStart) {
// Special World (SW) areas
if (asm_version >= 3 && asm_version != 0xFF) {
// Use expanded sprite tables for v3
sprite_graphics_[0] = (*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp +
parent_ - kSpecialWorldMapIdStart];
} else {
// Use original sprite tables for v2/vanilla
sprite_graphics_[0] = (*rom_)[kOverworldSpecialGfxGroup +
parent_ - kSpecialWorldMapIdStart];
}
// Handle special cases for specific maps
if (index_ == 0x88 || index_ == 0x93) {
area_graphics_ = 0x51;
area_palette_ = 0x00;
} else if (index_ == 0x95) {
// Make this the same GFX as LW death mountain areas
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x03];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x03];
} else if (index_ == 0x96) {
// Make this the same GFX as pyramid areas
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x5B];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x5B];
} else if (index_ == 0x9C) {
// Make this the same GFX as DW death mountain areas
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x43];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x43];
}
}
}
```
## Loading Process
### 1. Version Detection
Both editors first detect the ROM version:
```cpp
uint8_t asm_version = rom[OverworldCustomASMHasBeenApplied];
```
### 2. Map Initialization
For each of the 160 overworld maps (0x00-0x9F):
```cpp
// ZScream
var map = new OverworldMap(index, overworld);
// Yaze
OverworldMap map(index, rom);
```
### 3. Property Loading
The loading process varies by ROM version:
#### Vanilla ROMs (asm_version == 0xFF)
```cpp
void LoadAreaInfo() {
// Load from vanilla tables
message_id_ = rom[kOverworldMessageIds + index_ * 2];
area_graphics_ = rom[kOverworldMapGfx + index_];
area_palette_ = rom[kOverworldMapPaletteIds + index_];
// Determine large map status
large_map_ = (rom[kOverworldMapSize + index_] != 0);
// Load vanilla overlay
LoadVanillaOverlay();
}
```
#### ZSCustomOverworld v2/v3
```cpp
void LoadAreaInfo() {
// Use expanded tables for v3
if (asm_version >= 3) {
message_id_ = rom[kOverworldMessagesExpanded + index_ * 2];
area_size_ = static_cast<AreaSizeEnum>(rom[kOverworldScreenSize + index_]);
} else {
message_id_ = rom[kOverworldMessageIds + index_ * 2];
area_size_ = large_map_ ? LargeArea : SmallArea;
}
// Load custom overworld data
LoadCustomOverworldData();
}
```
### 4. Custom Data Loading
For ZSCustomOverworld ROMs:
```cpp
void LoadCustomOverworldData() {
// Load main palette
main_palette_ = rom[OverworldCustomMainPaletteArray + index_];
// Load custom background color
if (rom[OverworldCustomAreaSpecificBGEnabled] != 0) {
area_specific_bg_color_ = rom[OverworldCustomAreaSpecificBGPalette + index_ * 2];
}
// Load v3 features
if (asm_version >= 3) {
subscreen_overlay_ = rom[OverworldCustomSubscreenOverlayArray + index_ * 2];
animated_gfx_ = rom[OverworldCustomAnimatedGFXArray + index_];
// Load custom tile graphics (8 sheets)
for (int i = 0; i < 8; i++) {
custom_gfx_ids_[i] = rom[OverworldCustomTileGFXGroupArray + index_ * 8 + i];
}
}
}
```
## ZScream Implementation
### OverworldMap Constructor
```csharp
public OverworldMap(byte index, Overworld overworld) {
Index = index;
this.overworld = overworld;
// Load area info
LoadAreaInfo();
// Load custom data if available
if (ROM.DATA[Constants.OverworldCustomASMHasBeenApplied] != 0xFF) {
LoadCustomOverworldData();
}
// Build graphics and palette
BuildMap();
}
```
### Key Methods
- `LoadAreaInfo()`: Loads basic map properties from ROM
- `LoadCustomOverworldData()`: Loads ZSCustomOverworld features
- `LoadPalette()`: Loads and processes palette data
- `BuildMap()`: Constructs the final map bitmap
**Note**: ZScream is the original C# implementation that yaze is designed to be compatible with.
## Yaze Implementation
### OverworldMap Constructor
```cpp
OverworldMap::OverworldMap(int index, Rom* rom) : index_(index), rom_(rom) {
LoadAreaInfo();
LoadCustomOverworldData();
SetupCustomTileset(asm_version);
}
```
### Key Methods
- `LoadAreaInfo()`: Loads basic map properties
- `LoadCustomOverworldData()`: Loads ZSCustomOverworld features
- `LoadVanillaOverlay()`: Loads vanilla overlay data
- `LoadPalette()`: Loads and processes palette data
- `BuildTileset()`: Constructs graphics tileset
- `BuildBitmap()`: Creates the final map bitmap
### Current Status
**ZSCustomOverworld v2/v3 Support**: Fully implemented and tested
**Vanilla ROM Support**: Complete compatibility maintained
**Overlay System**: Both vanilla and custom overlays supported
**Map Properties System**: Integrated with UI components
**Graphics Loading**: Optimized with caching and performance monitoring
## Key Differences
### 1. Language and Architecture
| Aspect | ZScream | Yaze |
|--------|---------|------|
| Language | C# | C++ |
| Memory Management | Garbage Collected | Manual (RAII) |
| Graphics | System.Drawing | Custom OpenGL |
| UI Framework | WinForms | ImGui |
### 2. Data Structures
**ZScream:**
```csharp
public class OverworldMap {
public byte Index { get; set; }
public AreaSizeEnum AreaSize { get; set; }
public Bitmap GFXBitmap { get; set; }
// ... other properties
}
```
**Yaze:**
```cpp
class OverworldMap {
uint8_t index_;
AreaSizeEnum area_size_;
std::vector<uint8_t> bitmap_data_;
// ... other member variables
};
```
### 3. Error Handling
**ZScream:** Uses exceptions and try-catch blocks
**Yaze:** Uses `absl::Status` return values and `RETURN_IF_ERROR` macros
### 4. Graphics Processing
**ZScream:** Uses .NET's `Bitmap` class and GDI+
**Yaze:** Uses custom `gfx::Bitmap` class with OpenGL textures
## Common Issues and Solutions
### 1. Version Detection Issues
**Problem:** ROM not recognized as ZSCustomOverworld
**Solution:** Check that `OverworldCustomASMHasBeenApplied` is set correctly
### 2. Palette Loading Errors
**Problem:** Maps appear with wrong colors
**Solution:** Verify palette group addresses and 0xFF fallback handling
### 3. Graphics Not Loading
**Problem:** Blank textures or missing graphics
**Solution:** Check graphics buffer bounds and ProcessGraphicsBuffer implementation
### 4. Overlay Issues
**Problem:** Vanilla overlays not displaying
**Solution:**
- Verify overlay pointer addresses and SNES-to-PC conversion
- Ensure special area maps (0x80-0x9F) are properly loaded with correct graphics
- Check that overlay ID mappings are correct (e.g., 0x009D → map 0x9D)
- Verify that overlay preview shows the actual bitmap of the referenced special area map
**Problem:** Overlay preview showing incorrect information
**Solution:** Ensure overlay preview correctly maps overlay IDs to special area map indices and displays the appropriate bitmap from the special area maps (0x80-0x9F)
### 5. Large Map Problems
**Problem:** Large maps not rendering correctly
**Solution:** Check parent-child relationships and large map detection logic
### 6. Special Area Graphics Issues
**Problem:** Special area maps (0x80-0x9F) showing blank or incorrect graphics
**Solution:**
- Verify special area graphics loading in `LoadAreaInfo()`
- Check that special cases for maps like 0x88, 0x93, 0x95, 0x96, 0x9C are handled correctly
- Ensure proper sprite graphics table selection for v2 vs v3 ROMs
- Verify that special area maps use the correct graphics from referenced LW/DW maps
## Best Practices
### 1. Version-Specific Code
Always check the ASM version before accessing version-specific features:
```cpp
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
if (asm_version >= 3) {
// v3 features
} else if (asm_version == 0xFF) {
// Vanilla features
}
```
### 2. Error Handling
Use proper error handling for ROM operations:
```cpp
absl::Status LoadPalette() {
RETURN_IF_ERROR(LoadPaletteData());
RETURN_IF_ERROR(ProcessPalette());
return absl::OkStatus();
}
```
### 3. Memory Management
Be careful with memory management in C++:
```cpp
// Good: RAII and smart pointers
std::vector<uint8_t> data;
std::unique_ptr<OverworldMap> map;
// Bad: Raw pointers without cleanup
uint8_t* raw_data = new uint8_t[size];
OverworldMap* map = new OverworldMap();
```
### 4. Thread Safety
Both editors use threading for performance:
```cpp
// Yaze: Use std::async for parallel processing
auto future = std::async(std::launch::async, [this](int map_index) {
RefreshChildMap(map_index);
}, map_index);
```
## Conclusion
Understanding the differences between ZScream and yaze implementations is crucial for maintaining compatibility and adding new features. Both editors follow similar patterns but use different approaches due to their respective languages and architectures.
The key is to maintain the same ROM data structure understanding while adapting to each editor's specific implementation patterns.

View File

@@ -1,58 +0,0 @@
# Build Instructions
For VSCode users, use the following CMake extensions
- https://marketplace.visualstudio.com/items?itemName=twxs.cmake
- https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
Yaze uses CMake to build the project. If you are unexperienced with CMake, please refer to the [CMake documentation](https://cmake.org/documentation/).
The gui editor is built using SDL2 and ImGui. For reference on how to use ImGui, see the [Getting Started](https://github.com/ocornut/imgui/wiki/Getting-Started) guide. For SDL2, see the [SDL2 documentation](https://wiki.libsdl.org/).
For those who want to reduce compile times, consider installing the dependencies on your system.
## Windows
Recommended to use [msys2](https://www.msys2.org/) for a Unix-like environment on Windows.
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-sdl2`
- `mingw-w64-x86_64-libpng`
- `mingw-w64-x86_64-abseil-cpp`
For `yaze_py` you will need Boost Python
- `mingw-w64-x86_64-boost`
# macOS
Prefer to use clang provided with XCode command line tools over gcc.
Install the following packages using `brew install <package-name>`
- `cmake`
- `sdl2`
- `zlib`
- `libpng`
- `abseil`
- `boost-python3`
# iOS
Xcode is required to build for iOS. Currently testing with iOS 18 on iPad Pro.
The xcodeproject file is located in the `ios` directory.
You will need to link `SDL2.framework` and `libpng.a` to the project.
# GNU/Linux
You can use your package manager to install the same dependencies as macOS.
I trust you know how to use your package manager.

View File

@@ -1,47 +0,0 @@
# Changelog
## 0.0.1 (06-08-2022)
- Started project
- Added ImGui
- Added SDL2
- Added yaze_test target with gtest
## 0.0.2 - 0.0.4
- TODO: Track changes over this time
## 0.0.5 (11-21-2023)
- DungeonEditor
- DungeonObjectRenderer
## 0.0.6 (11-22-2023)
- ScreenEditor DungeonMap
- Tile16 Editor
- Canvas updates
## 0.0.7 (01-27-2024)
- OverworldEntities
- Entrances
- Exits
- Items
- Sprites
## 0.1.0 (05-11-2024)
- TODO: Track changes over this time
## 0.2.0 (07-20-2024)
- iOS app support
- Graphics Sheet Browser
- Project Files
## 0.2.1 (08-20-2024)
- Improved MessageEditor parsing
- Added integration test window
- Bitmap bug fixes

View File

@@ -1,139 +0,0 @@
# Contributing
This project is looking for contributors to help improve the software and enhance the user experience. If you are interested in contributing, please read the following guidelines and suggestions for areas where you can make a difference.
Discussion on the editor and its development can be found on the [Oracle of Secrets Discord](https://discord.gg/MBFkMTPEmk) server.
## Style Guide
When contributing to the project, please follow these guidelines to ensure consistency and readability across the codebase:
C++ Code should follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) with the following exceptions:
- Boost libraries are allowed, but require cross platform compatibility.
Objective-C Code should follow the [Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.html).
Python Code should follow the [PEP 8 Style Guide](https://pep8.org/).
Assembly code should follow the [65816 Style Guide](docs/asm-style-guide.md).
## Testing Facilities
The project includes the `yaze_test` target which defines unit tests and an integration test window. The unit tests make use of GoogleTest and GoogleMock. The integration test window is an ImGui window build out of the yaze::app::core::Controller and yaze::test::integration::TestEditor. The integration test window can be accessed by passing the argument `integration` to the target.
New modules should define unit tests in the `src/test` directory and integration tests in the `src/test/integration` directory. The `yaze_test` target will automatically include all tests in these directories.
## Key Areas of Contribution
### 1. Extensions System
Yaze *(stylized as yaze)* emphasizes extensibility. The `yaze_ext` library allows developers to build and integrate extensions using C, C++, or Python. This system is central to yaze's modular design, enabling new features, custom editors, or tools to be added without modifying the core codebase.
- C/C++ Extensions: Utilize the `yaze_extension` interface to integrate custom functionality into the editor. You can add new tabs, manipulate ROM data, or extend the editors capabilities with custom tools.
- Python Extensions: Currently unimplemented, Python extensions will allow developers to write scripts that interact with the editor, modify ROM data, or automate repetitive tasks.
Examples of Extensions:
- UI enhancements like additional menus, panels, or status displays.
- Rom manipulation tools for editing data structures, such as the overworld maps or dungeon objects.
- Custom editors for specific tasks, like file format conversion, data visualization, or event scripting.
### 2. Sprite Builder System
The sprite builder system in yaze is based on the [ZSpriteMaker](https://github.com/Zarby89/ZSpriteMaker/) project and allows users to create custom sprites for use in ROM hacks. The goal is to support ZSM files and provide an intuitive interface for editing sprites without the need for writing assembly code. Contributions to the sprite builder system might include:
- Implementing new features for sprite editing, such as palette management, animation preview, or tileset manipulation.
- Extending the sprite builder interface by writing assembly code for sprite behavior.
### 3. Emulator Subsystem
yaze includes an emulator subsystem that allows developers to test their modifications directly within the editor. The emulator can currently run certain test ROMs but lacks the ability to play any complex games with audio because of timing issues with the APU and Spc700. Contributions to the emulator subsystem might include:
- Improving the accuracy and performance of the emulator to support more games and features.
- Implementing new debugging tools, such as memory viewers, breakpoints, or trace logs.
- Extending the emulator to support additional features, such as save states, cheat codes, or multiplayer modes.
### 4. Editor Management
The `EditorManager` class manages the core functionalities of YAZE, including rendering the UI, handling user input, and managing multiple editors. While this class is central to yaze's operations, it has many responsibilities. You can help by:
- Refactoring `EditorManager` to delegate responsibilities to specialized managers (e.g., `MenuManager`, `TabManager`, `StatusManager`).
- Optimizing the rendering and update loop to improve performance, especially when handling large textures or complex editors.
- Implementing new features that streamline the editing process, such as better keyboard shortcuts, command palette integration, or project management tools.
### 5. User Interface and UX
yaze's UI is built with ImGui, offering a flexible and customizable interface. Contributions to the UI might include:
- Designing and implementing new themes or layouts to improve the user experience.
- Adding new UI components, such as toolbars, context menus, or customizable panels.
- Improving the accessibility of the editor, ensuring it is usable by a wide range of users, including those with disabilities.
### 6. ROM Manipulation
The `Rom` class is at the heart of yaze's ability to modify and interact with ROM data. Contributions here might involve:
- Optimizing the loading and saving processes to handle larger ROMs or more complex modifications efficiently.
- Extensions should be able to change the way the `Rom` class interacts with the ROM data with custom pointers to expanded data structures.
### 7. Testing and Documentation
Quality assurance and documentation are critical to yaze's success. Contributions in this area include:
- Writing unit tests for new and existing features to ensure they work correctly and remain stable over time.
- Contributing to the documentation, both for end-users and developers, to make yaze easier to use and extend.
- Creating tutorials or guides that help new developers get started with building extensions or contributing to the project.
## Building the Project
For detailed instructions on building YAZE, including its dependencies and supported platforms, refer to [build-instructions.md](docs/build-instructions.md).
## Getting Started
1. Clone the Repository:
```bash
git clone https://github.com/yourusername/yaze.git
cd yaze
```
2. Initialize the Submodules:
```bash
git submodule update --init --recursive
```
3. Build the Project:
Follow the instructions in the [build-instructions.md](docs/build-instructions.md). file to configure and build the project on your target platform.
4. Run the Application:
After building, you can run the application on your chosen platform and start exploring the existing features.
## Contributing your Changes
1. Fork the Repository:
Create a fork of the project on GitHub and clone your fork to your local machine.
2. Create a Branch:
Create a new branch for your feature or bugfix.
```bash
git checkout -b feature/my-new-feature
```
3. Implement Your Changes:
Follow the guidelines above to implement new features, extensions, or improvements.
4. Test Your Changes:
Ensure your changes dont introduce new bugs or regressions. Write unit tests where applicable.
5. Submit a Pull Request:
Push your changes to your fork and submit a pull request to the main repository. Provide a clear description of your changes and why they are beneficial.

View File

@@ -1,60 +0,0 @@
# Getting Started
This software allows you to modify "The Legend of Zelda: A Link to the Past" (US or JP) ROMs.
This editor is built to be compatible with ZScream projects and is designed to be cross platform.
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.
## General Tips
- Experiment flags determine whether certain features are enabled or not. To change your flags, go to `File` > `Options` > `Experiment Flags` or in the Settings tab.
- Backup files are enabled by default. Each save will produce a timestamped copy of your ROM before you last saved. You can disable this feature in the settings.
## Extending Functionality
In addition to the built-in features, this software provides a pure C library interface and a Python module that can be used for building extensions and custom sprites without assembly. In the editor these can be loaded under the `Extensions` menu.
This feature is still in development and is not yet fully documented.
## Supported Features
| Feature | Status | Details |
|---------|--------|-------------|
| Overworld Maps | Done | Edit and save tile32 data. |
| Overworld Map Properties | Done | Edit and save map properties. |
| Overworld Entrances | Done | Edit and save entrance data. |
| Overworld Exits | Done | Edit and save exit data. |
| Overworld Sprites | In Progress | Edit sprite positions, add and remove sprites. |
| Tile16 Editing | Todo | Edit and save tile16 data. |
| Dungeon | In Progress | View dungeon room metadata and edit room data. |
| Palette | In Progress | Edit and save palettes, palette groups. |
| Graphics Sheets | In Progress | Edit and save graphics sheets. |
| Graphics Groups | Done | Edit and save graphics groups. |
| Sprite | Todo | View-only sprite data. |
| Custom Sprites | Todo | Edit and create custom sprite data. |
| Music | Todo | Edit music data. |
| Dungeon Maps | Todo | Edit dungeon maps. |
| Scad Format | Done-ish | Open and view scad files (SCR, CGX, COL) |
| Hex Editing | Done | View and edit ROM data in hex. |
| Asar Patching | In Progress | Apply Asar patches to your ROM or Project. |
## Command Line Interface
Included with the editor is a command line interface (CLI) that allows you to perform various operations on your ROMs from the command line. This aims to reduce the need for multiple tools in zelda3 hacking like Zcompress, LunarExpand, LunarAddress, Asar, and others.
| 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 |
| Asar Patch | -asar | asm_file rom_file | In progress |
| 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 |

43
docs/index.md Normal file
View File

@@ -0,0 +1,43 @@
# YAZE Documentation
Yet Another Zelda3 Editor - A comprehensive ROM editor for The Legend of Zelda: A Link to the Past.
## Quick Start
- [Getting Started](01-getting-started.md) - Basic setup and usage
- [Build Instructions](02-build-instructions.md) - Cross-platform build guide
- [Asar Integration](03-asar-integration.md) - 65816 assembler usage
- [API Reference](04-api-reference.md) - C/C++ API documentation
## Development
- [Testing Guide](A1-testing-guide.md) - Testing framework and best practices
- [Contributing](B1-contributing.md) - Development guidelines and standards
- [Platform Compatibility](B2-platform-compatibility.md) - Cross-platform support details
- [Build Presets](B3-build-presets.md) - CMake preset usage guide
## Technical Documentation
### Assembly & Code
- [Assembly Style Guide](E1-asm-style-guide.md) - 65816 assembly coding standards
### Editor Systems
- [Dungeon Editor Guide](E2-dungeon-editor-guide.md) - Complete dungeon editing guide
- [Dungeon Editor Design](E3-dungeon-editor-design.md) - Architecture and development guide
- [Dungeon Editor Refactoring](E4-dungeon-editor-refactoring.md) - Component-based architecture plan
- [Dungeon Object System](E5-dungeon-object-system.md) - Object management framework
### Overworld System
- [Overworld Loading](F1-overworld-loading.md) - ZSCustomOverworld v3 implementation
## Key Features
- Complete GUI editor for all aspects of Zelda 3 ROM hacking
- Integrated Asar 65816 assembler for custom code patches
- ZSCustomOverworld v3 support for enhanced overworld editing
- Cross-platform support (Windows, macOS, Linux)
- Modern C++23 codebase with comprehensive testing
---
*Last updated: September 2025 - Version 0.3.0*

View File

@@ -1,86 +0,0 @@
# Infrastructure Overview
For developers to reference.
The goal of yaze is to build a cross platform editor for the Legend of Zelda: A Link to the Past. The project is built using C++20, SDL2, and ImGui. The project is built using CMake and is designed to be modular and extensible. The project is designed to be built on Windows, macOS, iOS, and Linux.
## Targets
- **yaze**: Desktop application for Windows/macOS/Linux
- **z3ed**: Command Line Interface
- **yaze_c**: C Library
- **yaze_py**: Python Module
- **yaze_test**: Unit test executable
- **yaze_ios**: iOS application
## Directory Structure
- **assets**: Hosts assets like fonts, icons, assembly source, etc.
- **cmake**: Contains CMake configurations.
- **docs**: Contains documentation for users and developers.
- **src**: Contains source files.
- **app**: Contains the GUI editor `yaze`
- **app/emu**: Contains a standalone Snes emulator application `yaze_emu`
- **cli**: Contains the command line interface `z3ed`
- **incl**: Contains the data headers for `yaze_c`
- **ios**: Contains the iOS application `yaze_ios`
- **lib**: Contains the dependencies as git submodules
- **py**: Contains the Python module `yaze_py`
- **test**: Contains testing interface `yaze_test`
- **win32**: Contains Windows resource file and icon
## Dependencies
See [build-instructions.md](docs/build-instructions.md) for more information.
- **SDL2**: Graphics library
- **ImGui**: GUI library
- **Abseil**: C++ library
- **libpng**: Image library
- **Boost**: Python library
## Flow of Control
- app/yaze.cc
- Initializes `absl::FailureSignalHandler` for stack tracing.
- Runs the `core::Controller` loop.
- app/core/controller.cc
- Initializes SDLRenderer and SDLWindow
- Initializes ImGui, fonts, themes, and clipboard.
- Handles user input from keyboard and mouse.
- Renders the output to the screen.
- Handles the teardown of SDL and ImGui resources.
- app/editor/editor_manager.cc
- Handles the main menu bar
- Handles `absl::Status` errors as popups delivered to the user.
- Dispatches messages to the various editors.
- Update all the editors in a tab view.
- app/editor/code/assembly_editor.cc
- app/editor/dungeon/dungeon_editor.cc
- app/editor/graphics/graphics_editor.cc
- app/editor/graphics/gfx_group_editor.cc
- app/editor/graphics/palette_editor.cc
- app/editor/graphics/tile16_editor.cc
- app/editor/message/message_editor.cc
- app/editor/music/music_editor.cc
- app/editor/overworld/overworld_editor.cc
- app/editor/graphics/screen_editor.cc
- app/editor/sprite/sprite_editor.cc
- app/editor/system/settings_editor.cc
## Rom
- app/rom.cc
- app/rom.h
The Rom class provides methods to manipulate and access data from a ROM.
Currently implemented as a singleton with SharedRom which is not great but has helped with development velocity. Potential room for improvement is to refactor the editors to take the ROM as a parameter.
## Bitmap
- app/gfx/bitmap.cc
- app/gfx/bitmap.h
This class is responsible for creating, managing, and manipulating bitmap data, which can be displayed on the screen using SDL2 Textures and the ImGui draw list. It also provides functions for exporting these bitmaps to the clipboard in PNG format using libpng.

View File

@@ -1,61 +0,0 @@
#ifndef YAZE_BASE_DUNGEON_H_
#define YAZE_BASE_DUNGEON_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
typedef struct z3_object_door {
short id;
uint8_t x;
uint8_t y;
uint8_t size;
uint8_t type;
uint8_t layer;
} z3_object_door;
typedef struct z3_dungeon_destination {
uint8_t index;
uint8_t target;
uint8_t target_layer;
} z3_dungeon_destination;
typedef struct z3_staircase {
uint8_t id;
uint8_t room;
const char *label;
} z3_staircase;
typedef struct z3_chest {
uint8_t x;
uint8_t y;
uint8_t item;
bool picker;
bool big_chest;
} z3_chest;
typedef struct z3_chest_data {
uint8_t id;
bool size;
} z3_chest_data;
typedef enum z3_dungeon_background2 {
Off,
Parallax,
Dark,
OnTop,
Translucent,
Addition,
Normal,
Transparent,
DarkRoom
} z3_dungeon_background2;
#ifdef __cplusplus
}
#endif
#endif // YAZE_BASE_DUNGEON_H_

View File

@@ -1,47 +0,0 @@
#ifndef YAZE_OVERWORLD_H
#define YAZE_OVERWORLD_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "sprite.h"
/**
* @brief Primitive of an overworld map.
*/
typedef struct z3_overworld_map {
uint8_t id; /**< ID of the overworld map. */
uint8_t parent_id;
uint8_t quadrant_id;
uint8_t world_id;
uint8_t game_state;
uint8_t area_graphics;
uint8_t area_palette;
uint8_t sprite_graphics[3];
uint8_t sprite_palette[3];
uint8_t area_music[4];
uint8_t static_graphics[16];
} z3_overworld_map;
/**
* @brief Primitive of the overworld.
*/
typedef struct z3_overworld {
void *impl; // yaze::app::Overworld*
uint8_t *tile32_data; /**< Pointer to the 32x32 tile data. */
uint8_t *tile16_data; /**< Pointer to the 16x16 tile data. */
z3_sprite **sprites; /**< Pointer to the sprites per map. */
z3_overworld_map **maps; /**< Pointer to the overworld maps. */
} z3_overworld;
#ifdef __cplusplus
}
#endif
#endif // YAZE_OVERWORLD_H

View File

@@ -1,32 +0,0 @@
#ifndef YAZE_BASE_SNES_COLOR_H_
#define YAZE_BASE_SNES_COLOR_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* @brief Primitive of 16-bit RGB SNES color.
*/
typedef struct snes_color {
uint16_t red; /**< Red component of the color. */
uint16_t blue; /**< Blue component of the color. */
uint16_t green; /**< Green component of the color. */
} snes_color;
/**
* @brief Primitive of a SNES color palette.
*/
typedef struct snes_palette {
unsigned int id; /**< ID of the palette. */
unsigned int size; /**< Size of the palette. */
snes_color* colors; /**< Pointer to the colors in the palette. */
} snes_palette;
#ifdef __cplusplus
}
#endif
#endif // YAZE_BASE_SNES_COLOR_H_

View File

@@ -1,40 +0,0 @@
#ifndef YAZE_INCL_SNES_TILE_H
#define YAZE_INCL_SNES_TILE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
typedef struct snes_tile8 {
uint32_t id;
uint32_t palette_id;
uint8_t data[64];
} snes_tile8;
typedef struct snes_tile_info {
uint16_t id;
uint8_t palette;
bool priority;
bool vertical_mirror;
bool horizontal_mirror;
} snes_tile_info;
typedef struct snes_tile16 {
snes_tile_info tiles[4];
} snes_tile16;
typedef struct snes_tile32 {
uint16_t t0;
uint16_t t1;
uint16_t t2;
uint16_t t3;
} snes_tile32;
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,23 +0,0 @@
#ifndef YAZE_BASE_SPRITE_H_
#define YAZE_BASE_SPRITE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* @brief Primitive of a sprite.
*/
typedef struct z3_sprite {
const char* name; /**< Name of the sprite. */
uint8_t id; /**< ID of the sprite. */
uint8_t subtype; /**< Subtype of the sprite. */
} z3_sprite;
#ifdef __cplusplus
}
#endif
#endif // YAZE_BASE_SPRITE_H_

View File

@@ -1,91 +0,0 @@
#ifndef EXTENSION_INTERFACE_H
#define EXTENSION_INTERFACE_H
#ifdef __cplusplus
extern "C" {
#endif
#include "yaze.h"
typedef void (*yaze_initialize_func)(yaze_editor_context* context);
typedef void (*yaze_cleanup_func)(void);
typedef void (*yaze_extend_ui_func)(yaze_editor_context* context);
typedef void (*yaze_manipulate_rom_func)(z3_rom* rom);
typedef void (*yaze_command_func)(void);
typedef void (*yaze_event_hook_func)(void);
typedef enum {
YAZE_EVENT_ROM_LOADED,
YAZE_EVENT_ROM_SAVED,
YAZE_EVENT_SPRITE_MODIFIED,
YAZE_EVENT_PALETTE_CHANGED,
} yaze_event_type;
/**
* @brief Extension interface for Yaze.
*
* @details Yaze extensions can be written in C or Python.
*/
typedef struct yaze_extension {
const char* name;
const char* version;
/**
* @brief Function to initialize the extension.
*
* @details This function is called when the extension is loaded. It can be
* used to set up any resources or state needed by the extension.
*/
yaze_initialize_func initialize;
/**
* @brief Function to clean up the extension.
*
* @details This function is called when the extension is unloaded. It can be
* used to clean up any resources or state used by the extension.
*/
yaze_cleanup_func cleanup;
/**
* @brief Function to manipulate the ROM.
*
* @param rom The ROM to manipulate.
*
*/
yaze_manipulate_rom_func manipulate_rom;
/**
* @brief Function to extend the UI.
*
* @param context The editor context.
*
* @details This function is called when the extension is loaded. It can be
* used to add custom UI elements to the editor. The context parameter
* provides access to the project, command registry, event dispatcher, and
* ImGui context.
*/
yaze_extend_ui_func extend_ui;
/**
* @brief Register commands in the yaze_command_registry.
*/
yaze_command_func register_commands;
/**
* @brief Register custom tools in the yaze_command_registry.
*/
yaze_command_func register_custom_tools;
/**
* @brief Register event hooks in the yaze_event_dispatcher.
*/
void (*register_event_hooks)(yaze_event_type event,
yaze_event_hook_func hook);
} yaze_extension;
#ifdef __cplusplus
}
#endif
#endif // EXTENSION_INTERFACE_H

View File

@@ -1,122 +1,585 @@
#ifndef YAZE_H #ifndef YAZE_H
#define YAZE_H #define YAZE_H
/**
* @file yaze.h
* @brief Yet Another Zelda3 Editor (YAZE) - Public C API
*
* This header provides the main C API for YAZE, a modern ROM editor for
* The Legend of Zelda: A Link to the Past. This API allows external
* applications to interact with YAZE's functionality.
*
* @version 0.3.0
* @author YAZE Team
*/
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "dungeon.h" #include "zelda.h"
#include "overworld.h"
#include "snes_color.h"
#include "sprite.h"
typedef struct z3_rom z3_rom;
typedef struct yaze_project yaze_project;
typedef struct yaze_command_registry yaze_command_registry;
typedef struct yaze_event_dispatcher yaze_event_dispatcher;
/** /**
* @brief Extension editor context. * @defgroup version Version Information
* @{
*/ */
typedef struct yaze_editor_context {
z3_rom* rom;
yaze_project* project;
yaze_command_registry* command_registry; /** Major version number */
yaze_event_dispatcher* event_dispatcher; #define YAZE_VERSION_MAJOR 0
/** Minor version number */
#define YAZE_VERSION_MINOR 3
/** Patch version number */
#define YAZE_VERSION_PATCH 0
/** Combined version as a string */
#define YAZE_VERSION_STRING "0.3.0"
/** Combined version as a number (major * 10000 + minor * 100 + patch) */
#define YAZE_VERSION_NUMBER 300
/** @} */
typedef struct yaze_editor_context {
zelda3_rom* rom;
const char* error_message;
} yaze_editor_context; } yaze_editor_context;
/** /**
* @brief Initialize the Yaze library. * @defgroup core Core API
* @{
*/ */
int yaze_init(yaze_editor_context*);
/** /**
* @brief Clean up the Yaze library. * @brief Status codes returned by YAZE functions
*
* All YAZE functions that can fail return a status code to indicate
* success or the type of error that occurred.
*/ */
void yaze_cleanup(yaze_editor_context*); typedef enum yaze_status {
YAZE_OK = 0, /**< Operation completed successfully */
YAZE_ERROR_UNKNOWN = -1, /**< Unknown error occurred */
YAZE_ERROR_INVALID_ARG = 1, /**< Invalid argument provided */
YAZE_ERROR_FILE_NOT_FOUND = 2, /**< File not found */
YAZE_ERROR_MEMORY = 3, /**< Memory allocation failed */
YAZE_ERROR_IO = 4, /**< I/O operation failed */
YAZE_ERROR_CORRUPTION = 5, /**< Data corruption detected */
YAZE_ERROR_NOT_INITIALIZED = 6, /**< Component not initialized */
} yaze_status;
/** /**
* @brief Primitive of a Yaze project. * @brief Convert a status code to a human-readable string
*
* @param status The status code to convert
* @return A null-terminated string describing the status
*/ */
struct yaze_project { const char* yaze_status_to_string(yaze_status status);
const char* name;
const char* filepath;
const char* rom_filename;
const char* code_folder;
const char* labels_filename;
};
yaze_project yaze_load_project(const char* filename);
/** /**
* @brief Primitive of a Zelda3 ROM. * @brief Initialize the YAZE library
*
* This function must be called before using any other YAZE functions.
* It initializes internal subsystems and prepares the library for use.
*
* @return YAZE_OK on success, error code on failure
*/ */
struct z3_rom { yaze_status yaze_library_init(void);
const char* filename;
const uint8_t* data;
size_t size;
void* impl; // yaze::app::Rom*
};
/** /**
* @brief Load a Zelda3 ROM from a file. * @brief Shutdown the YAZE library
*
* This function cleans up resources allocated by yaze_library_init().
* After calling this function, no other YAZE functions should be called
* until yaze_library_init() is called again.
*/ */
z3_rom* yaze_load_rom(const char* filename); void yaze_library_shutdown(void);
/** /**
* @brief Unload a Zelda3 ROM. * @brief Main entry point for the YAZE application
*
* @param argc Number of command line arguments
* @param argv Array of command line argument strings
* @return Exit code (0 for success, non-zero for error)
*/ */
void yaze_unload_rom(z3_rom* rom); int yaze_app_main(int argc, char** argv);
/** /**
* @brief Primitive of a Bitmap * @brief Check if the current YAZE version is compatible with the expected version
*
* @param expected_version Expected version string (e.g., "0.3.0")
* @return true if compatible, false otherwise
*/
bool yaze_check_version_compatibility(const char* expected_version);
/**
* @brief Get the current YAZE version string
*
* @return A null-terminated string containing the version
*/
const char* yaze_get_version_string(void);
/**
* @brief Get the current YAZE version number
*
* @return Version number (major * 10000 + minor * 100 + patch)
*/
int yaze_get_version_number(void);
/**
* @brief Initialize a YAZE editor context
*
* Creates and initializes an editor context for working with ROM files.
* The context manages the ROM data and provides access to editing functions.
*
* @param context Pointer to context structure to initialize
* @param rom_filename Path to the ROM file to load (can be NULL to create empty context)
* @return YAZE_OK on success, error code on failure
*
* @note The caller is responsible for calling yaze_shutdown() to clean up the context
*/
yaze_status yaze_init(yaze_editor_context* context, const char* rom_filename);
/**
* @brief Shutdown and clean up a YAZE editor context
*
* Releases all resources associated with the context, including ROM data.
* After calling this function, the context should not be used.
*
* @param context Pointer to context to shutdown
* @return YAZE_OK on success, error code on failure
*/
yaze_status yaze_shutdown(yaze_editor_context* context);
/** @} */
/**
* @defgroup graphics Graphics and Bitmap Functions
* @{
*/
/**
* @brief Bitmap data structure
*
* Represents a bitmap image with pixel data and metadata.
*/ */
typedef struct yaze_bitmap { typedef struct yaze_bitmap {
int width; int width; /**< Width in pixels */
int height; int height; /**< Height in pixels */
uint8_t bpp; uint8_t bpp; /**< Bits per pixel (1, 2, 4, 8) */
uint8_t* data; uint8_t* data; /**< Pixel data (caller owns memory) */
} yaze_bitmap; } yaze_bitmap;
/** /**
* @brief Load a bitmap from a file. * @brief Load a bitmap from file
*
* Loads a bitmap image from the specified file. Supports common
* image formats and SNES-specific formats.
*
* @param filename Path to the image file
* @return Bitmap structure with loaded data, or empty bitmap on error
*
* @note The caller is responsible for freeing the data pointer
*/ */
yaze_bitmap yaze_load_bitmap(const char* filename); yaze_bitmap yaze_load_bitmap(const char* filename);
/** /**
* @brief Get a color from a palette set. * @brief Free bitmap data
*
* Releases memory allocated for bitmap pixel data.
*
* @param bitmap Pointer to bitmap structure to free
*/ */
snes_color yaze_get_color_from_paletteset(const z3_rom* rom, int palette_set, void yaze_free_bitmap(yaze_bitmap* bitmap);
int palette, int color);
/** /**
* @brief Load the overworld from a Zelda3 ROM. * @brief Create an empty bitmap
*
* Allocates a new bitmap with the specified dimensions.
*
* @param width Width in pixels
* @param height Height in pixels
* @param bpp Bits per pixel
* @return Initialized bitmap structure, or empty bitmap on error
*/ */
z3_overworld* yaze_load_overworld(const z3_rom* rom); yaze_bitmap yaze_create_bitmap(int width, int height, uint8_t bpp);
/** /**
* @brief Check the version of the Yaze library. * @brief SNES color in 15-bit RGB format (BGR555)
*
* Represents a color in the SNES native format. Colors are stored
* as 8-bit values but only the lower 5 bits are used by the SNES.
*/ */
void yaze_check_version(const char* version); typedef struct snes_color {
uint16_t red; /**< Red component (0-255, but SNES uses 0-31) */
uint16_t green; /**< Green component (0-255, but SNES uses 0-31) */
uint16_t blue; /**< Blue component (0-255, but SNES uses 0-31) */
} snes_color;
/** /**
* @brief Command registry. * @brief Convert RGB888 color to SNES color
*
* @param r Red component (0-255)
* @param g Green component (0-255)
* @param b Blue component (0-255)
* @return SNES color structure
*/ */
struct yaze_command_registry { snes_color yaze_rgb_to_snes_color(uint8_t r, uint8_t g, uint8_t b);
void (*register_command)(const char* name, void (*command)(void));
};
/** /**
* @brief Event dispatcher. * @brief Convert SNES color to RGB888
*
* @param color SNES color to convert
* @param r Pointer to store red component (0-255)
* @param g Pointer to store green component (0-255)
* @param b Pointer to store blue component (0-255)
*/ */
struct yaze_event_dispatcher { void yaze_snes_color_to_rgb(snes_color color, uint8_t* r, uint8_t* g, uint8_t* b);
void (*register_event_hook)(void (*event_hook)(void));
}; /**
* @brief SNES color palette
*
* Represents a color palette used by the SNES. Each palette contains
* up to 256 colors, though most modes use fewer colors per palette.
*/
typedef struct snes_palette {
uint16_t id; /**< Palette ID (0-255) */
uint16_t size; /**< Number of colors in palette (1-256) */
snes_color* colors; /**< Array of colors (caller owns memory) */
} snes_palette;
/**
* @brief Create an empty palette
*
* @param id Palette ID
* @param size Number of colors to allocate
* @return Initialized palette structure, or NULL on error
*/
snes_palette* yaze_create_palette(uint16_t id, uint16_t size);
/**
* @brief Free palette memory
*
* @param palette Pointer to palette to free
*/
void yaze_free_palette(snes_palette* palette);
/**
* @brief Load palette from ROM
*
* @param rom ROM to load palette from
* @param palette_id ID of palette to load
* @return Loaded palette, or NULL on error
*/
snes_palette* yaze_load_palette_from_rom(const zelda3_rom* rom, uint16_t palette_id);
/**
* @brief 8x8 SNES tile data
*
* Represents an 8x8 pixel tile with indexed color data.
* Each pixel value is an index into a palette.
*/
typedef struct snes_tile8 {
uint32_t id; /**< Tile ID for reference */
uint32_t palette_id; /**< Associated palette ID */
uint8_t data[64]; /**< 64 pixels in row-major order (y*8+x) */
} snes_tile8;
/**
* @brief Load tile data from ROM
*
* @param rom ROM to load from
* @param tile_id ID of tile to load
* @param bpp Bits per pixel (1, 2, 4, 8)
* @return Loaded tile data, or empty tile on error
*/
snes_tile8 yaze_load_tile_from_rom(const zelda3_rom* rom, uint32_t tile_id, uint8_t bpp);
/**
* @brief Convert tile data between different bit depths
*
* @param tile Source tile data
* @param from_bpp Source bits per pixel
* @param to_bpp Target bits per pixel
* @return Converted tile data
*/
snes_tile8 yaze_convert_tile_bpp(const snes_tile8* tile, uint8_t from_bpp, uint8_t to_bpp);
typedef struct snes_tile_info {
uint16_t id;
uint8_t palette;
bool priority;
bool vertical_mirror;
bool horizontal_mirror;
} snes_tile_info;
typedef struct snes_tile16 {
snes_tile_info tiles[4];
} snes_tile16;
typedef struct snes_tile32 {
uint16_t t0;
uint16_t t1;
uint16_t t2;
uint16_t t3;
} snes_tile32;
/** @} */
/**
* @defgroup rom ROM Manipulation
* @{
*/
/**
* @brief Load a ROM file
*
* Loads a Zelda 3 ROM file and validates its format.
*
* @param filename Path to ROM file (.sfc, .smc, etc.)
* @return Pointer to ROM structure, or NULL on error
*
* @note Caller must call yaze_unload_rom() to free memory
*/
zelda3_rom* yaze_load_rom_file(const char* filename);
/**
* @brief Validate ROM integrity
*
* Checks if the ROM data is valid and uncorrupted.
*
* @param rom ROM to validate
* @return YAZE_OK if valid, error code if corrupted
*/
yaze_status yaze_validate_rom(const zelda3_rom* rom);
/**
* @brief Get ROM information
*
* @param rom ROM to query
* @param version Pointer to store detected ROM version
* @param size Pointer to store ROM size in bytes
* @return YAZE_OK on success, error code on failure
*/
yaze_status yaze_get_rom_info(const zelda3_rom* rom, zelda3_version* version, uint64_t* size);
/**
* @brief Get a color from a palette set
*
* Retrieves a specific color from a palette set in the ROM.
*
* @param rom The ROM to get the color from
* @param palette_set The palette set index (0-255)
* @param palette The palette index within the set (0-15)
* @param color The color index within the palette (0-15)
* @return The color from the palette set
*/
snes_color yaze_get_color_from_paletteset(const zelda3_rom* rom,
int palette_set, int palette,
int color);
/** @} */
/**
* @defgroup overworld Overworld Functions
* @{
*/
/**
* @brief Load the overworld from ROM
*
* Loads and parses the overworld data from the ROM, including all maps,
* sprites, and related data structures.
*
* @param rom The ROM to load the overworld from
* @return Pointer to overworld structure, or NULL on error
*
* @note Caller must free the returned pointer when done
*/
zelda3_overworld* yaze_load_overworld(const zelda3_rom* rom);
/**
* @brief Free overworld data
*
* @param overworld Pointer to overworld to free
*/
void yaze_free_overworld(zelda3_overworld* overworld);
/**
* @brief Get overworld map by index
*
* @param overworld Overworld data
* @param map_index Map index (0-159 for most ROMs)
* @return Pointer to map data, or NULL if invalid index
*/
const zelda3_overworld_map* yaze_get_overworld_map(const zelda3_overworld* overworld, int map_index);
/**
* @brief Get total number of overworld maps
*
* @param overworld Overworld data
* @return Number of maps available
*/
int yaze_get_overworld_map_count(const zelda3_overworld* overworld);
/** @} */
/**
* @defgroup dungeon Dungeon Functions
* @{
*/
/**
* @brief Load all dungeon rooms from ROM
*
* Loads and parses all dungeon room data from the ROM.
*
* @param rom The ROM to load rooms from
* @param room_count Pointer to store the number of rooms loaded
* @return Array of room structures, or NULL on error
*
* @note Caller must free the returned array when done
*/
zelda3_dungeon_room* yaze_load_all_rooms(const zelda3_rom* rom, int* room_count);
/**
* @brief Load a specific dungeon room
*
* @param rom ROM to load from
* @param room_id Room ID to load (0-295 for most ROMs)
* @return Pointer to room data, or NULL on error
*/
const zelda3_dungeon_room* yaze_load_room(const zelda3_rom* rom, int room_id);
/**
* @brief Free dungeon room data
*
* @param rooms Array of rooms to free
* @param room_count Number of rooms in array
*/
void yaze_free_rooms(zelda3_dungeon_room* rooms, int room_count);
/** @} */
/**
* @defgroup messages Message System
* @{
*/
/**
* @brief Load all text messages from ROM
*
* Loads and parses all in-game text messages from the ROM.
*
* @param rom The ROM to load messages from
* @param messages Pointer to store array of messages
* @param message_count Pointer to store number of messages loaded
* @return YAZE_OK on success, error code on failure
*
* @note Caller must free the messages array when done
*/
yaze_status yaze_load_messages(const zelda3_rom* rom, zelda3_message** messages, int* message_count);
/**
* @brief Get a specific message by ID
*
* @param rom ROM to load from
* @param message_id Message ID to retrieve
* @return Pointer to message data, or NULL if not found
*/
const zelda3_message* yaze_get_message(const zelda3_rom* rom, int message_id);
/**
* @brief Free message data
*
* @param messages Array of messages to free
* @param message_count Number of messages in array
*/
void yaze_free_messages(zelda3_message* messages, int message_count);
/** @} */
/**
* @brief Function pointer to initialize the extension.
*
* @param context The editor context.
*/
typedef void (*yaze_initialize_func)(yaze_editor_context* context);
typedef void (*yaze_cleanup_func)(void);
/**
* @defgroup extensions Extension System
* @{
*/
/**
* @brief Extension interface for YAZE
*
* Defines the interface for YAZE extensions. Extensions can add new
* functionality to YAZE and can be written in C or other languages.
*/
typedef struct yaze_extension {
const char* name; /**< Extension name (must not be NULL) */
const char* version; /**< Extension version string */
const char* description; /**< Brief description of functionality */
const char* author; /**< Extension author */
int api_version; /**< Required YAZE API version */
/**
* @brief Initialize the extension
*
* Called when the extension is loaded. Use this to set up
* any resources or state needed by the extension.
*
* @param context Editor context provided by YAZE
* @return YAZE_OK on success, error code on failure
*/
yaze_status (*initialize)(yaze_editor_context* context);
/**
* @brief Clean up the extension
*
* Called when the extension is unloaded. Use this to clean up
* any resources or state used by the extension.
*/
void (*cleanup)(void);
/**
* @brief Get extension capabilities
*
* Returns a bitmask indicating what features this extension provides.
*
* @return Capability flags (see YAZE_EXT_CAP_* constants)
*/
uint32_t (*get_capabilities)(void);
} yaze_extension;
/** Extension capability flags */
#define YAZE_EXT_CAP_ROM_EDITING (1 << 0) /**< Can edit ROM data */
#define YAZE_EXT_CAP_GRAPHICS (1 << 1) /**< Provides graphics functions */
#define YAZE_EXT_CAP_AUDIO (1 << 2) /**< Provides audio functions */
#define YAZE_EXT_CAP_SCRIPTING (1 << 3) /**< Provides scripting support */
#define YAZE_EXT_CAP_IMPORT_EXPORT (1 << 4) /**< Can import/export data */
/**
* @brief Register an extension with YAZE
*
* @param extension Extension to register
* @return YAZE_OK on success, error code on failure
*/
yaze_status yaze_register_extension(const yaze_extension* extension);
/**
* @brief Unregister an extension
*
* @param name Name of extension to unregister
* @return YAZE_OK on success, error code on failure
*/
yaze_status yaze_unregister_extension(const char* name);
/** @} */
#ifdef __cplusplus #ifdef __cplusplus
} }

499
incl/zelda.h Normal file
View File

@@ -0,0 +1,499 @@
#ifndef ZELDA_H
#define ZELDA_H
/**
* @file zelda.h
* @brief The Legend of Zelda: A Link to the Past - Data Structures and Constants
*
* This header defines data structures and constants specific to
* The Legend of Zelda: A Link to the Past ROM format and game data.
*
* @version 0.3.0
* @author YAZE Team
*/
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
/**
* @defgroup rom_types ROM Types and Versions
* @{
*/
/**
* @brief Different versions of the game supported by YAZE
*
* YAZE supports multiple regional versions and ROM hacks of
* The Legend of Zelda: A Link to the Past.
*/
typedef enum zelda3_version {
ZELDA3_VERSION_UNKNOWN = 0, /**< Unknown or unsupported version */
ZELDA3_VERSION_US = 1, /**< US/North American version */
ZELDA3_VERSION_JP = 2, /**< Japanese version */
ZELDA3_VERSION_EU = 3, /**< European version */
ZELDA3_VERSION_PROTO = 4, /**< Prototype/development version */
ZELDA3_VERSION_RANDOMIZER = 5, /**< Randomizer ROM (experimental) */
// Legacy aliases for backward compatibility
US = ZELDA3_VERSION_US, /**< @deprecated Use ZELDA3_VERSION_US */
JP = ZELDA3_VERSION_JP, /**< @deprecated Use ZELDA3_VERSION_JP */
SD = ZELDA3_VERSION_PROTO, /**< @deprecated Use ZELDA3_VERSION_PROTO */
RANDO = ZELDA3_VERSION_RANDOMIZER, /**< @deprecated Use ZELDA3_VERSION_RANDOMIZER */
} zelda3_version;
/**
* @brief Detect ROM version from header data
*
* @param rom_data Pointer to ROM data
* @param size Size of ROM data in bytes
* @return Detected version, or ZELDA3_VERSION_UNKNOWN if not recognized
*/
zelda3_version zelda3_detect_version(const uint8_t* rom_data, size_t size);
/**
* @brief Get version name as string
*
* @param version Version enum value
* @return Human-readable version name
*/
const char* zelda3_version_to_string(zelda3_version version);
/**
* @brief ROM data pointers for different game versions
*
* Contains memory addresses where specific data structures are located
* within the ROM. These addresses vary between different regional versions.
*/
typedef struct zelda3_version_pointers {
// New Google C++ style names
uint32_t gfx_animated_pointer; /**< Animated graphics pointer */
uint32_t overworld_gfx_groups1; /**< Overworld graphics group 1 */
uint32_t overworld_gfx_groups2; /**< Overworld graphics group 2 */
uint32_t compressed_map32_pointers_high; /**< Map32 high pointers */
uint32_t compressed_map32_pointers_low; /**< Map32 low pointers */
uint32_t overworld_map_palette_group; /**< Map palette groups */
uint32_t overlay_pointers; /**< Overlay data pointers */
uint32_t overlay_pointers_bank; /**< Overlay bank number */
uint32_t overworld_tiles_type; /**< Tile type definitions */
uint32_t overworld_gfx_ptr1; /**< Graphics pointer 1 */
uint32_t overworld_gfx_ptr2; /**< Graphics pointer 2 */
uint32_t overworld_gfx_ptr3; /**< Graphics pointer 3 */
uint32_t map32_tile_tl; /**< 32x32 tile top-left */
uint32_t map32_tile_tr; /**< 32x32 tile top-right */
uint32_t map32_tile_bl; /**< 32x32 tile bottom-left */
uint32_t map32_tile_br; /**< 32x32 tile bottom-right */
uint32_t sprite_blockset_pointer; /**< Sprite graphics pointer */
uint32_t dungeon_palettes_groups; /**< Dungeon palette groups */
// Legacy aliases for backward compatibility (deprecated)
uint32_t kGfxAnimatedPointer; /**< @deprecated Use gfx_animated_pointer */
uint32_t kOverworldGfxGroups1; /**< @deprecated Use overworld_gfx_groups1 */
uint32_t kOverworldGfxGroups2; /**< @deprecated Use overworld_gfx_groups2 */
uint32_t kCompressedAllMap32PointersHigh; /**< @deprecated Use compressed_map32_pointers_high */
uint32_t kCompressedAllMap32PointersLow; /**< @deprecated Use compressed_map32_pointers_low */
uint32_t kOverworldMapPaletteGroup; /**< @deprecated Use overworld_map_palette_group */
uint32_t kOverlayPointers; /**< @deprecated Use overlay_pointers */
uint32_t kOverlayPointersBank; /**< @deprecated Use overlay_pointers_bank */
uint32_t kOverworldTilesType; /**< @deprecated Use overworld_tiles_type */
uint32_t kOverworldGfxPtr1; /**< @deprecated Use overworld_gfx_ptr1 */
uint32_t kOverworldGfxPtr2; /**< @deprecated Use overworld_gfx_ptr2 */
uint32_t kOverworldGfxPtr3; /**< @deprecated Use overworld_gfx_ptr3 */
uint32_t kMap32TileTL; /**< @deprecated Use map32_tile_tl */
uint32_t kMap32TileTR; /**< @deprecated Use map32_tile_tr */
uint32_t kMap32TileBL; /**< @deprecated Use map32_tile_bl */
uint32_t kMap32TileBR; /**< @deprecated Use map32_tile_br */
uint32_t kSpriteBlocksetPointer; /**< @deprecated Use sprite_blockset_pointer */
uint32_t kDungeonPalettesGroups; /**< @deprecated Use dungeon_palettes_groups */
} zelda3_version_pointers;
/**
* @brief Get version-specific pointers
*
* @param version ROM version
* @return Pointer to version-specific address structure
*/
const zelda3_version_pointers* zelda3_get_version_pointers(zelda3_version version);
const static zelda3_version_pointers zelda3_us_pointers = {
// New style names
0x10275, // gfx_animated_pointer
0x5D97, // overworld_gfx_groups1
0x6073, // overworld_gfx_groups2
0x1794D, // compressed_map32_pointers_high
0x17B2D, // compressed_map32_pointers_low
0x75504, // overworld_map_palette_group
0x77664, // overlay_pointers
0x0E, // overlay_pointers_bank
0x71459, // overworld_tiles_type
0x4F80, // overworld_gfx_ptr1
0x505F, // overworld_gfx_ptr2
0x513E, // overworld_gfx_ptr3
0x18000, // map32_tile_tl
0x1B400, // map32_tile_tr
0x20000, // map32_tile_bl
0x23400, // map32_tile_br
0x5B57, // sprite_blockset_pointer
0x75460, // dungeon_palettes_groups
// Legacy k-prefixed names (same values for backward compatibility)
0x10275, // kGfxAnimatedPointer
0x5D97, // kOverworldGfxGroups1
0x6073, // kOverworldGfxGroups2
0x1794D, // kCompressedAllMap32PointersHigh
0x17B2D, // kCompressedAllMap32PointersLow
0x75504, // kOverworldMapPaletteGroup
0x77664, // kOverlayPointers
0x0E, // kOverlayPointersBank
0x71459, // kOverworldTilesType
0x4F80, // kOverworldGfxPtr1
0x505F, // kOverworldGfxPtr2
0x513E, // kOverworldGfxPtr3
0x18000, // kMap32TileTL
0x1B400, // kMap32TileTR
0x20000, // kMap32TileBL
0x23400, // kMap32TileBR
0x5B57, // kSpriteBlocksetPointer
0x75460, // kDungeonPalettesGroups
};
const static zelda3_version_pointers zelda3_jp_pointers = {
// New style names
0x10624, // gfx_animated_pointer
0x5DD7, // overworld_gfx_groups1
0x60B3, // overworld_gfx_groups2
0x176B1, // compressed_map32_pointers_high
0x17891, // compressed_map32_pointers_low
0x67E74, // overworld_map_palette_group
0x3FAF4, // overlay_pointers
0x07, // overlay_pointers_bank
0x7FD94, // overworld_tiles_type
0x4FC0, // overworld_gfx_ptr1
0x509F, // overworld_gfx_ptr2
0x517E, // overworld_gfx_ptr3
0x18000, // map32_tile_tl
0x1B3C0, // map32_tile_tr
0x20000, // map32_tile_bl
0x233C0, // map32_tile_br
0x5B97, // sprite_blockset_pointer
0x67DD0, // dungeon_palettes_groups
// Legacy k-prefixed names (same values for backward compatibility)
0x10624, // kGfxAnimatedPointer
0x5DD7, // kOverworldGfxGroups1
0x60B3, // kOverworldGfxGroups2
0x176B1, // kCompressedAllMap32PointersHigh
0x17891, // kCompressedAllMap32PointersLow
0x67E74, // kOverworldMapPaletteGroup
0x3FAF4, // kOverlayPointers
0x07, // kOverlayPointersBank
0x7FD94, // kOverworldTilesType
0x4FC0, // kOverworldGfxPtr1
0x509F, // kOverworldGfxPtr2
0x517E, // kOverworldGfxPtr3
0x18000, // kMap32TileTL
0x1B3C0, // kMap32TileTR
0x20000, // kMap32TileBL
0x233C0, // kMap32TileBR
0x5B97, // kSpriteBlocksetPointer
0x67DD0, // kDungeonPalettesGroups
};
/**
* @brief ROM data structure
*
* Represents a loaded Zelda 3 ROM with its data and metadata.
*/
typedef struct zelda3_rom {
const char* filename; /**< Original filename (can be NULL) */
uint8_t* data; /**< ROM data (read-only for external users) */
uint64_t size; /**< Size of ROM data in bytes */
zelda3_version version; /**< Detected ROM version */
bool is_modified; /**< True if ROM has been modified */
void* impl; /**< Internal implementation pointer */
} zelda3_rom;
/** @} */
/**
* @defgroup rom_functions ROM File Operations
* @{
*/
/**
* @brief Load a ROM file
*
* @param filename Path to ROM file
* @return Loaded ROM structure, or NULL on error
*/
zelda3_rom* yaze_load_rom(const char* filename);
/**
* @brief Unload and free ROM data
*
* @param rom ROM to unload
*/
void yaze_unload_rom(zelda3_rom* rom);
/**
* @brief Save ROM to file
*
* @param rom ROM to save
* @param filename Output filename
* @return YAZE_OK on success, error code on failure
*/
int yaze_save_rom(zelda3_rom* rom, const char* filename);
/**
* @brief Create a copy of ROM data
*
* @param rom Source ROM
* @return Copy of ROM, or NULL on error
*/
zelda3_rom* yaze_copy_rom(const zelda3_rom* rom);
/** @} */
/**
* @defgroup messages Message Data Structures
* @{
*/
/**
* @brief In-game text message data
*
* Represents a text message from the game, including both raw
* ROM data and parsed/decoded text content.
*/
typedef struct zelda3_message {
uint16_t id; /**< Message ID (0-65535) */
uint32_t rom_address; /**< Address in ROM where message data starts */
uint16_t length; /**< Length of message data in bytes */
uint8_t* raw_data; /**< Raw message data from ROM */
char* parsed_text; /**< Decoded text content (UTF-8) */
bool is_compressed; /**< True if message uses compression */
uint8_t encoding_type; /**< Text encoding type used */
} zelda3_message;
/** @} */
/**
* @defgroup overworld Overworld Data Structures
* @{
*/
/**
* @brief Overworld map data
*
* Represents a single screen/area in the overworld, including
* graphics, palette, music, and sprite information.
*/
typedef struct zelda3_overworld_map {
uint16_t id; /**< Map ID (0-159 for most ROMs) */
uint8_t parent_id; /**< Parent map ID for sub-areas */
uint8_t quadrant_id; /**< Quadrant within parent (0-3) */
uint8_t world_id; /**< World number (Light/Dark) */
uint8_t game_state; /**< Game state requirements */
/* Graphics and Visual Properties */
uint8_t area_graphics; /**< Area graphics set ID */
uint8_t area_palette; /**< Area palette set ID */
uint8_t main_palette; /**< Main palette ID */
uint8_t animated_gfx; /**< Animated graphics ID */
/* Sprite Configuration */
uint8_t sprite_graphics[3]; /**< Sprite graphics sets */
uint8_t sprite_palette[3]; /**< Sprite palette sets */
/* Audio Configuration */
uint8_t area_music[4]; /**< Music tracks for different states */
/* Extended Graphics (ZSCustomOverworld) */
uint8_t static_graphics[16]; /**< Static graphics assignments */
uint8_t custom_tileset[8]; /**< Custom tileset assignments */
/* Screen Properties */
uint16_t area_specific_bg_color; /**< Background color override */
uint16_t subscreen_overlay; /**< Subscreen overlay settings */
/* Flags and Metadata */
bool is_large_map; /**< True for 32x32 maps */
bool has_special_gfx; /**< True if uses special graphics */
} zelda3_overworld_map;
/**
* @brief Complete overworld data
*
* Contains all overworld maps and related data for the entire game world.
*/
typedef struct zelda3_overworld {
void* impl; /**< Internal implementation pointer */
zelda3_overworld_map** maps; /**< Array of overworld maps */
int map_count; /**< Number of maps in array */
zelda3_version rom_version; /**< ROM version this data came from */
bool has_zsco_features; /**< True if ZSCustomOverworld features detected */
} zelda3_overworld;
/** @} */
/**
* @defgroup dungeon Dungeon Data Structures
* @{
*/
/**
* @brief Dungeon sprite definition
*
* Represents a sprite that can appear in dungeon rooms.
*/
typedef struct dungeon_sprite {
const char* name; /**< Sprite name (for debugging/display) */
uint8_t id; /**< Sprite type ID */
uint8_t subtype; /**< Sprite subtype/variant */
uint8_t x; /**< X position in room */
uint8_t y; /**< Y position in room */
uint8_t layer; /**< Layer (0=background, 1=foreground) */
uint16_t properties; /**< Additional sprite properties */
} dungeon_sprite;
/**
* @brief Background layer 2 effects
*
* Defines the different visual effects that can be applied to
* background layer 2 in dungeon rooms.
*/
typedef enum zelda3_bg2_effect {
ZELDA3_BG2_OFF = 0, /**< Background layer 2 disabled */
ZELDA3_BG2_PARALLAX = 1, /**< Parallax scrolling effect */
ZELDA3_BG2_DARK = 2, /**< Dark overlay effect */
ZELDA3_BG2_ON_TOP = 3, /**< Layer appears on top */
ZELDA3_BG2_TRANSLUCENT = 4, /**< Semi-transparent overlay */
ZELDA3_BG2_ADDITION = 5, /**< Additive blending */
ZELDA3_BG2_NORMAL = 6, /**< Normal blending */
ZELDA3_BG2_TRANSPARENT = 7, /**< Fully transparent */
ZELDA3_BG2_DARK_ROOM = 8 /**< Dark room effect */
} zelda3_bg2_effect;
// Legacy aliases for backward compatibility
typedef zelda3_bg2_effect background2;
#define Off ZELDA3_BG2_OFF
#define Parallax ZELDA3_BG2_PARALLAX
#define Dark ZELDA3_BG2_DARK
#define OnTop ZELDA3_BG2_ON_TOP
#define Translucent ZELDA3_BG2_TRANSLUCENT
#define Addition ZELDA3_BG2_ADDITION
#define Normal ZELDA3_BG2_NORMAL
#define Transparent ZELDA3_BG2_TRANSPARENT
#define DarkRoom ZELDA3_BG2_DARK_ROOM
/**
* @brief Dungeon door object
*
* Represents a door or passage between rooms.
*/
typedef struct object_door {
uint16_t id; /**< Door ID for reference */
uint8_t x; /**< X position in room (0-63) */
uint8_t y; /**< Y position in room (0-63) */
uint8_t size; /**< Door size (width/height) */
uint8_t type; /**< Door type (normal, locked, etc.) */
uint8_t layer; /**< Layer (0=background, 1=foreground) */
uint8_t key_type; /**< Required key type (0=none) */
bool is_locked; /**< True if door requires key */
} object_door;
/**
* @brief Staircase connection
*
* Represents stairs or holes that connect different rooms or levels.
*/
typedef struct staircase {
uint8_t id; /**< Staircase ID */
uint8_t room; /**< Target room ID (for backward compatibility) */
const char* label; /**< Description (for debugging) */
} staircase;
/**
* @brief Treasure chest
*
* Represents a chest containing an item.
*/
typedef struct chest {
uint8_t x; /**< X position in room */
uint8_t y; /**< Y position in room */
uint8_t item; /**< Item ID (for backward compatibility) */
bool picker; /**< Legacy field */
bool big_chest; /**< True for large chests */
} chest;
/**
* @brief Legacy chest data structure
*
* @deprecated Use chest structure instead
*/
typedef struct chest_data {
uint8_t id; /**< Chest ID */
bool size; /**< True for big chest */
} chest_data;
/**
* @brief Room transition destination
*
* Defines where the player goes when using stairs, holes, or other transitions.
*/
typedef struct destination {
uint8_t index; /**< Entrance index */
uint8_t target; /**< Target room ID */
uint8_t target_layer; /**< Target layer */
} destination;
/** @} */
/**
* @brief Complete dungeon room data
*
* Contains all objects, sprites, and properties for a single dungeon room.
*/
typedef struct zelda3_dungeon_room {
uint16_t id; /**< Room ID (0-295) */
background2 bg2; /**< Background layer 2 effect (legacy) */
/* Room Contents */
dungeon_sprite* sprites; /**< Array of sprites in room */
int sprite_count; /**< Number of sprites */
object_door* doors; /**< Array of doors */
int door_count; /**< Number of doors */
staircase* staircases; /**< Array of staircases */
int staircase_count; /**< Number of staircases */
chest* chests; /**< Array of chests */
int chest_count; /**< Number of chests */
/* Room Connections */
destination pits; /**< Pit fall destination */
destination stairs[4]; /**< Stair destinations (up to 4) */
/* Room Properties */
uint8_t floor_type; /**< Floor graphics type */
uint8_t wall_type; /**< Wall graphics type */
uint8_t palette_id; /**< Room palette ID */
uint8_t music_track; /**< Background music track */
/* Flags */
bool is_dark; /**< True if room requires lamp */
bool has_water; /**< True if room contains water */
bool blocks_items; /**< True if room blocks certain items */
} zelda3_dungeon_room;
/** @} */
#ifdef __cplusplus
}
#endif
#endif // ZELDA_H

369
scripts/agent.sh Executable file
View File

@@ -0,0 +1,369 @@
#!/usr/bin/env bash
set -euo pipefail
# Unified background agent script for YAZE (macOS launchd, Linux systemd)
# Subcommands:
# install [--build-type X] [--use-inotify] [--ubuntu-bootstrap] [--enable-linger]
# uninstall
# run-once # one-shot build & test
# watch # linux: inotify-based watch loop
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
BUILD_DIR_DEFAULT="${PROJECT_ROOT}/build"
BUILD_TYPE_DEFAULT="Debug"
usage() {
cat <<EOF
Usage: $0 <subcommand> [options]
Subcommands:
install Install background agent
--build-type X CMake build type (default: ${BUILD_TYPE_DEFAULT})
--use-inotify Linux: use long-lived inotify watcher
--ubuntu-bootstrap Install Ubuntu deps via apt (sudo)
--enable-linger Enable user lingering (Linux)
uninstall Remove installed background agent
run-once One-shot build and test
env: BUILD_DIR, BUILD_TYPE
watch Linux: inotify-based watch loop
env: BUILD_DIR, BUILD_TYPE, INTERVAL_SECONDS (fallback)
Environment:
BUILD_DIR Build directory (default: ${BUILD_DIR_DEFAULT})
BUILD_TYPE CMake build type (default: ${BUILD_TYPE_DEFAULT})
EOF
}
build_and_test() {
local build_dir="${BUILD_DIR:-${BUILD_DIR_DEFAULT}}"
local build_type="${BUILD_TYPE:-${BUILD_TYPE_DEFAULT}}"
local log_file="${LOG_FILE:-"${build_dir}/yaze_agent.log"}"
mkdir -p "${build_dir}"
{
echo "==== $(date '+%Y-%m-%d %H:%M:%S') | yaze agent run (type=${build_type}) ===="
echo "Project: ${PROJECT_ROOT}"
cmake -S "${PROJECT_ROOT}" -B "${build_dir}" -DCMAKE_BUILD_TYPE="${build_type}"
cmake --build "${build_dir}" --config "${build_type}" -j
if [[ -x "${build_dir}/bin/yaze_test" ]]; then
"${build_dir}/bin/yaze_test"
else
if command -v ctest >/dev/null 2>&1; then
ctest --test-dir "${build_dir}" --output-on-failure
else
echo "ctest not found and test binary missing. Skipping tests." >&2
exit 1
fi
fi
} >>"${log_file}" 2>&1
}
sub_run_once() {
build_and_test
}
sub_watch() {
local build_dir="${BUILD_DIR:-${BUILD_DIR_DEFAULT}}"
local build_type="${BUILD_TYPE:-${BUILD_TYPE_DEFAULT}}"
local interval="${INTERVAL_SECONDS:-5}"
mkdir -p "${build_dir}"
if command -v inotifywait >/dev/null 2>&1; then
echo "[agent] using inotifywait watcher"
while true; do
BUILD_DIR="${build_dir}" BUILD_TYPE="${build_type}" build_and_test || true
inotifywait -r -e modify,create,delete,move \
"${PROJECT_ROOT}/src" \
"${PROJECT_ROOT}/test" \
"${PROJECT_ROOT}/CMakeLists.txt" \
"${PROJECT_ROOT}/src/CMakeLists.txt" >/dev/null 2>&1 || true
done
else
echo "[agent] inotifywait not found; running periodic loop (${interval}s)"
while true; do
BUILD_DIR="${build_dir}" BUILD_TYPE="${build_type}" build_and_test || true
sleep "${interval}"
done
fi
}
ensure_exec() {
if [[ ! -x "$1" ]]; then
chmod +x "$1"
fi
}
ubuntu_bootstrap() {
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y inotify-tools build-essential cmake ninja-build pkg-config \
libsdl2-dev libpng-dev libglew-dev libwavpack-dev libabsl-dev \
libboost-all-dev libboost-python-dev python3-dev libpython3-dev
else
echo "apt-get not found; skipping Ubuntu bootstrap" >&2
fi
}
enable_linger_linux() {
if command -v loginctl >/dev/null 2>&1; then
if sudo -n true 2>/dev/null; then
sudo loginctl enable-linger "$USER" || true
else
echo "Note: enabling linger may require sudo: sudo loginctl enable-linger $USER" >&2
fi
fi
}
# Wrapper to run systemctl --user with a session bus in headless shells
systemctl_user() {
# Only apply on Linux
if [[ "$(uname -s)" != "Linux" ]]; then
systemctl "$@"
return
fi
local uid
uid="$(id -u)"
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/${uid}}"
export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=${XDG_RUNTIME_DIR}/bus}"
if [[ ! -S "${XDG_RUNTIME_DIR}/bus" ]]; then
echo "[agent] Warning: user bus not found at ${XDG_RUNTIME_DIR}/bus. If this fails, run: sudo loginctl enable-linger $USER" >&2
fi
systemctl --user "$@"
}
is_systemd_available() {
# True if systemd is PID 1 and systemctl exists and a user bus likely available
if [[ ! -d /run/systemd/system ]]; then
return 1
fi
if ! command -v systemctl >/dev/null 2>&1; then
return 1
fi
return 0
}
start_userland_agent() {
local build_type="${1}"
local build_dir="${2}"
local agent_home="$HOME/.local/share/yaze-agent"
mkdir -p "${agent_home}"
local log_file="${agent_home}/agent.log"
nohup env BUILD_TYPE="${build_type}" BUILD_DIR="${build_dir}" "${SCRIPT_DIR}/agent.sh" watch >>"${log_file}" 2>&1 & echo $! > "${agent_home}/agent.pid"
echo "Userland agent started (PID $(cat "${agent_home}/agent.pid")) - logs: ${log_file}"
}
stop_userland_agent() {
local agent_home="$HOME/.local/share/yaze-agent"
local pid_file="${agent_home}/agent.pid"
if [[ -f "${pid_file}" ]]; then
local pid
pid="$(cat "${pid_file}")"
if kill -0 "${pid}" >/dev/null 2>&1; then
kill "${pid}" || true
fi
rm -f "${pid_file}"
echo "Stopped userland agent"
fi
}
install_macos() {
local build_type="${1}"
local build_dir="${2}"
local label="com.yaze.watchtest"
local plist_path="$HOME/Library/LaunchAgents/${label}.plist"
mkdir -p "${build_dir}"
cat >"$plist_path" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${label}</string>
<key>RunAtLoad</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>-lc</string>
<string>"${SCRIPT_DIR}/agent.sh" run-once</string>
</array>
<key>WorkingDirectory</key>
<string>${PROJECT_ROOT}</string>
<key>WatchPaths</key>
<array>
<string>${PROJECT_ROOT}/src</string>
<string>${PROJECT_ROOT}/test</string>
<string>${PROJECT_ROOT}/CMakeLists.txt</string>
<string>${PROJECT_ROOT}/src/CMakeLists.txt</string>
</array>
<key>StandardOutPath</key>
<string>${build_dir}/yaze_agent.out.log</string>
<key>StandardErrorPath</key>
<string>${build_dir}/yaze_agent.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>BUILD_TYPE</key><string>${build_type}</string>
<key>BUILD_DIR</key><string>${build_dir}</string>
</dict>
</dict>
</plist>
PLIST
launchctl unload -w "$plist_path" 2>/dev/null || true
launchctl load -w "$plist_path"
echo "LaunchAgent installed and loaded: ${plist_path}"
}
install_linux() {
local build_type="${1}"
local build_dir="${2}"
local use_inotify="${3}"
local systemd_dir="$HOME/.config/systemd/user"
local service_name="yaze-watchtest.service"
local path_name="yaze-watchtest.path"
mkdir -p "${systemd_dir}" "${build_dir}"
if ! is_systemd_available; then
echo "[agent] systemd not available; installing userland background agent"
start_userland_agent "${build_type}" "${build_dir}"
return
fi
if [[ "${use_inotify}" == "1" ]]; then
cat >"${systemd_dir}/yaze-watchtest-inotify.service" <<UNIT
[Unit]
Description=Yaze inotify watcher build-and-test
[Service]
Type=simple
Environment=BUILD_TYPE=${build_type}
Environment=BUILD_DIR=${build_dir}
WorkingDirectory=${PROJECT_ROOT}
ExecStart=/bin/bash -lc '"${SCRIPT_DIR}/agent.sh" watch'
Restart=always
RestartSec=2
[Install]
WantedBy=default.target
UNIT
systemctl_user daemon-reload
systemctl_user enable --now yaze-watchtest-inotify.service
echo "systemd user service enabled: yaze-watchtest-inotify.service"
return
fi
cat >"${systemd_dir}/${service_name}" <<UNIT
[Unit]
Description=Yaze build-and-test (one-shot)
[Service]
Type=oneshot
Environment=BUILD_TYPE=${build_type}
Environment=BUILD_DIR=${build_dir}
WorkingDirectory=${PROJECT_ROOT}
ExecStart=/bin/bash -lc '"${SCRIPT_DIR}/agent.sh" run-once'
UNIT
cat >"${systemd_dir}/${path_name}" <<UNIT
[Unit]
Description=Watch Yaze src/test for changes
[Path]
PathModified=${PROJECT_ROOT}/src
PathModified=${PROJECT_ROOT}/test
PathModified=${PROJECT_ROOT}/CMakeLists.txt
PathModified=${PROJECT_ROOT}/src/CMakeLists.txt
Unit=${service_name}
[Install]
WantedBy=default.target
UNIT
systemctl_user daemon-reload
systemctl_user enable --now "$path_name"
echo "systemd user path unit enabled: ${path_name}"
systemctl_user start "$service_name" || true
}
sub_install() {
local build_type="${BUILD_TYPE:-${BUILD_TYPE_DEFAULT}}"
local build_dir="${BUILD_DIR:-${BUILD_DIR_DEFAULT}}"
local use_inotify=0
local do_bootstrap=0
local do_linger=0
while [[ $# -gt 0 ]]; do
case "$1" in
--build-type) build_type="$2"; shift 2 ;;
--use-inotify) use_inotify=1; shift ;;
--ubuntu-bootstrap) do_bootstrap=1; shift ;;
--enable-linger) do_linger=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option for install: $1" >&2; usage; exit 1 ;;
esac
done
case "$(uname -s)" in
Darwin)
install_macos "${build_type}" "${build_dir}" ;;
Linux)
if (( do_bootstrap == 1 )); then ubuntu_bootstrap; fi
if (( do_linger == 1 )); then enable_linger_linux; fi
install_linux "${build_type}" "${build_dir}" "${use_inotify}" ;;
*)
echo "Unsupported platform" >&2; exit 1 ;;
esac
}
sub_uninstall() {
case "$(uname -s)" in
Darwin)
local label="com.yaze.watchtest"
local plist_path="$HOME/Library/LaunchAgents/${label}.plist"
launchctl unload -w "$plist_path" 2>/dev/null || true
rm -f "$plist_path"
echo "Removed LaunchAgent ${label}"
;;
Linux)
local systemd_dir="$HOME/.config/systemd/user"
if is_systemd_available; then
systemctl_user stop yaze-watchtest.path 2>/dev/null || true
systemctl_user disable yaze-watchtest.path 2>/dev/null || true
systemctl_user stop yaze-watchtest.service 2>/dev/null || true
systemctl_user stop yaze-watchtest-inotify.service 2>/dev/null || true
systemctl_user disable yaze-watchtest-inotify.service 2>/dev/null || true
rm -f "${systemd_dir}/yaze-watchtest.service" "${systemd_dir}/yaze-watchtest.path" "${systemd_dir}/yaze-watchtest-inotify.service"
systemctl_user daemon-reload || true
echo "Removed systemd user units"
fi
stop_userland_agent
;;
*) echo "Unsupported platform" >&2; exit 1 ;;
esac
}
main() {
ensure_exec "$0"
local subcmd="${1:-}"; shift || true
case "${subcmd}" in
install) sub_install "$@" ;;
uninstall) sub_uninstall ;;
run-once) sub_run_once ;;
watch) sub_watch ;;
-h|--help|help|"") usage ;;
*) echo "Unknown subcommand: ${subcmd}" >&2; usage; exit 1 ;;
esac
}
main "$@"

138
scripts/create_release.sh Executable file
View File

@@ -0,0 +1,138 @@
#!/bin/bash
# Script to create a proper release tag for YAZE
# Usage: ./scripts/create_release.sh [version]
# Example: ./scripts/create_release.sh 0.3.0
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_error "Not in a git repository!"
exit 1
fi
# Check if we're on master branch
current_branch=$(git branch --show-current)
if [ "$current_branch" != "master" ]; then
print_warning "You're on branch '$current_branch', not 'master'"
read -p "Continue anyway? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_info "Switching to master branch..."
git checkout master
fi
fi
# Get version argument or prompt for it
if [ $# -eq 0 ]; then
echo
print_info "Enter the version number (e.g., 0.3.0, 1.0.0-beta, 2.1.0-rc1):"
read -p "Version: " version
else
version=$1
fi
# Validate version format
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then
print_error "Invalid version format: '$version'"
print_info "Version must follow semantic versioning (e.g., 1.2.3 or 1.2.3-beta)"
exit 1
fi
# Create tag with v prefix
tag="v$version"
# Check if tag already exists
if git tag -l | grep -q "^$tag$"; then
print_error "Tag '$tag' already exists!"
exit 1
fi
# Show current status
echo
print_info "Current repository status:"
git status --short
# Check for uncommitted changes
if ! git diff-index --quiet HEAD --; then
print_warning "You have uncommitted changes!"
read -p "Continue with creating release? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_info "Please commit your changes first, then run this script again."
exit 1
fi
fi
# Show what will happen
echo
print_info "Creating release for YAZE:"
echo " 📦 Version: $version"
echo " 🏷️ Tag: $tag"
echo " 🌿 Branch: $current_branch"
echo " 📝 Changelog: docs/C1-changelog.md"
echo
# Confirm
read -p "Create this release? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_info "Release creation cancelled."
exit 0
fi
# Create and push tag
print_info "Creating tag '$tag'..."
if git tag -a "$tag" -m "Release $tag"; then
print_success "Tag '$tag' created successfully!"
else
print_error "Failed to create tag!"
exit 1
fi
print_info "Pushing tag to origin..."
if git push origin "$tag"; then
print_success "Tag pushed successfully!"
else
print_error "Failed to push tag!"
print_info "You can manually push with: git push origin $tag"
exit 1
fi
echo
print_success "🎉 Release $tag created successfully!"
print_info "The GitHub Actions release workflow will now:"
echo " • Build packages for Windows, macOS, and Linux"
echo " • Extract changelog from docs/C1-changelog.md"
echo " • Create GitHub release with all assets"
echo " • Include themes, fonts, layouts, and documentation"
echo
print_info "Check the release progress at:"
echo " https://github.com/scawful/yaze/actions"
echo
print_info "The release will be available at:"
echo " https://github.com/scawful/yaze/releases/tag/$tag"

94
scripts/extract_changelog.py Executable file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Extract changelog section for a specific version from docs/C1-changelog.md
Usage: python3 extract_changelog.py <version>
Example: python3 extract_changelog.py 0.3.0
"""
import re
import sys
import os
def extract_version_changelog(version_num, changelog_file):
"""Extract changelog section for specific version"""
try:
with open(changelog_file, 'r') as f:
content = f.read()
# Find the section for this version
version_pattern = rf"## {re.escape(version_num)}\s*\([^)]+\)"
next_version_pattern = r"## \d+\.\d+\.\d+\s*\([^)]+\)"
# Find start of current version section
version_match = re.search(version_pattern, content)
if not version_match:
return f"Changelog section not found for version {version_num}."
start_pos = version_match.end()
# Find start of next version section
remaining_content = content[start_pos:]
next_match = re.search(next_version_pattern, remaining_content)
if next_match:
end_pos = start_pos + next_match.start()
section_content = content[start_pos:end_pos].strip()
else:
section_content = remaining_content.strip()
return section_content
except Exception as e:
return f"Error reading changelog: {str(e)}"
def main():
if len(sys.argv) != 2:
print("Usage: python3 extract_changelog.py <version>")
sys.exit(1)
version_num = sys.argv[1]
changelog_file = "docs/C1-changelog.md"
# Check if changelog file exists
if not os.path.exists(changelog_file):
print(f"Error: Changelog file {changelog_file} not found")
sys.exit(1)
# Extract changelog content
changelog_content = extract_version_changelog(version_num, changelog_file)
# Generate full release notes
release_notes = f"""# Yaze v{version_num} Release Notes
{changelog_content}
## Download Instructions
### Windows
- Download `yaze-windows-x64.zip` for 64-bit Windows
- Download `yaze-windows-x86.zip` for 32-bit Windows
- Extract and run `yaze.exe`
### macOS
- Download `yaze-macos.dmg`
- Mount the DMG and drag Yaze to Applications
- You may need to allow the app in System Preferences > Security & Privacy
### Linux
- Download `yaze-linux-x64.tar.gz`
- Extract: `tar -xzf yaze-linux-x64.tar.gz`
- Run: `./yaze`
## System Requirements
- **Windows**: Windows 10 or later (64-bit recommended)
- **macOS**: macOS 10.15 (Catalina) or later
- **Linux**: Ubuntu 20.04 or equivalent, with X11 or Wayland
## Support
For issues and questions, please visit our [GitHub Issues](https://github.com/scawful/yaze/issues) page.
"""
print(release_notes)
if __name__ == "__main__":
main()

54
scripts/quality_check.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/bin/bash
# Quality check script for YAZE codebase
# This script runs various code quality checks to ensure CI/CD pipeline passes
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${PROJECT_ROOT}"
echo "🔍 Running code quality checks for YAZE..."
# Check if required tools are available
command -v clang-format >/dev/null 2>&1 || { echo "❌ clang-format not found. Please install it."; exit 1; }
command -v cppcheck >/dev/null 2>&1 || { echo "❌ cppcheck not found. Please install it."; exit 1; }
# Create .clang-format config if it doesn't exist
if [ ! -f .clang-format ]; then
echo "📝 Creating .clang-format configuration..."
clang-format --style=Google --dump-config > .clang-format
fi
echo "✅ Code formatting check..."
# Check formatting without modifying files
FORMATTING_ISSUES=$(find src test -name "*.cc" -o -name "*.h" | head -50 | xargs clang-format --dry-run --Werror --style=Google 2>&1 || true)
if [ -n "$FORMATTING_ISSUES" ]; then
echo "⚠️ Formatting issues found. Run 'make format' to fix them."
echo "$FORMATTING_ISSUES" | head -20
else
echo "✅ All files are properly formatted"
fi
echo "🔍 Running static analysis..."
# Run cppcheck on main source directories
cppcheck --enable=all --error-exitcode=0 \
--suppress=missingIncludeSystem \
--suppress=unusedFunction \
--suppress=unmatchedSuppression \
--suppress=unreadVariable \
--suppress=cstyleCast \
--suppress=variableScope \
src/ 2>&1 | head -30
echo "✅ Quality checks completed!"
echo ""
echo "💡 To fix formatting issues automatically, run:"
echo " find src test -name '*.cc' -o -name '*.h' | xargs clang-format -i --style=Google"
echo ""
echo "💡 For CI/CD pipeline compatibility, ensure:"
echo " - All formatting issues are resolved"
echo " - absl::Status return values are handled with RETURN_IF_ERROR() or PRINT_IF_ERROR()"
echo " - Use Google C++ style for consistency"

150
scripts/test_asar_integration.py Executable file
View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""
Asar Integration Test Script for Yaze
Tests the Asar 65816 assembler integration with real ROM files
"""
import os
import sys
import subprocess
import shutil
import tempfile
from pathlib import Path
def find_project_root():
"""Find the yaze project root directory"""
current = Path(__file__).parent
while current != current.parent:
if (current / "CMakeLists.txt").exists():
return current
current = current.parent
raise FileNotFoundError("Could not find yaze project root")
def main():
print("🧪 Yaze Asar Integration Test")
print("=" * 50)
project_root = find_project_root()
build_dir = project_root / "build_test"
rom_path = build_dir / "bin" / "zelda3.sfc"
test_patch = project_root / "test" / "assets" / "test_patch.asm"
# Check if ROM file exists
if not rom_path.exists():
print(f"❌ ROM file not found: {rom_path}")
print(" Please ensure you have a test ROM at the expected location")
return 1
print(f"✅ Found ROM file: {rom_path}")
print(f" Size: {rom_path.stat().st_size:,} bytes")
# Check if test patch exists
if not test_patch.exists():
print(f"❌ Test patch not found: {test_patch}")
return 1
print(f"✅ Found test patch: {test_patch}")
# Check if z3ed tool exists
z3ed_path = build_dir / "bin" / "z3ed"
if not z3ed_path.exists():
print(f"❌ z3ed CLI tool not found: {z3ed_path}")
print(" Run: cmake --build build_test --target z3ed")
return 1
print(f"✅ Found z3ed CLI tool: {z3ed_path}")
# Create temporary directory for testing
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
test_rom_path = temp_path / "test_rom.sfc"
patched_rom_path = temp_path / "patched_rom.sfc"
# Copy ROM to temporary location
shutil.copy2(rom_path, test_rom_path)
print(f"📋 Copied ROM to: {test_rom_path}")
# Test 1: Apply patch using z3ed CLI
print("\n🔧 Test 1: Applying patch with z3ed CLI")
try:
cmd = [str(z3ed_path), "asar", str(test_patch), str(test_rom_path)]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
print("✅ Patch applied successfully!")
if result.stdout:
print(f" Output: {result.stdout.strip()}")
else:
print(f"❌ Patch failed with return code: {result.returncode}")
if result.stderr:
print(f" Error: {result.stderr.strip()}")
return 1
except subprocess.TimeoutExpired:
print("❌ Patch operation timed out")
return 1
except Exception as e:
print(f"❌ Error running patch: {e}")
return 1
# Test 2: Verify ROM was modified
print("\n🔍 Test 2: Verifying ROM modification")
original_size = rom_path.stat().st_size
modified_size = test_rom_path.stat().st_size
print(f" Original ROM size: {original_size:,} bytes")
print(f" Modified ROM size: {modified_size:,} bytes")
# Read first few bytes to check for changes
with open(rom_path, 'rb') as orig_file, open(test_rom_path, 'rb') as mod_file:
orig_bytes = orig_file.read(1024)
mod_bytes = mod_file.read(1024)
if orig_bytes != mod_bytes:
print("✅ ROM was successfully modified!")
# Count different bytes
diff_count = sum(1 for a, b in zip(orig_bytes, mod_bytes) if a != b)
print(f" {diff_count} bytes differ in first 1KB")
else:
print("⚠️ No differences detected in first 1KB")
print(" (Patch may have been applied to a different region)")
# Test 3: Run unit tests if available
yaze_test_path = build_dir / "bin" / "yaze_test"
if yaze_test_path.exists():
print("\n🧪 Test 3: Running Asar unit tests")
try:
# Run only the Asar-related tests
cmd = [str(yaze_test_path), "--gtest_filter=*Asar*", "--gtest_brief=1"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
print(f" Exit code: {result.returncode}")
if result.stdout:
# Extract test results
lines = result.stdout.split('\n')
for line in lines:
if 'PASSED' in line or 'FAILED' in line or 'RUN' in line:
print(f" {line}")
if result.returncode == 0:
print("✅ Unit tests passed!")
else:
print("⚠️ Some unit tests failed (this may be expected)")
except subprocess.TimeoutExpired:
print("❌ Unit tests timed out")
except Exception as e:
print(f"⚠️ Error running unit tests: {e}")
else:
print("\n⚠️ Test 3: yaze_test executable not found")
print("\n🎉 Asar integration test completed!")
print("\nNext steps:")
print("- Run full test suite with: ctest --test-dir build_test")
print("- Test Asar functionality in the main yaze application")
print("- Create custom assembly patches for your ROM hacking projects")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -14,6 +14,13 @@ set(
app/emu/snes.cc app/emu/snes.cc
) )
set(
YAZE_UTIL_SRC
util/bps.cc
util/flag.cc
util/hex.cc
)
set(YAZE_RESOURCE_FILES set(YAZE_RESOURCE_FILES
${CMAKE_SOURCE_DIR}/assets/layouts/overworld.zeml ${CMAKE_SOURCE_DIR}/assets/layouts/overworld.zeml
${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf ${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf
@@ -25,6 +32,10 @@ set(YAZE_RESOURCE_FILES
${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf ${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf
) )
# Add theme files for macOS bundle (replacing the glob pattern with explicit files)
file(GLOB YAZE_THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme")
list(APPEND YAZE_RESOURCE_FILES ${YAZE_THEME_FILES})
foreach (FILE ${YAZE_RESOURCE_FILES}) foreach (FILE ${YAZE_RESOURCE_FILES})
file(RELATIVE_PATH NEW_FILE "${CMAKE_SOURCE_DIR}/assets" ${FILE}) file(RELATIVE_PATH NEW_FILE "${CMAKE_SOURCE_DIR}/assets" ${FILE})
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
@@ -34,6 +45,33 @@ foreach (FILE ${YAZE_RESOURCE_FILES})
) )
endforeach() endforeach()
# Conditionally add native file dialog (optional for CI builds)
if(NOT YAZE_MINIMAL_BUILD)
# Check if we can build NFD before adding it
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND AND UNIX AND NOT APPLE)
pkg_check_modules(GTK3 QUIET gtk+-3.0)
if(GTK3_FOUND)
add_subdirectory(lib/nativefiledialog-extended)
set(YAZE_HAS_NFD ON)
message(STATUS "NFD enabled with GTK3 support")
else()
set(YAZE_HAS_NFD OFF)
message(STATUS "NFD disabled - GTK3 not found")
endif()
elseif(WIN32 OR APPLE)
add_subdirectory(lib/nativefiledialog-extended)
set(YAZE_HAS_NFD ON)
message(STATUS "NFD enabled for Windows/macOS")
else()
set(YAZE_HAS_NFD OFF)
message(STATUS "NFD disabled - no platform support")
endif()
else()
set(YAZE_HAS_NFD OFF)
message(STATUS "NFD disabled for minimal build")
endif()
if (YAZE_BUILD_APP) if (YAZE_BUILD_APP)
include(app/app.cmake) include(app/app.cmake)
endif() endif()
@@ -43,18 +81,12 @@ endif()
if (YAZE_BUILD_Z3ED) if (YAZE_BUILD_Z3ED)
include(cli/z3ed.cmake) include(cli/z3ed.cmake)
endif() endif()
if (YAZE_BUILD_PYTHON)
include(cli/python/yaze_py.cmake)
endif()
if (YAZE_BUILD_TESTS)
include(test/CMakeLists.txt)
endif()
if(MACOS) if(MACOS)
set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/win32/yaze.ico)
set_target_properties(yaze set_target_properties(yaze
PROPERTIES PROPERTIES
BUNDLE True BUNDLE True
OUTPUT_NAME "yaze"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
@@ -72,51 +104,131 @@ elseif(UNIX)
target_compile_definitions(yaze PRIVATE "linux") target_compile_definitions(yaze PRIVATE "linux")
target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp") target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
else() else()
set_target_properties(yaze if(YAZE_MINIMAL_BUILD)
PROPERTIES # Skip Windows resource file in CI/minimal builds to avoid architecture conflicts
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" set_target_properties(yaze
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/win32/yaze.res" 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}/win32/yaze.res"
)
endif()
endif() endif()
# Yaze C API # Yaze Core Library (for testing and C API)
if (YAZE_BUILD_LIB) if (YAZE_BUILD_LIB)
add_library( # Create core library for testing (includes editor and zelda3 components needed by tests)
yaze_c SHARED set(YAZE_CORE_SOURCES
./yaze.cc
app/rom.cc app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC} ${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC} ${YAZE_APP_GFX_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_ZELDA3_SRC} ${YAZE_APP_ZELDA3_SRC}
${YAZE_APP_EMU_SRC}
${YAZE_GUI_SRC} ${YAZE_GUI_SRC}
${IMGUI_SRC} ${YAZE_UTIL_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
) )
# Create full library for C API
set(YAZE_C_SOURCES
./yaze.cc
${YAZE_CORE_SOURCES}
${YAZE_GUI_SRC}
${IMGUI_SRC}
)
# Add emulator sources (required for comprehensive testing)
list(APPEND YAZE_C_SOURCES ${YAZE_APP_EMU_SRC})
# Only add ImGui Test Engine sources if UI tests are enabled
if(YAZE_ENABLE_UI_TESTS)
list(APPEND YAZE_C_SOURCES ${IMGUI_TEST_ENGINE_SOURCES})
endif()
# Create the core library (static for testing)
add_library(yaze_core STATIC ${YAZE_CORE_SOURCES})
# Create the full C API library (static for CI, shared for release)
if(YAZE_MINIMAL_BUILD)
add_library(yaze_c STATIC ${YAZE_C_SOURCES})
else()
add_library(yaze_c SHARED ${YAZE_C_SOURCES})
endif()
# Configure core library (for testing)
target_include_directories( target_include_directories(
yaze_c PUBLIC yaze_core PUBLIC
lib/ ${CMAKE_SOURCE_DIR}/src/lib/
app/ ${CMAKE_SOURCE_DIR}/src/app/
${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${CMAKE_SOURCE_DIR}/incl/ ${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/ ${CMAKE_SOURCE_DIR}/src/
${PNG_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src/lib/imgui
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${SDL2_INCLUDE_DIR} ${SDL2_INCLUDE_DIR}
${PROJECT_BINARY_DIR}
) )
target_link_libraries( target_link_libraries(
yaze_c PRIVATE yaze_core PUBLIC
asar-static
${ABSL_TARGETS} ${ABSL_TARGETS}
${SDL_TARGETS} ${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS} ${CMAKE_DL_LIBS}
ImGuiTestEngine
ImGui ImGui
) )
# Configure full C API library
target_include_directories(
yaze_c PUBLIC
${CMAKE_SOURCE_DIR}/src/lib/
${CMAKE_SOURCE_DIR}/src/app/
${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${SDL2_INCLUDE_DIR}
${PROJECT_BINARY_DIR}
)
# Conditionally add PNG include dirs if available
if(PNG_FOUND)
target_include_directories(yaze_c PUBLIC ${PNG_INCLUDE_DIRS})
target_include_directories(yaze_core PUBLIC ${PNG_INCLUDE_DIRS})
endif()
target_link_libraries(
yaze_c PRIVATE
yaze_core
ImGui
)
# Conditionally link ImGui Test Engine and set definitions
if(YAZE_ENABLE_UI_TESTS AND TARGET ImGuiTestEngine)
target_link_libraries(yaze_c PRIVATE ImGuiTestEngine)
target_compile_definitions(yaze_c PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=1)
else()
target_compile_definitions(yaze_c PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
# Conditionally link PNG if available
if(PNG_FOUND)
target_link_libraries(yaze_c PRIVATE ${PNG_LIBRARIES})
target_link_libraries(yaze_core PRIVATE ${PNG_LIBRARIES})
endif()
if (YAZE_INSTALL_LIB) if (YAZE_INSTALL_LIB)
install(TARGETS yaze_c install(TARGETS yaze_c
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
@@ -125,12 +237,8 @@ if (YAZE_BUILD_LIB)
install( install(
FILES FILES
yaze.h incl/yaze.h
incl/sprite.h incl/zelda.h
incl/snes_tile.h
incl/snes_color.h
incl/overworld.h
incl/dungeon.h
DESTINATION DESTINATION
include include
) )

View File

@@ -5,69 +5,121 @@ include(app/gui/gui.cmake)
include(app/zelda3/zelda3.cmake) include(app/zelda3/zelda3.cmake)
if (APPLE) if (APPLE)
add_executable( add_executable(
yaze yaze
MACOSX_BUNDLE MACOSX_BUNDLE
app/main.cc app/main.cc
app/rom.cc app/rom.cc
${YAZE_APP_EMU_SRC} ${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC} ${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC} ${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC} ${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC} ${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC} ${YAZE_UTIL_SRC}
${IMGUI_SRC} ${YAZE_GUI_SRC}
${IMGUI_SRC}
# Bundled Resources
${YAZE_RESOURCE_FILES}
)
# Bundled Resources # Add the app icon to the macOS bundle
${YAZE_RESOURCE_FILES} set(ICON_FILE "${CMAKE_SOURCE_DIR}/assets/yaze.icns")
) target_sources(yaze PRIVATE ${ICON_FILE})
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# Set macOS bundle properties
set_target_properties(yaze PROPERTIES
MACOSX_BUNDLE_ICON_FILE "yaze.icns"
MACOSX_BUNDLE_BUNDLE_NAME "Yaze"
MACOSX_BUNDLE_EXECUTABLE_NAME "yaze"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.scawful.yaze"
MACOSX_BUNDLE_INFO_STRING "Yet Another Zelda3 Editor"
MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 scawful. All rights reserved."
)
else() else()
add_executable( add_executable(
yaze yaze
app/main.cc app/main.cc
app/rom.cc app/rom.cc
${YAZE_APP_EMU_SRC} ${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC} ${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC} ${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC} ${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC} ${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC} ${YAZE_UTIL_SRC}
${IMGUI_SRC} ${YAZE_GUI_SRC}
) ${IMGUI_SRC}
)
endif() endif()
target_include_directories( target_include_directories(
yaze PUBLIC yaze PUBLIC
lib/ ${CMAKE_SOURCE_DIR}/src/lib/
app/ ${CMAKE_SOURCE_DIR}/src/app/
${ASAR_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src/lib/asar/src
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar
${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c
${CMAKE_SOURCE_DIR}/incl/ ${CMAKE_SOURCE_DIR}/incl/
${CMAKE_SOURCE_DIR}/src/ ${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR} ${SDL2_INCLUDE_DIR}
${PROJECT_BINARY_DIR}
) )
# Conditionally add PNG include dirs if available
if(PNG_FOUND)
target_include_directories(yaze PUBLIC ${PNG_INCLUDE_DIRS})
endif()
# Conditionally link nfd if available
if(YAZE_HAS_NFD)
target_link_libraries(yaze PRIVATE nfd)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=1)
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_NFD=0)
endif()
target_link_libraries( target_link_libraries(
yaze PUBLIC yaze PUBLIC
asar-static asar-static
${ABSL_TARGETS} ${ABSL_TARGETS}
${SDL_TARGETS} ${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS} ${CMAKE_DL_LIBS}
ImGui ImGui
ImGuiTestEngine
) )
# Conditionally link ImGui Test Engine
if(YAZE_ENABLE_UI_TESTS)
if(TARGET ImGuiTestEngine)
target_include_directories(yaze PUBLIC ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine)
target_link_libraries(yaze PUBLIC ImGuiTestEngine)
target_compile_definitions(yaze PRIVATE
YAZE_ENABLE_IMGUI_TEST_ENGINE=1
${IMGUI_TEST_ENGINE_DEFINITIONS})
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
# Link Google Test if available for integrated testing
if(YAZE_BUILD_TESTS AND TARGET gtest AND TARGET gtest_main)
target_link_libraries(yaze PRIVATE gtest gtest_main)
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=1)
else()
target_compile_definitions(yaze PRIVATE YAZE_ENABLE_GTEST=0)
endif()
# Conditionally link PNG if available
if(PNG_FOUND)
target_link_libraries(yaze PUBLIC ${PNG_LIBRARIES})
endif()
if (APPLE) if (APPLE)
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY}) target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
endif() endif()
if (WIN32 OR MINGW)
target_link_libraries(
yaze PUBLIC
${CMAKE_SOURCE_DIR}/build/build-windows/bin/libpng16.dll
zlib
mingw32
ws2_32)
endif()

View File

@@ -0,0 +1,297 @@
#include "app/core/asar_wrapper.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include <filesystem>
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
// Include Asar C bindings
#include "asar-dll-bindings/c/asar.h"
namespace yaze {
namespace app {
namespace core {
AsarWrapper::AsarWrapper() : initialized_(false) {}
AsarWrapper::~AsarWrapper() {
if (initialized_) {
Shutdown();
}
}
absl::Status AsarWrapper::Initialize() {
if (initialized_) {
return absl::OkStatus();
}
// Verify API version compatibility
int api_version = asar_apiversion();
if (api_version < 300) { // Require at least API version 3.0
return absl::InternalError(absl::StrFormat(
"Asar API version %d is too old (required: 300+)", api_version));
}
initialized_ = true;
return absl::OkStatus();
}
void AsarWrapper::Shutdown() {
if (initialized_) {
// Note: Static library doesn't have asar_close()
initialized_ = false;
}
}
std::string AsarWrapper::GetVersion() const {
if (!initialized_) {
return "Not initialized";
}
int version = asar_version();
int major = version / 10000;
int minor = (version / 100) % 100;
int patch = version % 100;
return absl::StrFormat("%d.%d.%d", major, minor, patch);
}
int AsarWrapper::GetApiVersion() const {
if (!initialized_) {
return 0;
}
return asar_apiversion();
}
absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatch(
const std::string& patch_path,
std::vector<uint8_t>& rom_data,
const std::vector<std::string>& include_paths) {
if (!initialized_) {
return absl::FailedPreconditionError("Asar not initialized");
}
// Reset previous state
Reset();
AsarPatchResult result;
result.success = false;
// Prepare ROM data
int rom_size = static_cast<int>(rom_data.size());
int buffer_size = std::max(rom_size, 16 * 1024 * 1024); // At least 16MB buffer
// Resize ROM data if needed
if (rom_data.size() < buffer_size) {
rom_data.resize(buffer_size, 0);
}
// Apply the patch
bool patch_success = asar_patch(
patch_path.c_str(),
reinterpret_cast<char*>(rom_data.data()),
buffer_size,
&rom_size);
// Process results
ProcessErrors();
ProcessWarnings();
result.errors = last_errors_;
result.warnings = last_warnings_;
result.success = patch_success && last_errors_.empty();
if (result.success) {
// Resize ROM data to actual size
rom_data.resize(rom_size);
result.rom_size = rom_size;
// Extract symbols
ExtractSymbolsFromLastOperation();
result.symbols.reserve(symbol_table_.size());
for (const auto& [name, symbol] : symbol_table_) {
result.symbols.push_back(symbol);
}
// Calculate CRC32 if available
// Note: Asar might provide this, check if function exists
result.crc32 = 0; // TODO: Implement CRC32 calculation
} else {
return absl::InternalError(absl::StrFormat(
"Patch failed: %s", absl::StrJoin(last_errors_, "; ")));
}
return result;
}
absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatchFromString(
const std::string& patch_content,
std::vector<uint8_t>& rom_data,
const std::string& base_path) {
// Create temporary file for patch content
std::string temp_path = "/tmp/yaze_temp_patch.asm";
if (!base_path.empty()) {
// Ensure directory exists
std::filesystem::create_directories(base_path);
temp_path = base_path + "/temp_patch.asm";
}
std::ofstream temp_file(temp_path);
if (!temp_file) {
return absl::InternalError(absl::StrFormat(
"Failed to create temporary patch file at: %s", temp_path));
}
temp_file << patch_content;
temp_file.close();
auto result = ApplyPatch(temp_path, rom_data);
// Clean up temporary file
std::remove(temp_path.c_str());
return result;
}
absl::StatusOr<std::vector<AsarSymbol>> AsarWrapper::ExtractSymbols(
const std::string& asm_path,
const std::vector<std::string>& include_paths) {
if (!initialized_) {
return absl::FailedPreconditionError("Asar not initialized");
}
// Create a dummy ROM for symbol extraction
std::vector<uint8_t> dummy_rom(1024 * 1024, 0); // 1MB dummy ROM
auto result = ApplyPatch(asm_path, dummy_rom, include_paths);
if (!result.ok()) {
return result.status();
}
return result->symbols;
}
std::map<std::string, AsarSymbol> AsarWrapper::GetSymbolTable() const {
return symbol_table_;
}
std::optional<AsarSymbol> AsarWrapper::FindSymbol(const std::string& name) const {
auto it = symbol_table_.find(name);
if (it != symbol_table_.end()) {
return it->second;
}
return std::nullopt;
}
std::vector<AsarSymbol> AsarWrapper::GetSymbolsAtAddress(uint32_t address) const {
std::vector<AsarSymbol> symbols;
for (const auto& [name, symbol] : symbol_table_) {
if (symbol.address == address) {
symbols.push_back(symbol);
}
}
return symbols;
}
void AsarWrapper::Reset() {
if (initialized_) {
asar_reset();
}
symbol_table_.clear();
last_errors_.clear();
last_warnings_.clear();
}
absl::Status AsarWrapper::CreatePatch(
const std::vector<uint8_t>& original_rom,
const std::vector<uint8_t>& modified_rom,
const std::string& patch_path) {
// This is a complex operation that would require:
// 1. Analyzing differences between ROMs
// 2. Generating appropriate assembly code
// 3. Writing the patch file
// For now, return not implemented
return absl::UnimplementedError(
"Patch creation from ROM differences not yet implemented");
}
absl::Status AsarWrapper::ValidateAssembly(const std::string& asm_path) {
// Create a dummy ROM for validation
std::vector<uint8_t> dummy_rom(1024, 0);
auto result = ApplyPatch(asm_path, dummy_rom);
if (!result.ok()) {
return result.status();
}
if (!result->success) {
return absl::InvalidArgumentError(absl::StrFormat(
"Assembly validation failed: %s",
absl::StrJoin(result->errors, "; ")));
}
return absl::OkStatus();
}
void AsarWrapper::ProcessErrors() {
last_errors_.clear();
int error_count = 0;
const errordata* errors = asar_geterrors(&error_count);
for (int i = 0; i < error_count; ++i) {
last_errors_.push_back(std::string(errors[i].fullerrdata));
}
}
void AsarWrapper::ProcessWarnings() {
last_warnings_.clear();
int warning_count = 0;
const errordata* warnings = asar_getwarnings(&warning_count);
for (int i = 0; i < warning_count; ++i) {
last_warnings_.push_back(std::string(warnings[i].fullerrdata));
}
}
void AsarWrapper::ExtractSymbolsFromLastOperation() {
symbol_table_.clear();
// Extract labels using the correct API function
int symbol_count = 0;
const labeldata* labels = asar_getalllabels(&symbol_count);
for (int i = 0; i < symbol_count; ++i) {
AsarSymbol symbol;
symbol.name = std::string(labels[i].name);
symbol.address = labels[i].location;
symbol.file = ""; // Not available in basic API
symbol.line = 0; // Not available in basic API
symbol.opcode = ""; // Would need additional processing
symbol.comment = "";
symbol_table_[symbol.name] = symbol;
}
}
AsarSymbol AsarWrapper::ConvertAsarSymbol(const void* asar_symbol_data) const {
// This would convert from Asar's internal symbol representation
// to our AsarSymbol struct. Implementation depends on Asar's API.
AsarSymbol symbol;
// Placeholder implementation
return symbol;
}
} // namespace core
} // namespace app
} // namespace yaze

212
src/app/core/asar_wrapper.h Normal file
View File

@@ -0,0 +1,212 @@
#ifndef YAZE_APP_CORE_ASAR_WRAPPER_H
#define YAZE_APP_CORE_ASAR_WRAPPER_H
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <optional>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
namespace yaze {
namespace app {
namespace core {
/**
* @brief Symbol information extracted from Asar assembly
*/
struct AsarSymbol {
std::string name; // Symbol name
uint32_t address; // Memory address
std::string opcode; // Associated opcode if available
std::string file; // Source file
int line; // Line number in source
std::string comment; // Optional comment
};
/**
* @brief Asar patch result information
*/
struct AsarPatchResult {
bool success; // Whether patch was successful
std::vector<std::string> errors; // Error messages if any
std::vector<std::string> warnings; // Warning messages
std::vector<AsarSymbol> symbols; // Extracted symbols
uint32_t rom_size; // Final ROM size after patching
uint32_t crc32; // CRC32 checksum of patched ROM
};
/**
* @brief Modern C++ wrapper for Asar 65816 assembler integration
*
* This class provides a high-level interface for:
* - Patching ROMs with assembly code
* - Extracting symbol names and opcodes
* - Cross-platform compatibility (Windows, macOS, Linux)
*/
class AsarWrapper {
public:
AsarWrapper();
~AsarWrapper();
// Disable copy constructor and assignment
AsarWrapper(const AsarWrapper&) = delete;
AsarWrapper& operator=(const AsarWrapper&) = delete;
// Enable move constructor and assignment
AsarWrapper(AsarWrapper&&) = default;
AsarWrapper& operator=(AsarWrapper&&) = default;
/**
* @brief Initialize the Asar library
* @return Status indicating success or failure
*/
absl::Status Initialize();
/**
* @brief Clean up and close the Asar library
*/
void Shutdown();
/**
* @brief Check if Asar is initialized and ready
* @return True if initialized, false otherwise
*/
bool IsInitialized() const { return initialized_; }
/**
* @brief Get Asar version information
* @return Version string
*/
std::string GetVersion() const;
/**
* @brief Get Asar API version
* @return API version number
*/
int GetApiVersion() const;
/**
* @brief Apply an assembly patch to a ROM
* @param patch_path Path to the .asm patch file
* @param rom_data ROM data to patch (will be modified)
* @param include_paths Additional include paths for assembly files
* @return Patch result with status and extracted information
*/
absl::StatusOr<AsarPatchResult> ApplyPatch(
const std::string& patch_path,
std::vector<uint8_t>& rom_data,
const std::vector<std::string>& include_paths = {});
/**
* @brief Apply an assembly patch from string content
* @param patch_content Assembly source code as string
* @param rom_data ROM data to patch (will be modified)
* @param base_path Base path for resolving includes
* @return Patch result with status and extracted information
*/
absl::StatusOr<AsarPatchResult> ApplyPatchFromString(
const std::string& patch_content,
std::vector<uint8_t>& rom_data,
const std::string& base_path = "");
/**
* @brief Extract symbols from an assembly file without patching
* @param asm_path Path to the assembly file
* @param include_paths Additional include paths
* @return Vector of extracted symbols
*/
absl::StatusOr<std::vector<AsarSymbol>> ExtractSymbols(
const std::string& asm_path,
const std::vector<std::string>& include_paths = {});
/**
* @brief Get all available symbols from the last patch operation
* @return Map of symbol names to symbol information
*/
std::map<std::string, AsarSymbol> GetSymbolTable() const;
/**
* @brief Find a symbol by name
* @param name Symbol name to search for
* @return Symbol information if found
*/
std::optional<AsarSymbol> FindSymbol(const std::string& name) const;
/**
* @brief Get symbols at a specific address
* @param address Memory address to search
* @return Vector of symbols at that address
*/
std::vector<AsarSymbol> GetSymbolsAtAddress(uint32_t address) const;
/**
* @brief Reset the Asar state (clear errors, warnings, symbols)
*/
void Reset();
/**
* @brief Get the last error messages
* @return Vector of error strings
*/
std::vector<std::string> GetLastErrors() const { return last_errors_; }
/**
* @brief Get the last warning messages
* @return Vector of warning strings
*/
std::vector<std::string> GetLastWarnings() const { return last_warnings_; }
/**
* @brief Create a patch that can be applied to transform one ROM to another
* @param original_rom Original ROM data
* @param modified_rom Modified ROM data
* @param patch_path Output path for the generated patch
* @return Status indicating success or failure
*/
absl::Status CreatePatch(
const std::vector<uint8_t>& original_rom,
const std::vector<uint8_t>& modified_rom,
const std::string& patch_path);
/**
* @brief Validate an assembly file for syntax errors
* @param asm_path Path to the assembly file
* @return Status indicating validation result
*/
absl::Status ValidateAssembly(const std::string& asm_path);
private:
bool initialized_;
std::map<std::string, AsarSymbol> symbol_table_;
std::vector<std::string> last_errors_;
std::vector<std::string> last_warnings_;
/**
* @brief Process errors from Asar and store them
*/
void ProcessErrors();
/**
* @brief Process warnings from Asar and store them
*/
void ProcessWarnings();
/**
* @brief Extract symbols from the last Asar operation
*/
void ExtractSymbolsFromLastOperation();
/**
* @brief Convert Asar symbol data to AsarSymbol struct
*/
AsarSymbol ConvertAsarSymbol(const void* asar_symbol_data) const;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_ASAR_WRAPPER_H

View File

@@ -1,321 +0,0 @@
#ifndef YAZE_CORE_COMMON_H
#define YAZE_CORE_COMMON_H
#include <cstdint>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/container/flat_hash_map.h"
namespace yaze {
namespace app {
/**
* @namespace yaze::app::core
* @brief Core application logic and utilities.
*/
namespace core {
std::string UppercaseHexByte(uint8_t byte, bool leading = false);
std::string UppercaseHexWord(uint16_t word, bool leading = false);
std::string UppercaseHexLong(uint32_t dword);
std::string UppercaseHexLongLong(uint64_t qword);
bool StringReplace(std::string &str, const std::string &from,
const std::string &to);
/**
* @class ExperimentFlags
* @brief A class to manage experimental feature flags.
*/
class ExperimentFlags {
public:
struct Flags {
// Log instructions to the GUI debugger.
bool kLogInstructions = true;
// 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 saving of gfx groups to the rom.
bool kSaveGfxGroups = false;
// Flag to enable the change queue, which could have any anonymous
// save routine for the Rom. In practice, just the overworld tilemap
// and tile32 save.
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;
// Uses texture streaming from SDL for my dynamic updates.
bool kLoadTexturesAsStreaming = true;
// Save dungeon map edits to the Rom.
bool kSaveDungeonMaps = false;
// Save graphics sheet to the Rom.
bool kSaveGraphicsSheet = 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;
// Load custom overworld data from the ROM and enable UI.
bool kLoadCustomOverworld = false;
} 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();
}
std::string Serialize() const {
std::string result;
result +=
"kLogInstructions: " + std::to_string(flags_->kLogInstructions) + "\n";
result +=
"kUseNewImGuiInput: " + std::to_string(flags_->kUseNewImGuiInput) +
"\n";
result +=
"kSaveAllPalettes: " + std::to_string(flags_->kSaveAllPalettes) + "\n";
result +=
"kSaveGfxGroups: " + std::to_string(flags_->kSaveGfxGroups) + "\n";
result += "kSaveWithChangeQueue: " +
std::to_string(flags_->kSaveWithChangeQueue) + "\n";
result += "kDrawDungeonRoomGraphics: " +
std::to_string(flags_->kDrawDungeonRoomGraphics) + "\n";
result += "kNewFileDialogWrapper: " +
std::to_string(flags_->kNewFileDialogWrapper) + "\n";
result += "kLoadTexturesAsStreaming: " +
std::to_string(flags_->kLoadTexturesAsStreaming) + "\n";
result +=
"kSaveDungeonMaps: " + std::to_string(flags_->kSaveDungeonMaps) + "\n";
result += "kLogToConsole: " + std::to_string(flags_->kLogToConsole) + "\n";
result += "kDrawOverworldSprites: " +
std::to_string(flags_->overworld.kDrawOverworldSprites) + "\n";
result += "kSaveOverworldMaps: " +
std::to_string(flags_->overworld.kSaveOverworldMaps) + "\n";
result += "kSaveOverworldEntrances: " +
std::to_string(flags_->overworld.kSaveOverworldEntrances) + "\n";
result += "kSaveOverworldExits: " +
std::to_string(flags_->overworld.kSaveOverworldExits) + "\n";
result += "kSaveOverworldItems: " +
std::to_string(flags_->overworld.kSaveOverworldItems) + "\n";
result += "kSaveOverworldProperties: " +
std::to_string(flags_->overworld.kSaveOverworldProperties) + "\n";
return result;
}
private:
static std::shared_ptr<Flags> flags_;
};
/**
* @class NotifyValue
* @brief A class to manage a value that can be modified and notify when it
* changes.
*/
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_;
};
static bool log_to_console = false;
static std::string log_file_out = "log.txt";
template <typename... Args>
static void logf(const absl::FormatSpec<Args...> &format, const Args &...args) {
std::string message = absl::StrFormat(format, args...);
if (log_to_console) {
std::cout << message << std::endl;
}
static std::ofstream fout(log_file_out, std::ios::out | std::ios::app);
fout << message << std::endl;
}
struct StructuredLog {
std::string raw_message;
std::string category;
};
static absl::flat_hash_map<std::string, std::vector<std::string>> log_categories;
template <typename... Args>
static void logm(const std::string &category,
const absl::FormatSpec<Args...> &format, const Args &...args) {
std::string message = absl::StrFormat(format, args...);
if (log_to_console) {
std::cout << category << ": " << message << std::endl;
}
if (log_categories.contains(category)) {
log_categories[category].push_back(message);
} else {
log_categories[category] = {message};
}
}
class Logger {
public:
static void log(std::string message) {
static std::ofstream fout(log_file_out, std::ios::out | std::ios::app);
fout << message << std::endl;
}
};
constexpr uint32_t kFastRomRegion = 0x808000;
inline uint32_t SnesToPc(uint32_t addr) noexcept {
if (addr >= kFastRomRegion) {
addr -= kFastRomRegion;
}
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
return (temp + 0x0);
}
inline 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;
}
inline int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept {
return (bank << 16) | (high << 8) | low;
}
inline uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) noexcept {
uint32_t result = 0;
result = (bank << 16) | addr;
return result;
}
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true);
/**
* @brief 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);
void stle16b(uint8_t *const p_arr, uint16_t const p_val);
/**
* @brief 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);
// Load little endian halfword (16-bit) dereferenced from
uint16_t ldle16b(uint8_t const *const p_arr);
struct FolderItem {
std::string name;
std::vector<FolderItem> subfolders;
std::vector<std::string> files;
};
typedef struct FolderItem FolderItem;
void CreateBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &target,
std::vector<uint8_t> &patch);
void ApplyBpsPatch(const std::vector<uint8_t> &source,
const std::vector<uint8_t> &patch,
std::vector<uint8_t> &target);
constexpr std::string_view kYazeVersion = "0.2.1";
absl::StatusOr<std::string> CheckVersion(const char *version);
} // namespace core
} // namespace app
} // namespace yaze
#endif

View File

@@ -2,281 +2,82 @@
#include <SDL.h> #include <SDL.h>
#include <filesystem>
#include <memory>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/strings/str_format.h" #include "app/core/window.h"
#include "app/core/platform/font_loader.h"
#include "app/editor/editor_manager.h" #include "app/editor/editor_manager.h"
#include "app/gui/style.h" #include "app/gui/background_renderer.h"
#include "core/utils/file_util.h" #include "app/gui/theme_manager.h"
#include "imgui/backends/imgui_impl_sdl2.h" #include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h" #include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
namespace {
constexpr ImGuiWindowFlags kMainEditorFlags =
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
using ImGui::Begin;
using ImGui::End;
using ImGui::GetIO;
using ImGui::NewFrame;
using ImGui::SetNextWindowPos;
using ImGui::SetNextWindowSize;
void NewMasterFrame() {
const ImGuiIO &io = GetIO();
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
NewFrame();
SetNextWindowPos(gui::kZeroPos);
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
SetNextWindowSize(dimensions, ImGuiCond_Always);
if (!Begin("##YazeMain", nullptr, kMainEditorFlags)) {
End();
return;
}
}
} // namespace
absl::Status Controller::OnEntry(std::string filename) { absl::Status Controller::OnEntry(std::string filename) {
#if defined(__APPLE__) && defined(__MACH__) RETURN_IF_ERROR(CreateWindow(window_, SDL_WINDOW_RESIZABLE));
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1 editor_manager_.emulator().set_audio_buffer(window_.audio_buffer_.get());
platform_ = Platform::kiOS; editor_manager_.emulator().set_audio_device_id(window_.audio_device_);
#elif TARGET_OS_MAC == 1 editor_manager_.Initialize(filename);
platform_ = Platform::kMacOS;
#endif
#elif defined(_WIN32)
platform_ = Platform::kWindows;
#elif defined(__linux__)
platform_ = Platform::kLinux;
#else
platform_ = Platform::kUnknown;
#endif
RETURN_IF_ERROR(CreateWindow())
RETURN_IF_ERROR(CreateRenderer())
RETURN_IF_ERROR(CreateGuiContext())
RETURN_IF_ERROR(LoadAudioDevice())
editor_manager_.SetupScreen(filename);
active_ = true; active_ = true;
return absl::OkStatus(); return absl::OkStatus();
} }
void Controller::OnInput() { void Controller::OnInput() {
int wheel = 0; PRINT_IF_ERROR(HandleEvents(window_));
SDL_Event event;
ImGuiIO &io = ImGui::GetIO();
while (SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event);
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP: {
ImGuiIO &io = ImGui::GetIO();
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);
break;
}
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
active_ = false;
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
io.DisplaySize.x = static_cast<float>(event.window.data1);
io.DisplaySize.y = static_cast<float>(event.window.data2);
break;
default:
break;
}
break;
default:
break;
}
}
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);
} }
absl::Status Controller::OnLoad() { absl::Status Controller::OnLoad() {
if (editor_manager_.quit()) { if (editor_manager_.quit() || !window_.active_) {
active_ = false; active_ = false;
return absl::OkStatus();
} }
#if TARGET_OS_IPHONE != 1 #if TARGET_OS_IPHONE != 1
if (platform_ != Platform::kiOS) { ImGui_ImplSDLRenderer2_NewFrame();
NewMasterFrame(); ImGui_ImplSDL2_NewFrame();
} ImGui::NewFrame();
const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGuiWindowFlags window_flags =
ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
window_flags |=
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("DockSpaceWindow", nullptr, window_flags);
ImGui::PopStyleVar(3);
// Create DockSpace first
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
gui::DockSpaceRenderer::BeginEnhancedDockSpace(dockspace_id, ImVec2(0.0f, 0.0f),
ImGuiDockNodeFlags_PassthruCentralNode);
editor_manager_.DrawMenuBar(); // Draw the fixed menu bar at the top
ImGui::End();
#endif #endif
RETURN_IF_ERROR(editor_manager_.Update()); RETURN_IF_ERROR(editor_manager_.Update());
#if TARGET_OS_IPHONE != 1
if (platform_ != Platform::kiOS) {
End();
}
#endif
return absl::OkStatus();
}
absl::Status Controller::OnTestLoad() {
RETURN_IF_ERROR(test_editor_->Update());
return absl::OkStatus(); return absl::OkStatus();
} }
void Controller::DoRender() const { void Controller::DoRender() const {
ImGui::Render(); ImGui::Render();
SDL_RenderClear(Renderer::GetInstance().renderer()); SDL_RenderClear(Renderer::Get().renderer());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(),
Renderer::GetInstance().renderer()); Renderer::Get().renderer());
SDL_RenderPresent(Renderer::GetInstance().renderer()); SDL_RenderPresent(Renderer::Get().renderer());
} }
void Controller::OnExit() { void Controller::OnExit() { PRINT_IF_ERROR(ShutdownWindow(window_)); }
SDL_PauseAudioDevice(audio_device_, 1);
SDL_CloseAudioDevice(audio_device_);
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_Quit();
}
absl::Status Controller::CreateWindow() {
auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
if (flags()->kUseNewImGuiInput) {
sdl_flags |= SDL_INIT_GAMECONTROLLER;
}
if (SDL_Init(sdl_flags) != 0) {
return absl::InternalError(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
}
SDL_DisplayMode display_mode;
SDL_GetCurrentDisplayMode(0, &display_mode);
int screen_width = display_mode.w * 0.8;
int screen_height = display_mode.h * 0.8;
window_ = std::unique_ptr<SDL_Window, core::SDL_Deleter>(
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
screen_width, // width, in pixels
screen_height, // height, in pixels
SDL_WINDOW_RESIZABLE),
core::SDL_Deleter());
if (window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
return absl::OkStatus();
}
absl::Status Controller::CreateRenderer() {
return Renderer::GetInstance().CreateRenderer(window_.get());
}
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 based on the backend
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(),
Renderer::GetInstance().renderer());
ImGui_ImplSDLRenderer2_Init(Renderer::GetInstance().renderer());
RETURN_IF_ERROR(LoadFontFamilies());
// Set the default style
gui::ColorsYaze();
// Build a new ImGui frame
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
return absl::OkStatus();
}
absl::Status Controller::LoadFontFamilies() const {
// LoadSystemFonts();
return LoadPackageFonts();
}
absl::Status Controller::LoadAudioDevice() {
SDL_AudioSpec want, have;
SDL_memset(&want, 0, sizeof(want));
want.freq = audio_frequency_;
want.format = AUDIO_S16;
want.channels = 2;
want.samples = 2048;
want.callback = NULL; // Uses the queue
audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (audio_device_ == 0) {
return absl::InternalError(
absl::StrFormat("Failed to open audio: %s\n", SDL_GetError()));
}
// audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
audio_buffer_ = std::make_shared<int16_t>(audio_frequency_ / 50 * 4);
SDL_PauseAudioDevice(audio_device_, 0);
editor_manager_.emulator().set_audio_buffer(audio_buffer_.get());
editor_manager_.emulator().set_audio_device_id(audio_device_);
return absl::OkStatus();
}
absl::Status Controller::LoadConfigFiles() {
// Create and load a dotfile for the application
// This will store the user's preferences and settings
std::string config_directory = GetConfigDirectory(platform_);
// Create the directory if it doesn't exist
if (!std::filesystem::exists(config_directory)) {
if (!std::filesystem::create_directory(config_directory)) {
return absl::InternalError(absl::StrFormat(
"Failed to create config directory %s", config_directory));
}
}
// Check if the config file exists
std::string config_file = config_directory + "yaze.cfg";
if (!std::filesystem::exists(config_file)) {
// Create the file if it doesn't exist
std::ofstream file(config_file);
if (!file.is_open()) {
return absl::InternalError(
absl::StrFormat("Failed to create config file %s", config_file));
}
file.close();
}
return absl::OkStatus();
}
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -6,19 +6,12 @@
#include <memory> #include <memory>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/platform/renderer.h" #include "app/core/window.h"
#include "app/core/utils/file_util.h"
#include "app/editor/editor_manager.h" #include "app/editor/editor_manager.h"
#include "app/editor/editor.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imconfig.h"
#include "imgui/imgui.h"
int main(int argc, char **argv); int main(int argc, char **argv);
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
/** /**
@@ -27,50 +20,28 @@ namespace core {
* This class is responsible for managing the main window and the * This class is responsible for managing the main window and the
* main editor. It is the main entry point for the application. * main editor. It is the main entry point for the application.
*/ */
class Controller : public ExperimentFlags { class Controller {
public: public:
bool IsActive() const { return active_; } bool IsActive() const { return active_; }
absl::Status OnEntry(std::string filename = ""); absl::Status OnEntry(std::string filename = "");
void OnInput(); void OnInput();
absl::Status OnLoad(); absl::Status OnLoad();
absl::Status OnTestLoad();
void DoRender() const; void DoRender() const;
void OnExit(); void OnExit();
absl::Status CreateWindow(); auto window() -> SDL_Window * { return window_.window_.get(); }
absl::Status CreateRenderer();
absl::Status CreateGuiContext();
absl::Status LoadFontFamilies() const;
absl::Status LoadAudioDevice();
absl::Status LoadConfigFiles();
void SetupScreen(std::string filename = "") {
editor_manager_.SetupScreen(filename);
}
auto editor_manager() -> editor::EditorManager & { return editor_manager_; }
auto renderer() -> SDL_Renderer * {
return Renderer::GetInstance().renderer();
}
auto window() -> SDL_Window * { return window_.get(); }
void init_test_editor(editor::Editor *editor) { test_editor_ = editor; }
void set_active(bool active) { active_ = active; } void set_active(bool active) { active_ = active; }
auto active() const { return active_; }
private: private:
friend int ::main(int argc, char **argv); friend int ::main(int argc, char **argv);
bool active_; bool active_ = false;
Platform platform_; core::Window window_;
editor::Editor *test_editor_;
editor::EditorManager editor_manager_; editor::EditorManager editor_manager_;
int audio_frequency_ = 48000;
SDL_AudioDeviceID audio_device_;
std::shared_ptr<int16_t> audio_buffer_;
std::shared_ptr<SDL_Window> window_;
}; };
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_CONTROLLER_H #endif // YAZE_APP_CORE_CONTROLLER_H

View File

@@ -1,10 +1,10 @@
set( set(
YAZE_APP_CORE_SRC YAZE_APP_CORE_SRC
app/core/common.cc
app/core/controller.cc app/core/controller.cc
app/emu/emulator.cc app/emu/emulator.cc
app/core/project.cc app/core/project.cc
app/core/utils/file_util.cc app/core/window.cc
app/core/asar_wrapper.cc
) )
if (WIN32 OR MINGW OR UNIX AND NOT APPLE) if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
@@ -17,12 +17,12 @@ endif()
if(APPLE) if(APPLE)
list(APPEND YAZE_APP_CORE_SRC list(APPEND YAZE_APP_CORE_SRC
app/core/platform/file_dialog.cc
app/core/platform/file_dialog.mm app/core/platform/file_dialog.mm
app/core/platform/app_delegate.mm app/core/platform/app_delegate.mm
app/core/platform/font_loader.cc app/core/platform/font_loader.cc
app/core/platform/font_loader.mm app/core/platform/font_loader.mm
app/core/platform/clipboard.mm app/core/platform/clipboard.mm
app/core/platform/file_path.mm
) )
find_library(COCOA_LIBRARY Cocoa) find_library(COCOA_LIBRARY Cocoa)

164
src/app/core/features.h Normal file
View File

@@ -0,0 +1,164 @@
#ifndef YAZE_APP_CORE_FEATURES_H
#define YAZE_APP_CORE_FEATURES_H
#include <string>
#include "imgui/imgui.h"
namespace yaze {
namespace core {
/**
* @class FeatureFlags
* @brief A class to manage experimental feature flags.
*/
class FeatureFlags {
public:
struct Flags {
// Log instructions to the GUI debugger.
bool kLogInstructions = true;
// Flag to enable the saving of all palettes to the Rom.
bool kSaveAllPalettes = false;
// Flag to enable the saving of gfx groups to the rom.
bool kSaveGfxGroups = false;
// Flag to enable the change queue, which could have any anonymous
// save routine for the Rom. In practice, just the overworld tilemap
// and tile32 save.
bool kSaveWithChangeQueue = false;
// Save dungeon map edits to the Rom.
bool kSaveDungeonMaps = false;
// Save graphics sheet to the Rom.
bool kSaveGraphicsSheet = false;
// Log to the console.
bool kLogToConsole = false;
// Use NFD (Native File Dialog) instead of bespoke file dialog implementation.
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
bool kUseNativeFileDialog = true;
#else
bool kUseNativeFileDialog = false;
#endif
// 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;
// Load custom overworld data from the ROM and enable UI.
bool kLoadCustomOverworld = false;
// Apply ZSCustomOverworld ASM patches when upgrading ROM versions.
bool kApplyZSCustomOverworldASM = false;
} overworld;
};
static Flags &get() {
static Flags instance;
return instance;
}
std::string Serialize() const {
std::string result;
result +=
"kLogInstructions: " + std::to_string(get().kLogInstructions) + "\n";
result +=
"kSaveAllPalettes: " + std::to_string(get().kSaveAllPalettes) + "\n";
result += "kSaveGfxGroups: " + std::to_string(get().kSaveGfxGroups) + "\n";
result +=
"kSaveWithChangeQueue: " + std::to_string(get().kSaveWithChangeQueue) +
"\n";
result +=
"kSaveDungeonMaps: " + std::to_string(get().kSaveDungeonMaps) + "\n";
result += "kLogToConsole: " + std::to_string(get().kLogToConsole) + "\n";
result += "kDrawOverworldSprites: " +
std::to_string(get().overworld.kDrawOverworldSprites) + "\n";
result += "kSaveOverworldMaps: " +
std::to_string(get().overworld.kSaveOverworldMaps) + "\n";
result += "kSaveOverworldEntrances: " +
std::to_string(get().overworld.kSaveOverworldEntrances) + "\n";
result += "kSaveOverworldExits: " +
std::to_string(get().overworld.kSaveOverworldExits) + "\n";
result += "kSaveOverworldItems: " +
std::to_string(get().overworld.kSaveOverworldItems) + "\n";
result += "kSaveOverworldProperties: " +
std::to_string(get().overworld.kSaveOverworldProperties) + "\n";
result += "kLoadCustomOverworld: " +
std::to_string(get().overworld.kLoadCustomOverworld) + "\n";
result += "kApplyZSCustomOverworldASM: " +
std::to_string(get().overworld.kApplyZSCustomOverworldASM) + "\n";
result += "kUseNativeFileDialog: " +
std::to_string(get().kUseNativeFileDialog) + "\n";
return result;
}
};
using ImGui::BeginMenu;
using ImGui::Checkbox;
using ImGui::EndMenu;
using ImGui::MenuItem;
using ImGui::Separator;
struct FlagsMenu {
void DrawOverworldFlags() {
Checkbox("Enable Overworld Sprites",
&FeatureFlags::get().overworld.kDrawOverworldSprites);
Separator();
Checkbox("Save Overworld Maps",
&FeatureFlags::get().overworld.kSaveOverworldMaps);
Checkbox("Save Overworld Entrances",
&FeatureFlags::get().overworld.kSaveOverworldEntrances);
Checkbox("Save Overworld Exits",
&FeatureFlags::get().overworld.kSaveOverworldExits);
Checkbox("Save Overworld Items",
&FeatureFlags::get().overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&FeatureFlags::get().overworld.kSaveOverworldProperties);
Checkbox("Load Custom Overworld",
&FeatureFlags::get().overworld.kLoadCustomOverworld);
Checkbox("Apply ZSCustomOverworld ASM",
&FeatureFlags::get().overworld.kApplyZSCustomOverworldASM);
}
void DrawDungeonFlags() {
Checkbox("Save Dungeon Maps", &FeatureFlags::get().kSaveDungeonMaps);
}
void DrawResourceFlags() {
Checkbox("Save All Palettes", &FeatureFlags::get().kSaveAllPalettes);
Checkbox("Save Gfx Groups", &FeatureFlags::get().kSaveGfxGroups);
Checkbox("Save Graphics Sheets", &FeatureFlags::get().kSaveGraphicsSheet);
}
void DrawSystemFlags() {
Checkbox("Enable Console Logging", &FeatureFlags::get().kLogToConsole);
Checkbox("Log Instructions to Emulator Debugger",
&FeatureFlags::get().kLogInstructions);
Checkbox("Use Native File Dialog (NFD)", &FeatureFlags::get().kUseNativeFileDialog);
}
};
} // namespace core
} // namespace yaze
#endif // YAZE_APP_CORE_FEATURES_H

View File

@@ -51,7 +51,7 @@ void yaze_initialize_cocoa();
/** /**
* @brief Run the Cocoa application delegate. * @brief Run the Cocoa application delegate.
*/ */
void yaze_run_cocoa_app_delegate(const char *filename); int yaze_run_cocoa_app_delegate(const char *filename);
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"

View File

@@ -206,15 +206,16 @@
} }
- (void)openFileAction:(id)sender { - (void)openFileAction:(id)sender {
if (!yaze::app::SharedRom::shared_rom_ // TODO: Re-implmenent this without the SharedRom singleton
->LoadFromFile(yaze::app::core::FileDialogWrapper::ShowOpenFileDialog()) // if (!yaze::SharedRom::shared_rom_
.ok()) { // ->LoadFromFile(yaze::core::FileDialogWrapper::ShowOpenFileDialog())
NSAlert *alert = [[NSAlert alloc] init]; // .ok()) {
[alert setMessageText:@"Error"]; // NSAlert *alert = [[NSAlert alloc] init];
[alert setInformativeText:@"Failed to load file."]; // [alert setMessageText:@"Error"];
[alert addButtonWithTitle:@"OK"]; // [alert setInformativeText:@"Failed to load file."];
[alert runModal]; // [alert addButtonWithTitle:@"OK"];
} // [alert runModal];
// }
} }
- (void)cutAction:(id)sender { - (void)cutAction:(id)sender {
@@ -236,20 +237,26 @@ extern "C" void yaze_initialize_cococa() {
} }
} }
extern "C" void yaze_run_cocoa_app_delegate(const char *filename) { extern "C" int yaze_run_cocoa_app_delegate(const char *filename) {
yaze_initialize_cococa(); yaze_initialize_cococa();
yaze::app::core::Controller controller; auto controller = std::make_unique<yaze::core::Controller>();
RETURN_VOID_IF_ERROR(controller.OnEntry(filename)); EXIT_IF_ERROR(controller->OnEntry(filename));
while (controller.IsActive()) { while (controller->IsActive()) {
@autoreleasepool { @autoreleasepool {
controller.OnInput(); controller->OnInput();
if (auto status = controller.OnLoad(); !status.ok()) { if (auto status = controller->OnLoad(); !status.ok()) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Error"];
[alert setInformativeText:[NSString stringWithUTF8String:status.message().data()]];
[alert addButtonWithTitle:@"OK"];
[alert runModal];
break; break;
} }
controller.DoRender(); controller->DoRender();
} }
} }
controller.OnExit(); controller->OnExit();
return EXIT_SUCCESS;
} }
#endif #endif

View File

@@ -4,13 +4,13 @@
#include <vector> #include <vector>
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
#if YAZE_LIB_PNG == 1
void CopyImageToClipboard(const std::vector<uint8_t>& data) {} void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
int& height) {} int& height) {}
#endif
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -5,14 +5,14 @@
#include <vector> #include <vector>
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
#if YAZE_LIB_PNG == 1
void CopyImageToClipboard(const std::vector<uint8_t> &data); void CopyImageToClipboard(const std::vector<uint8_t> &data);
void GetImageFromClipboard(std::vector<uint8_t> &data, int &width, int &height); void GetImageFromClipboard(std::vector<uint8_t> &data, int &width, int &height);
#endif
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H #endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H

View File

@@ -6,7 +6,8 @@
#ifdef TARGET_OS_MAC #ifdef TARGET_OS_MAC
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
void yaze::app::core::CopyImageToClipboard(const std::vector<uint8_t>& pngData) { #if YAZE_LIB_PNG == 1
void yaze::core::CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()]; NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()];
NSImage* image = [[NSImage alloc] initWithData:data]; NSImage* image = [[NSImage alloc] initWithData:data];
@@ -15,7 +16,7 @@ void yaze::app::core::CopyImageToClipboard(const std::vector<uint8_t>& pngData)
[pasteboard writeObjects:@[ image ]]; [pasteboard writeObjects:@[ image ]];
} }
void yaze::app::core::GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) { void yaze::core::GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSArray* classArray = [NSArray arrayWithObject:[NSImage class]]; NSArray* classArray = [NSArray arrayWithObject:[NSImage class]];
NSDictionary* options = [NSDictionary dictionary]; NSDictionary* options = [NSDictionary dictionary];
@@ -41,5 +42,6 @@ void yaze::app::core::GetImageFromClipboard(std::vector<uint8_t>& pixel_data, in
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGContextRelease(context); CGContextRelease(context);
} }
#endif // YAZE_LIB_PNG
#endif #endif // TARGET_OS_MAC

View File

@@ -4,15 +4,144 @@
// Include Windows-specific headers // Include Windows-specific headers
#include <shobjidl.h> #include <shobjidl.h>
#include <windows.h> #include <windows.h>
#endif // _WIN32 #else // Linux and MacOS
#include <dirent.h>
#include <sys/stat.h>
#endif
#include <fstream>
#include <sstream>
#include <cstring>
#include "app/core/features.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
std::string GetFileExtension(const std::string &filename) {
size_t dot = filename.find_last_of(".");
if (dot == std::string::npos) {
return "";
}
return filename.substr(dot + 1);
}
std::string GetFileName(const std::string &filename) {
size_t slash = filename.find_last_of("/");
if (slash == std::string::npos) {
return filename;
}
return filename.substr(slash + 1);
}
std::string LoadFile(const std::string &filename) {
std::string contents;
std::ifstream file(filename);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
} else {
// Throw an exception
throw std::runtime_error("Could not open file: " + filename);
}
return contents;
}
std::string LoadConfigFile(const std::string &filename) {
std::string contents;
#if defined(_WIN32)
Platform platform = Platform::kWindows;
#elif defined(__APPLE__)
Platform platform = Platform::kMacOS;
#else
Platform platform = Platform::kLinux;
#endif
std::string filepath = GetConfigDirectory() + "/" + filename;
std::ifstream file(filepath);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
}
return contents;
}
void SaveFile(const std::string &filename, const std::string &contents) {
std::string filepath = GetConfigDirectory() + "/" + filename;
std::ofstream file(filepath);
if (file.is_open()) {
file << contents;
file.close();
}
}
std::string GetResourcePath(const std::string &resource_path) {
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
return kBundlePath + resource_path;
#else
return GetBundleResourcePath() + "Contents/Resources/" + resource_path;
#endif
#else
return resource_path; // On Linux/Windows, resources are relative to executable
#endif
}
std::string GetConfigDirectory() {
std::string config_directory = ".yaze";
Platform platform;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
platform = Platform::kiOS;
#elif TARGET_OS_MAC == 1
platform = Platform::kMacOS;
#else
platform = Platform::kMacOS; // Default for macOS
#endif
#elif defined(_WIN32)
platform = Platform::kWindows;
#elif defined(__linux__)
platform = Platform::kLinux;
#else
platform = Platform::kUnknown;
#endif
switch (platform) {
case Platform::kWindows:
config_directory = "~/AppData/Roaming/yaze";
break;
case Platform::kMacOS:
case Platform::kLinux:
config_directory = "~/.config/yaze";
break;
default:
break;
}
return config_directory;
}
#ifdef _WIN32 #ifdef _WIN32
std::string FileDialogWrapper::ShowOpenFileDialog() { // Forward declaration for the main implementation
std::string ShowOpenFileDialogImpl();
std::string FileDialogWrapper::ShowOpenFileDialog() {
return ShowOpenFileDialogImpl();
}
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
// Windows doesn't use NFD in this implementation, fallback to bespoke
return ShowOpenFileDialogBespoke();
}
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
return ShowOpenFileDialogImpl();
}
std::string ShowOpenFileDialogImpl() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL; IFileDialog *pfd = NULL;
@@ -44,7 +173,23 @@ namespace core {
return file_path_windows; return file_path_windows;
} }
std::string FileDialogWrapper::ShowOpenFolderDialog() { // Forward declaration for folder dialog implementation
std::string ShowOpenFolderDialogImpl();
std::string FileDialogWrapper::ShowOpenFolderDialog() {
return ShowOpenFolderDialogImpl();
}
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
// Windows doesn't use NFD in this implementation, fallback to bespoke
return ShowOpenFolderDialogBespoke();
}
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
return ShowOpenFolderDialogImpl();
}
std::string ShowOpenFolderDialogImpl() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL; IFileDialog *pfd = NULL;
@@ -120,26 +265,123 @@ std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
#elif defined(__linux__) #elif defined(__linux__)
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
#include <nfd.h>
#endif
std::string FileDialogWrapper::ShowOpenFileDialog() { std::string FileDialogWrapper::ShowOpenFileDialog() {
return "Linux: Open file dialog"; // Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFileDialogNFD();
} else {
return ShowOpenFileDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdu8filteritem_t filters[1] = {{"Rom File", "sfc,smc"}};
nfdopendialogu8args_t args = {0};
args.filterList = filters;
args.filterCount = 1;
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path_linux(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path_linux;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "Error: NFD_OpenDialog";
#else
// NFD not available - fallback to bespoke
return ShowOpenFileDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
// Implement bespoke file dialog or return placeholder
// This would contain the custom macOS implementation
return ""; // Placeholder for bespoke implementation
} }
std::string FileDialogWrapper::ShowOpenFolderDialog() { std::string FileDialogWrapper::ShowOpenFolderDialog() {
return "Linux: Open folder dialog"; // Use global feature flag to choose implementation
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFolderDialogNFD();
} else {
return ShowOpenFolderDialogBespoke();
}
}
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
if (result == NFD_OKAY) {
std::string folder_path_linux(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return folder_path_linux;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "Error: NFD_PickFolder";
#else
// NFD not available - fallback to bespoke
return ShowOpenFolderDialogBespoke();
#endif
}
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
// Implement bespoke folder dialog or return placeholder
// This would contain the custom macOS implementation
return ""; // Placeholder for bespoke implementation
} }
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder( std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder_path) { const std::string &folder_path) {
return {"Linux: Subdirectories in folder"}; std::vector<std::string> subdirectories;
DIR *dir;
struct dirent *ent;
if ((dir = opendir(folder_path.c_str())) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_DIR) {
if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
subdirectories.push_back(ent->d_name);
}
}
}
closedir(dir);
}
return subdirectories;
} }
std::vector<std::string> FileDialogWrapper::GetFilesInFolder( std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string& folder_path) { const std::string &folder_path) {
return {"Linux: Files in folder"}; std::vector<std::string> files;
DIR *dir;
struct dirent *ent;
if ((dir = opendir(folder_path.c_str())) != NULL) {
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_REG) {
files.push_back(ent->d_name);
}
}
closedir(dir);
}
return files;
} }
#endif #endif
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -5,30 +5,50 @@
#include <vector> #include <vector>
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
class FileDialogWrapper { class FileDialogWrapper {
public: public:
/** /**
* @brief ShowOpenFileDialog opens a file dialog and returns the selected * @brief ShowOpenFileDialog opens a file dialog and returns the selected
* filepath. * filepath. Uses global feature flag to choose implementation.
*/ */
static std::string ShowOpenFileDialog(); static std::string ShowOpenFileDialog();
/** /**
* @brief ShowOpenFolderDialog opens a file dialog and returns the selected * @brief ShowOpenFolderDialog opens a file dialog and returns the selected
* folder path. * folder path. Uses global feature flag to choose implementation.
*/ */
static std::string ShowOpenFolderDialog(); static std::string ShowOpenFolderDialog();
// Specific implementations for testing
static std::string ShowOpenFileDialogNFD();
static std::string ShowOpenFileDialogBespoke();
static std::string ShowOpenFolderDialogNFD();
static std::string ShowOpenFolderDialogBespoke();
static std::vector<std::string> GetSubdirectoriesInFolder( static std::vector<std::string> GetSubdirectoriesInFolder(
const std::string& folder_path); const std::string &folder_path);
static std::vector<std::string> GetFilesInFolder( static std::vector<std::string> GetFilesInFolder(
const std::string& folder_path); const std::string &folder_path);
}; };
/**
* @brief GetBundleResourcePath returns the path to the bundle resource
* directory. Specific to MacOS.
*/
std::string GetBundleResourcePath();
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
std::string GetFileExtension(const std::string &filename);
std::string GetFileName(const std::string &filename);
std::string LoadFile(const std::string &filename);
std::string LoadConfigFile(const std::string &filename);
std::string GetConfigDirectory();
std::string GetResourcePath(const std::string &resource_path);
void SaveFile(const std::string &filename, const std::string &data);
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H #endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H

View File

@@ -1,11 +1,18 @@
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#include <iostream>
#include <string> #include <string>
#include <vector> #include <vector>
#include "imgui/imgui.h"
#include "app/core/features.h"
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
#include <nfd.h>
#endif
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */ /* Apple OSX and iOS (Darwin). */
#include <Foundation/Foundation.h>
#include <TargetConditionals.h> #include <TargetConditionals.h>
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
@@ -37,30 +44,36 @@ std::string ShowOpenFileDialogSync() {
return result; return result;
} }
} } // namespace
std::string yaze::app::core::FileDialogWrapper::ShowOpenFileDialog() { std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() { return ShowOpenFileDialogSync(); }
return ShowOpenFileDialogSync();
}
std::string yaze::app::core::FileDialogWrapper::ShowOpenFolderDialog() { return ""; } std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetFilesInFolder( std::vector<std::string> yaze::core::FileDialogWrapper::GetFilesInFolder(
const std::string &folder) { const std::string &folder) {
return {}; return {};
} }
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetSubdirectoriesInFolder( std::vector<std::string> yaze::core::FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string &folder) { const std::string &folder) {
return {}; return {};
} }
std::string yaze::core::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#elif TARGET_OS_MAC == 1 #elif TARGET_OS_MAC == 1
/* macOS */ /* macOS */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
std::string yaze::app::core::FileDialogWrapper::ShowOpenFileDialog() { std::string yaze::core::FileDialogWrapper::ShowOpenFileDialogBespoke() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel]; NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:YES]; [openPanel setCanChooseFiles:YES];
[openPanel setCanChooseDirectories:NO]; [openPanel setCanChooseDirectories:NO];
@@ -75,7 +88,75 @@ std::string yaze::app::core::FileDialogWrapper::ShowOpenFileDialog() {
return ""; return "";
} }
std::string yaze::app::core::FileDialogWrapper::ShowOpenFolderDialog() { // Global feature flag-based dispatch methods
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() {
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFileDialogNFD();
} else {
return ShowOpenFileDialogBespoke();
}
}
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialog() {
if (FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFolderDialogNFD();
} else {
return ShowOpenFolderDialogBespoke();
}
}
// NFD implementation for macOS (fallback to bespoke if NFD not available)
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdu8filteritem_t filters[1] = {{"Rom File", "sfc,smc"}};
nfdopendialogu8args_t args = {0};
args.filterList = filters;
args.filterCount = 1;
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
if (result == NFD_OKAY) {
std::string file_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return file_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not compiled in, use bespoke
return ShowOpenFileDialogBespoke();
#endif
}
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
if (result == NFD_OKAY) {
std::string folder_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return folder_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not compiled in, use bespoke
return ShowOpenFolderDialogBespoke();
#endif
}
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialogBespoke() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel]; NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO]; [openPanel setCanChooseFiles:NO];
[openPanel setCanChooseDirectories:YES]; [openPanel setCanChooseDirectories:YES];
@@ -90,7 +171,7 @@ std::string yaze::app::core::FileDialogWrapper::ShowOpenFolderDialog() {
return ""; return "";
} }
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetFilesInFolder( std::vector<std::string> yaze::core::FileDialogWrapper::GetFilesInFolder(
const std::string& folder) { const std::string& folder) {
std::vector<std::string> filenames; std::vector<std::string> filenames;
NSFileManager* fileManager = [NSFileManager defaultManager]; NSFileManager* fileManager = [NSFileManager defaultManager];
@@ -106,7 +187,7 @@ std::vector<std::string> yaze::app::core::FileDialogWrapper::GetFilesInFolder(
return filenames; return filenames;
} }
std::vector<std::string> yaze::app::core::FileDialogWrapper::GetSubdirectoriesInFolder( std::vector<std::string> yaze::core::FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder) { const std::string& folder) {
std::vector<std::string> subdirectories; std::vector<std::string> subdirectories;
NSFileManager* fileManager = [NSFileManager defaultManager]; NSFileManager* fileManager = [NSFileManager defaultManager];
@@ -127,6 +208,14 @@ std::vector<std::string> yaze::app::core::FileDialogWrapper::GetSubdirectoriesIn
} }
return subdirectories; return subdirectories;
} }
std::string yaze::core::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#else #else
// Unsupported platform // Unsupported platform
#endif // TARGET_OS_MAC #endif // TARGET_OS_MAC

View File

@@ -1,20 +0,0 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H
#define YAZE_APP_CORE_PLATFORM_FILE_PATH_H
#include <string>
namespace yaze {
namespace app {
namespace core {
/**
* @brief GetBundleResourcePath returns the path to the bundle resource
* directory. Specific to MacOS.
*/
std::string GetBundleResourcePath();
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_FILE_PATH_H

View File

@@ -1,26 +0,0 @@
#include "file_path.h"
#include <iostream>
#include <string>
#if defined(__APPLE__) && defined(__MACH__)
#include <Foundation/Foundation.h>
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
std::string yaze::app::core::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#elif TARGET_OS_MAC == 1
std::string yaze::app::core::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#endif
#endif

View File

@@ -1,124 +1,141 @@
#include "app/core/platform/font_loader.h" #include "app/core/platform/font_loader.h"
#include <filesystem>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
#include <filesystem>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/core/platform/file_path.h" #include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "util/macro.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
absl::Status LoadPackageFonts() { static const char* KARLA_REGULAR = "Karla-Regular.ttf";
ImGuiIO &io = ImGui::GetIO(); static const char* ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char* COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char* DROID_SANS = "DroidSans.ttf";
static const char* NOTO_SANS_JP = "NotoSansJP.ttf";
static const char* IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const char *KARLA_REGULAR = "Karla-Regular.ttf"; static const float FONT_SIZE_DEFAULT = 16.0f;
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf"; static const float FONT_SIZE_DROID_SANS = 18.0f;
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf"; static const float ICON_FONT_SIZE = 18.0f;
static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 16.0f;
static const float FONT_SIZE_DROID_SANS = 18.0f;
static const float ICON_FONT_SIZE = 18.0f;
// Icon configuration namespace {
std::string SetFontPath(const std::string& font_path) {
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
return kBundlePath + font_path;
#else
return absl::StrCat(GetBundleResourcePath(), "Contents/Resources/font/",
font_path);
#endif
#else
return absl::StrCat("assets/font/", font_path);
#endif
}
absl::Status LoadFont(const FontConfig& font_config) {
ImGuiIO& io = ImGui::GetIO();
std::string actual_font_path = SetFontPath(font_config.font_path);
// Check if the file exists with std library first, since ImGui IO will assert
// if the file does not exist
if (!std::filesystem::exists(actual_font_path)) {
return absl::InternalError(
absl::StrFormat("Font file %s does not exist", actual_font_path));
}
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
font_config.font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
return absl::OkStatus();
}
absl::Status AddIconFont(const FontConfig& config) {
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0}; static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config; ImFontConfig icons_config;
icons_config.MergeMode = true; icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f; icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f; icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true; icons_config.PixelSnapH = true;
std::string icon_font_path = SetFontPath(FONT_ICON_FILE_NAME_MD);
ImGuiIO& io = ImGui::GetIO();
if (!io.Fonts->AddFontFromFileTTF(icon_font_path.c_str(), ICON_FONT_SIZE,
&icons_config, icons_ranges)) {
return absl::InternalError("Failed to add icon fonts");
}
return absl::OkStatus();
}
// Japanese font configuration absl::Status AddJapaneseFont(const FontConfig& config) {
ImFontConfig japanese_font_config; ImFontConfig japanese_font_config;
japanese_font_config.MergeMode = true; japanese_font_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f; japanese_font_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f; japanese_font_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true; japanese_font_config.PixelSnapH = true;
std::string japanese_font_path = SetFontPath(NOTO_SANS_JP);
ImGuiIO& io = ImGui::GetIO();
if (!io.Fonts->AddFontFromFileTTF(japanese_font_path.data(), ICON_FONT_SIZE,
&japanese_font_config,
io.Fonts->GetGlyphRangesJapanese())) {
return absl::InternalError("Failed to add Japanese fonts");
}
return absl::OkStatus();
}
// List of fonts to be loaded } // namespace
std::vector<const char *> font_paths = {
KARLA_REGULAR, ROBOTO_MEDIUM, COUSINE_REGULAR, IBM_PLEX_JP, DROID_SANS}; absl::Status LoadPackageFonts() {
if (font_registry.fonts.empty()) {
// Initialize the font names and sizes
font_registry.fonts = {
{KARLA_REGULAR, FONT_SIZE_DEFAULT},
{ROBOTO_MEDIUM, FONT_SIZE_DEFAULT},
{COUSINE_REGULAR, FONT_SIZE_DEFAULT},
{IBM_PLEX_JP, FONT_SIZE_DEFAULT},
{DROID_SANS, FONT_SIZE_DROID_SANS},
};
}
// Load fonts with associated icon and Japanese merges // Load fonts with associated icon and Japanese merges
for (const auto &font_path : font_paths) { for (const auto& font_config : font_registry.fonts) {
float font_size = RETURN_IF_ERROR(LoadFont(font_config));
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT; RETURN_IF_ERROR(AddIconFont(font_config));
RETURN_IF_ERROR(AddJapaneseFont(font_config));
std::string actual_font_path;
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
actual_font_path = kBundlePath + font_path;
#else
actual_font_path = absl::StrCat(GetBundleResourcePath(),
"Contents/Resources/font/", font_path);
#endif
#else
actual_font_path = std::filesystem::absolute(font_path).string();
#endif
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
// Merge icon set
std::string actual_icon_font_path = "";
const char *icon_font_path = FONT_ICON_FILE_NAME_MD;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kIconBundlePath = GetBundleResourcePath();
actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf";
#else
actual_icon_font_path =
absl::StrCat(GetBundleResourcePath(),
"Contents/Resources/font/MaterialIcons-Regular.ttf");
#endif
#else
actual_icon_font_path = std::filesystem::absolute(icon_font_path).string();
#endif
io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(), ICON_FONT_SIZE,
&icons_config, icons_ranges);
// Merge Japanese font
std::string actual_japanese_font_path = "";
const char *japanese_font_path = NOTO_SANS_JP;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kJapaneseBundlePath = GetBundleResourcePath();
actual_japanese_font_path = kJapaneseBundlePath + japanese_font_path;
#else
actual_japanese_font_path =
absl::StrCat(GetBundleResourcePath(), "Contents/Resources/font/",
japanese_font_path);
#endif
#else
actual_japanese_font_path =
std::filesystem::absolute(japanese_font_path).string();
#endif
io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f,
&japanese_font_config,
io.Fonts->GetGlyphRangesJapanese());
} }
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status ReloadPackageFont(const FontConfig& config) {
ImGuiIO& io = ImGui::GetIO();
std::string actual_font_path = SetFontPath(config.font_path);
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
config.font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
RETURN_IF_ERROR(AddIconFont(config));
RETURN_IF_ERROR(AddJapaneseFont(config));
return absl::OkStatus();
}
#ifdef _WIN32 #ifdef _WIN32
#include <Windows.h> #include <Windows.h>
int CALLBACK EnumFontFamExProc(const LOGFONT *lpelfe, const TEXTMETRIC *lpntme, int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
DWORD FontType, LPARAM lParam) { DWORD FontType, LPARAM lParam) {
// Step 3: Load the font into ImGui // Step 3: Load the font into ImGui
ImGuiIO &io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f); io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
return 1; return 1;
@@ -141,8 +158,8 @@ void LoadSystemFonts() {
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount, RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
&maxValueNameSize, &maxValueDataSize, NULL, NULL); &maxValueNameSize, &maxValueDataSize, NULL, NULL);
char *valueName = new char[maxValueNameSize + 1]; // +1 for null terminator char* valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
BYTE *valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator BYTE* valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
// Enumerate all font entries // Enumerate all font entries
for (DWORD i = 0; i < valueCount; i++) { for (DWORD i = 0; i < valueCount; i++) {
@@ -159,7 +176,7 @@ void LoadSystemFonts() {
valueData, &valueDataSize) == ERROR_SUCCESS) { valueData, &valueDataSize) == ERROR_SUCCESS) {
if (valueType == REG_SZ) { if (valueType == REG_SZ) {
// Add the font file path to the vector // Add the font file path to the vector
std::string fontPath(reinterpret_cast<char *>(valueData), std::string fontPath(reinterpret_cast<char*>(valueData),
valueDataSize); valueDataSize);
fontPaths.push_back(fontPath); fontPaths.push_back(fontPath);
@@ -173,7 +190,7 @@ void LoadSystemFonts() {
RegCloseKey(hKey); RegCloseKey(hKey);
} }
ImGuiIO &io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
// List of common font face names // List of common font face names
static const std::unordered_set<std::string> commonFontFaceNames = { static const std::unordered_set<std::string> commonFontFaceNames = {
@@ -192,7 +209,7 @@ void LoadSystemFonts() {
"Tahoma", "Tahoma",
"Lucida Console"}; "Lucida Console"};
for (auto &fontPath : fontPaths) { for (auto& fontPath : fontPaths) {
// Check if the font path has a "C:\" prefix // Check if the font path has a "C:\" prefix
if (fontPath.substr(0, 2) != "C:") { if (fontPath.substr(0, 2) != "C:") {
// Add "C:\Windows\Fonts\" prefix to the font path // Add "C:\Windows\Fonts\" prefix to the font path
@@ -231,11 +248,9 @@ void LoadSystemFonts() {
void LoadSystemFonts() { void LoadSystemFonts() {
// Load Linux System Fonts into ImGui // Load Linux System Fonts into ImGui
// ...
} }
#endif #endif
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze

View File

@@ -1,17 +1,34 @@
#ifndef YAZE_APP_CORE_PLATFORM_FONTLOADER_H #ifndef YAZE_APP_CORE_PLATFORM_FONTLOADER_H
#define YAZE_APP_CORE_PLATFORM_FONTLOADER_H #define YAZE_APP_CORE_PLATFORM_FONTLOADER_H
#include <vector>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
void LoadSystemFonts(); struct FontConfig {
const char* font_path;
float font_size;
ImFontConfig im_font_config;
ImFontConfig jp_conf_config;
};
struct FontState {
std::vector<FontConfig> fonts;
};
static FontState font_registry;
absl::Status LoadPackageFonts(); absl::Status LoadPackageFonts();
absl::Status ReloadPackageFont(const FontConfig& config);
void LoadSystemFonts();
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_PLATFORM_FONTLOADER_H #endif // YAZE_APP_CORE_PLATFORM_FONTLOADER_H

View File

@@ -8,13 +8,13 @@
#if TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1 #if TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1
/* iOS */ /* iOS */
void yaze::app::core::LoadSystemFonts() {} void yaze::core::LoadSystemFonts() {}
#elif TARGET_OS_MAC == 1 #elif TARGET_OS_MAC == 1
/* macOS */ /* macOS */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
void yaze::app::core::LoadSystemFonts() { void yaze::core::LoadSystemFonts() {
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ]; NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
for (NSString *fontName in fontNames) { for (NSString *fontName in fontNames) {

View File

@@ -4,37 +4,35 @@
#include <SDL.h> #include <SDL.h>
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
/** /**
* @brief Deleter for SDL_Window and SDL_Renderer. * @brief Deleter for SDL_Window and SDL_Renderer.
*/ */
struct SDL_Deleter { struct SDL_Deleter {
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); } void operator()(SDL_Window* p) const { SDL_DestroyWindow(p); }
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); } void operator()(SDL_Renderer* p) const { SDL_DestroyRenderer(p); }
}; };
/** // Custom deleter for SDL_Surface
* @brief Deleter for SDL_Texture. struct SDL_Surface_Deleter {
*/ void operator()(SDL_Surface* p) const {
struct SDL_Texture_Deleter { if (p) {
void operator()(SDL_Texture *p) const { SDL_FreeSurface(p);
SDL_DestroyTexture(p); }
} }
}; };
/** // Custom deleter for SDL_Texture
* @brief Deleter for SDL_Surface. struct SDL_Texture_Deleter {
*/ void operator()(SDL_Texture* p) const {
struct SDL_Surface_Deleter { if (p) {
void operator()(SDL_Surface *p) const { SDL_DestroyTexture(p);
SDL_FreeSurface(p); }
} }
}; };
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_UTILS_SDL_DELETER_H_ #endif // YAZE_APP_CORE_UTILS_SDL_DELETER_H_

View File

@@ -10,7 +10,7 @@
@end @end
#else #else
@interface AppViewController : UIViewController <MTKViewDelegate> @interface AppViewController : UIViewController <MTKViewDelegate>
@property(nonatomic) yaze::app::core::Controller *controller; @property(nonatomic) yaze::core::Controller *controller;
@property(nonatomic) UIHoverGestureRecognizer *hoverGestureRecognizer; @property(nonatomic) UIHoverGestureRecognizer *hoverGestureRecognizer;
@property(nonatomic) UIPinchGestureRecognizer *pinchRecognizer; @property(nonatomic) UIPinchGestureRecognizer *pinchRecognizer;
@property(nonatomic) UISwipeGestureRecognizer *swipeRecognizer; @property(nonatomic) UISwipeGestureRecognizer *swipeRecognizer;

View File

@@ -1,189 +1,831 @@
#include "project.h" #include "project.h"
#include <chrono>
#include <filesystem>
#include <fstream> #include <fstream>
#include <string> #include <iomanip>
#include <sstream>
#include "app/core/constants.h" #include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h" #include "yaze_config.h"
namespace yaze { namespace yaze {
namespace app { namespace core {
absl::Status Project::Open(const std::string& project_path) { namespace {
filepath = project_path; // Helper functions for parsing key-value pairs
name = project_path.substr(project_path.find_last_of("/") + 1); std::pair<std::string, std::string> ParseKeyValue(const std::string& line) {
size_t eq_pos = line.find('=');
if (eq_pos == std::string::npos) return {"", ""};
std::ifstream in(project_path); std::string key = line.substr(0, eq_pos);
std::string value = line.substr(eq_pos + 1);
if (!in.good()) { // Trim whitespace
return absl::InternalError("Could not open project file."); key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
return {key, value};
} }
std::string line; bool ParseBool(const std::string& value) {
std::getline(in, name); return value == "true" || value == "1" || value == "yes";
std::getline(in, filepath); }
std::getline(in, rom_filename_);
std::getline(in, code_folder_);
std::getline(in, labels_filename_);
std::getline(in, keybindings_file);
while (std::getline(in, line)) { float ParseFloat(const std::string& value) {
if (line == kEndOfProjectFile) { try {
break; return std::stof(value);
} catch (...) {
return 0.0f;
} }
} }
in.close(); std::vector<std::string> ParseStringList(const std::string& value) {
std::vector<std::string> result;
if (value.empty()) return result;
return absl::OkStatus(); std::vector<std::string> parts = absl::StrSplit(value, ',');
for (const auto& part : parts) {
std::string trimmed = std::string(part);
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
trimmed.erase(trimmed.find_last_not_of(" \t") + 1);
if (!trimmed.empty()) {
result.push_back(trimmed);
}
}
return result;
}
} }
absl::Status Project::Save() { // YazeProject Implementation
RETURN_IF_ERROR(CheckForEmptyFields()); absl::Status YazeProject::Create(const std::string& project_name, const std::string& base_path) {
name = project_name;
filepath = base_path + "/" + project_name + ".yaze";
std::ofstream out(filepath + "/" + name + ".yaze"); // Initialize metadata
if (!out.good()) { auto now = std::chrono::system_clock::now();
return absl::InternalError("Could not open project file."); auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
metadata.created_date = ss.str();
metadata.last_modified = ss.str();
metadata.yaze_version = "0.3.0"; // TODO: Get from version header
metadata.version = "2.0";
metadata.created_by = "YAZE";
InitializeDefaults();
// Create project directory structure
std::filesystem::path project_dir(base_path + "/" + project_name);
std::filesystem::create_directories(project_dir);
std::filesystem::create_directories(project_dir / "code");
std::filesystem::create_directories(project_dir / "assets");
std::filesystem::create_directories(project_dir / "patches");
std::filesystem::create_directories(project_dir / "backups");
std::filesystem::create_directories(project_dir / "output");
// Set folder paths
code_folder = (project_dir / "code").string();
assets_folder = (project_dir / "assets").string();
patches_folder = (project_dir / "patches").string();
rom_backup_folder = (project_dir / "backups").string();
output_folder = (project_dir / "output").string();
labels_filename = (project_dir / "labels.txt").string();
symbols_filename = (project_dir / "symbols.txt").string();
return Save();
}
absl::Status YazeProject::Open(const std::string& project_path) {
filepath = project_path;
// Determine format and load accordingly
if (project_path.ends_with(".yaze")) {
format = ProjectFormat::kYazeNative;
return LoadFromYazeFormat(project_path);
} else if (project_path.ends_with(".zsproj")) {
format = ProjectFormat::kZScreamCompat;
return ImportFromZScreamFormat(project_path);
} }
out << name << std::endl; return absl::InvalidArgumentError("Unsupported project file format");
out << filepath << std::endl; }
out << rom_filename_ << std::endl;
out << code_folder_ << std::endl;
out << labels_filename_ << std::endl;
out << keybindings_file << std::endl;
out << kEndOfProjectFile << std::endl; absl::Status YazeProject::Save() {
return SaveToYazeFormat();
}
out.close(); absl::Status YazeProject::SaveAs(const std::string& new_path) {
std::string old_filepath = filepath;
filepath = new_path;
auto status = Save();
if (!status.ok()) {
filepath = old_filepath; // Restore on failure
}
return status;
}
absl::Status YazeProject::LoadFromYazeFormat(const std::string& project_path) {
std::ifstream file(project_path);
if (!file.is_open()) {
return absl::InvalidArgumentError(absl::StrFormat("Cannot open project file: %s", project_path));
}
std::string line;
std::string current_section = "";
while (std::getline(file, line)) {
// Skip empty lines and comments
if (line.empty() || line[0] == '#') continue;
// Check for section headers [section_name]
if (line[0] == '[' && line.back() == ']') {
current_section = line.substr(1, line.length() - 2);
continue;
}
auto [key, value] = ParseKeyValue(line);
if (key.empty()) continue;
// Parse based on current section
if (current_section == "project") {
if (key == "name") name = value;
else if (key == "description") metadata.description = value;
else if (key == "author") metadata.author = value;
else if (key == "license") metadata.license = value;
else if (key == "version") metadata.version = value;
else if (key == "created_date") metadata.created_date = value;
else if (key == "last_modified") metadata.last_modified = value;
else if (key == "yaze_version") metadata.yaze_version = value;
else if (key == "tags") metadata.tags = ParseStringList(value);
}
else if (current_section == "files") {
if (key == "rom_filename") rom_filename = value;
else if (key == "rom_backup_folder") rom_backup_folder = value;
else if (key == "code_folder") code_folder = value;
else if (key == "assets_folder") assets_folder = value;
else if (key == "patches_folder") patches_folder = value;
else if (key == "labels_filename") labels_filename = value;
else if (key == "symbols_filename") symbols_filename = value;
else if (key == "output_folder") output_folder = value;
else if (key == "additional_roms") additional_roms = ParseStringList(value);
}
else if (current_section == "feature_flags") {
if (key == "load_custom_overworld") feature_flags.overworld.kLoadCustomOverworld = ParseBool(value);
else if (key == "apply_zs_custom_overworld_asm") feature_flags.overworld.kApplyZSCustomOverworldASM = ParseBool(value);
else if (key == "save_dungeon_maps") feature_flags.kSaveDungeonMaps = ParseBool(value);
else if (key == "save_graphics_sheet") feature_flags.kSaveGraphicsSheet = ParseBool(value);
else if (key == "log_instructions") feature_flags.kLogInstructions = ParseBool(value);
}
else if (current_section == "workspace") {
if (key == "font_global_scale") workspace_settings.font_global_scale = ParseFloat(value);
else if (key == "dark_mode") workspace_settings.dark_mode = ParseBool(value);
else if (key == "ui_theme") workspace_settings.ui_theme = value;
else if (key == "autosave_enabled") workspace_settings.autosave_enabled = ParseBool(value);
else if (key == "autosave_interval_secs") workspace_settings.autosave_interval_secs = ParseFloat(value);
else if (key == "backup_on_save") workspace_settings.backup_on_save = ParseBool(value);
else if (key == "show_grid") workspace_settings.show_grid = ParseBool(value);
else if (key == "show_collision") workspace_settings.show_collision = ParseBool(value);
else if (key == "last_layout_preset") workspace_settings.last_layout_preset = value;
else if (key == "saved_layouts") workspace_settings.saved_layouts = ParseStringList(value);
else if (key == "recent_files") workspace_settings.recent_files = ParseStringList(value);
}
else if (current_section == "build") {
if (key == "build_script") build_script = value;
else if (key == "output_folder") output_folder = value;
else if (key == "git_repository") git_repository = value;
else if (key == "track_changes") track_changes = ParseBool(value);
else if (key == "build_configurations") build_configurations = ParseStringList(value);
}
else if (current_section.starts_with("labels_")) {
// Resource labels: [labels_type_name] followed by key=value pairs
std::string label_type = current_section.substr(7); // Remove "labels_" prefix
resource_labels[label_type][key] = value;
}
else if (current_section == "keybindings") {
workspace_settings.custom_keybindings[key] = value;
}
else if (current_section == "editor_visibility") {
workspace_settings.editor_visibility[key] = ParseBool(value);
}
else if (current_section == "zscream_compatibility") {
if (key == "original_project_file") zscream_project_file = value;
else zscream_mappings[key] = value;
}
}
file.close();
return absl::OkStatus();
}
absl::Status YazeProject::SaveToYazeFormat() {
// Update last modified timestamp
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
metadata.last_modified = ss.str();
std::ofstream file(filepath);
if (!file.is_open()) {
return absl::InvalidArgumentError(absl::StrFormat("Cannot create project file: %s", filepath));
}
// Write header comment
file << "# YAZE Project File\n";
file << "# Format Version: 2.0\n";
file << "# Generated by YAZE " << metadata.yaze_version << "\n";
file << "# Last Modified: " << metadata.last_modified << "\n\n";
// Project section
file << "[project]\n";
file << "name=" << name << "\n";
file << "description=" << metadata.description << "\n";
file << "author=" << metadata.author << "\n";
file << "license=" << metadata.license << "\n";
file << "version=" << metadata.version << "\n";
file << "created_date=" << metadata.created_date << "\n";
file << "last_modified=" << metadata.last_modified << "\n";
file << "yaze_version=" << metadata.yaze_version << "\n";
file << "tags=" << absl::StrJoin(metadata.tags, ",") << "\n\n";
// Files section
file << "[files]\n";
file << "rom_filename=" << GetRelativePath(rom_filename) << "\n";
file << "rom_backup_folder=" << GetRelativePath(rom_backup_folder) << "\n";
file << "code_folder=" << GetRelativePath(code_folder) << "\n";
file << "assets_folder=" << GetRelativePath(assets_folder) << "\n";
file << "patches_folder=" << GetRelativePath(patches_folder) << "\n";
file << "labels_filename=" << GetRelativePath(labels_filename) << "\n";
file << "symbols_filename=" << GetRelativePath(symbols_filename) << "\n";
file << "output_folder=" << GetRelativePath(output_folder) << "\n";
file << "additional_roms=" << absl::StrJoin(additional_roms, ",") << "\n\n";
// Feature flags section
file << "[feature_flags]\n";
file << "load_custom_overworld=" << (feature_flags.overworld.kLoadCustomOverworld ? "true" : "false") << "\n";
file << "apply_zs_custom_overworld_asm=" << (feature_flags.overworld.kApplyZSCustomOverworldASM ? "true" : "false") << "\n";
file << "save_dungeon_maps=" << (feature_flags.kSaveDungeonMaps ? "true" : "false") << "\n";
file << "save_graphics_sheet=" << (feature_flags.kSaveGraphicsSheet ? "true" : "false") << "\n";
file << "log_instructions=" << (feature_flags.kLogInstructions ? "true" : "false") << "\n\n";
// Workspace settings section
file << "[workspace]\n";
file << "font_global_scale=" << workspace_settings.font_global_scale << "\n";
file << "dark_mode=" << (workspace_settings.dark_mode ? "true" : "false") << "\n";
file << "ui_theme=" << workspace_settings.ui_theme << "\n";
file << "autosave_enabled=" << (workspace_settings.autosave_enabled ? "true" : "false") << "\n";
file << "autosave_interval_secs=" << workspace_settings.autosave_interval_secs << "\n";
file << "backup_on_save=" << (workspace_settings.backup_on_save ? "true" : "false") << "\n";
file << "show_grid=" << (workspace_settings.show_grid ? "true" : "false") << "\n";
file << "show_collision=" << (workspace_settings.show_collision ? "true" : "false") << "\n";
file << "last_layout_preset=" << workspace_settings.last_layout_preset << "\n";
file << "saved_layouts=" << absl::StrJoin(workspace_settings.saved_layouts, ",") << "\n";
file << "recent_files=" << absl::StrJoin(workspace_settings.recent_files, ",") << "\n\n";
// Custom keybindings section
if (!workspace_settings.custom_keybindings.empty()) {
file << "[keybindings]\n";
for (const auto& [key, value] : workspace_settings.custom_keybindings) {
file << key << "=" << value << "\n";
}
file << "\n";
}
// Editor visibility section
if (!workspace_settings.editor_visibility.empty()) {
file << "[editor_visibility]\n";
for (const auto& [key, value] : workspace_settings.editor_visibility) {
file << key << "=" << (value ? "true" : "false") << "\n";
}
file << "\n";
}
// Resource labels sections
for (const auto& [type, labels] : resource_labels) {
if (!labels.empty()) {
file << "[labels_" << type << "]\n";
for (const auto& [key, value] : labels) {
file << key << "=" << value << "\n";
}
file << "\n";
}
}
// Build settings section
file << "[build]\n";
file << "build_script=" << build_script << "\n";
file << "output_folder=" << GetRelativePath(output_folder) << "\n";
file << "git_repository=" << git_repository << "\n";
file << "track_changes=" << (track_changes ? "true" : "false") << "\n";
file << "build_configurations=" << absl::StrJoin(build_configurations, ",") << "\n\n";
// ZScream compatibility section
if (!zscream_project_file.empty()) {
file << "[zscream_compatibility]\n";
file << "original_project_file=" << zscream_project_file << "\n";
for (const auto& [key, value] : zscream_mappings) {
file << key << "=" << value << "\n";
}
file << "\n";
}
file << "# End of YAZE Project File\n";
file.close();
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status YazeProject::ImportZScreamProject(const std::string& zscream_project_path) {
// Basic ZScream project import (to be expanded based on ZScream format)
zscream_project_file = zscream_project_path;
format = ProjectFormat::kZScreamCompat;
// Extract project name from path
std::filesystem::path zs_path(zscream_project_path);
name = zs_path.stem().string() + "_imported";
// Set up basic mapping for common fields
zscream_mappings["rom_file"] = "rom_filename";
zscream_mappings["source_code"] = "code_folder";
zscream_mappings["project_name"] = "name";
InitializeDefaults();
// TODO: Implement actual ZScream format parsing when format is known
// For now, just create a project structure that can be manually configured
return absl::OkStatus();
}
absl::Status YazeProject::ExportForZScream(const std::string& target_path) {
// Create a simplified project file that ZScream might understand
std::ofstream file(target_path);
if (!file.is_open()) {
return absl::InvalidArgumentError(absl::StrFormat("Cannot create ZScream project file: %s", target_path));
}
// Write in a simple format that ZScream might understand
file << "# ZScream Compatible Project File\n";
file << "# Exported from YAZE " << metadata.yaze_version << "\n\n";
file << "name=" << name << "\n";
file << "rom_file=" << rom_filename << "\n";
file << "source_code=" << code_folder << "\n";
file << "description=" << metadata.description << "\n";
file << "author=" << metadata.author << "\n";
file << "created_with=YAZE " << metadata.yaze_version << "\n";
file.close();
return absl::OkStatus();
}
absl::Status YazeProject::LoadAllSettings() {
// Consolidated loading of all settings from project file
// This replaces scattered config loading throughout the application
return LoadFromYazeFormat(filepath);
}
absl::Status YazeProject::SaveAllSettings() {
// Consolidated saving of all settings to project file
return SaveToYazeFormat();
}
absl::Status YazeProject::ResetToDefaults() {
InitializeDefaults();
return Save();
}
absl::Status YazeProject::Validate() const {
std::vector<std::string> errors;
if (name.empty()) errors.push_back("Project name is required");
if (filepath.empty()) errors.push_back("Project file path is required");
if (rom_filename.empty()) errors.push_back("ROM file is required");
// Check if files exist
if (!rom_filename.empty() && !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
errors.push_back("ROM file does not exist: " + rom_filename);
}
if (!code_folder.empty() && !std::filesystem::exists(GetAbsolutePath(code_folder))) {
errors.push_back("Code folder does not exist: " + code_folder);
}
if (!labels_filename.empty() && !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
errors.push_back("Labels file does not exist: " + labels_filename);
}
if (!errors.empty()) {
return absl::InvalidArgumentError(absl::StrJoin(errors, "; "));
}
return absl::OkStatus();
}
std::vector<std::string> YazeProject::GetMissingFiles() const {
std::vector<std::string> missing;
if (!rom_filename.empty() && !std::filesystem::exists(GetAbsolutePath(rom_filename))) {
missing.push_back(rom_filename);
}
if (!labels_filename.empty() && !std::filesystem::exists(GetAbsolutePath(labels_filename))) {
missing.push_back(labels_filename);
}
if (!symbols_filename.empty() && !std::filesystem::exists(GetAbsolutePath(symbols_filename))) {
missing.push_back(symbols_filename);
}
return missing;
}
absl::Status YazeProject::RepairProject() {
// Create missing directories
std::vector<std::string> folders = {code_folder, assets_folder, patches_folder,
rom_backup_folder, output_folder};
for (const auto& folder : folders) {
if (!folder.empty()) {
std::filesystem::path abs_path = GetAbsolutePath(folder);
if (!std::filesystem::exists(abs_path)) {
std::filesystem::create_directories(abs_path);
}
}
}
// Create missing files with defaults
if (!labels_filename.empty()) {
std::filesystem::path abs_labels = GetAbsolutePath(labels_filename);
if (!std::filesystem::exists(abs_labels)) {
std::ofstream labels_file(abs_labels);
labels_file << "# YAZE Resource Labels\n";
labels_file << "# Format: [type] key=value\n\n";
labels_file.close();
}
}
return absl::OkStatus();
}
std::string YazeProject::GetDisplayName() const {
if (!metadata.description.empty()) {
return metadata.description;
}
return name.empty() ? "Untitled Project" : name;
}
std::string YazeProject::GetRelativePath(const std::string& absolute_path) const {
if (absolute_path.empty() || filepath.empty()) return absolute_path;
std::filesystem::path project_dir = std::filesystem::path(filepath).parent_path();
std::filesystem::path abs_path(absolute_path);
try {
std::filesystem::path relative = std::filesystem::relative(abs_path, project_dir);
return relative.string();
} catch (...) {
return absolute_path; // Return absolute path if relative conversion fails
}
}
std::string YazeProject::GetAbsolutePath(const std::string& relative_path) const {
if (relative_path.empty() || filepath.empty()) return relative_path;
std::filesystem::path project_dir = std::filesystem::path(filepath).parent_path();
std::filesystem::path abs_path = project_dir / relative_path;
return abs_path.string();
}
bool YazeProject::IsEmpty() const {
return name.empty() && rom_filename.empty() && code_folder.empty();
}
absl::Status YazeProject::ImportFromZScreamFormat(const std::string& project_path) {
// TODO: Implement ZScream format parsing when format specification is available
// For now, create a basic project that can be manually configured
std::filesystem::path zs_path(project_path);
name = zs_path.stem().string() + "_imported";
zscream_project_file = project_path;
InitializeDefaults();
return absl::OkStatus();
}
void YazeProject::InitializeDefaults() {
// Initialize default feature flags
feature_flags.overworld.kLoadCustomOverworld = false;
feature_flags.overworld.kApplyZSCustomOverworldASM = false;
feature_flags.kSaveDungeonMaps = true;
feature_flags.kSaveGraphicsSheet = true;
feature_flags.kLogInstructions = false;
// Initialize default workspace settings
workspace_settings.font_global_scale = 1.0f;
workspace_settings.dark_mode = true;
workspace_settings.ui_theme = "default";
workspace_settings.autosave_enabled = true;
workspace_settings.autosave_interval_secs = 300.0f; // 5 minutes
workspace_settings.backup_on_save = true;
workspace_settings.show_grid = true;
workspace_settings.show_collision = false;
// Initialize default build configurations
build_configurations = {"Debug", "Release", "Distribution"};
track_changes = true;
}
std::string YazeProject::GenerateProjectId() const {
auto now = std::chrono::system_clock::now().time_since_epoch();
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
return absl::StrFormat("yaze_project_%lld", timestamp);
}
// ProjectManager Implementation
std::vector<ProjectManager::ProjectTemplate> ProjectManager::GetProjectTemplates() {
return {
{
"Basic ROM Hack",
"Simple project for modifying an existing ROM with basic tools",
ICON_MD_VIDEOGAME_ASSET,
{} // Basic defaults
},
{
"Full Overworld Mod",
"Complete overworld modification with custom graphics and maps",
ICON_MD_MAP,
{} // Overworld-focused settings
},
{
"Dungeon Designer",
"Focused on dungeon creation and modification",
ICON_MD_DOMAIN,
{} // Dungeon-focused settings
},
{
"Graphics Pack",
"Project focused on graphics, sprites, and visual modifications",
ICON_MD_PALETTE,
{} // Graphics-focused settings
},
{
"Complete Overhaul",
"Full-scale ROM hack with all features enabled",
ICON_MD_BUILD,
{} // All features enabled
}
};
}
absl::StatusOr<YazeProject> ProjectManager::CreateFromTemplate(
const std::string& template_name,
const std::string& project_name,
const std::string& base_path) {
YazeProject project;
auto status = project.Create(project_name, base_path);
if (!status.ok()) {
return status;
}
// Customize based on template
if (template_name == "Full Overworld Mod") {
project.feature_flags.overworld.kLoadCustomOverworld = true;
project.feature_flags.overworld.kApplyZSCustomOverworldASM = true;
project.metadata.description = "Overworld modification project";
project.metadata.tags = {"overworld", "maps", "graphics"};
} else if (template_name == "Dungeon Designer") {
project.feature_flags.kSaveDungeonMaps = true;
project.workspace_settings.show_grid = true;
project.metadata.description = "Dungeon design and modification project";
project.metadata.tags = {"dungeons", "rooms", "design"};
} else if (template_name == "Graphics Pack") {
project.feature_flags.kSaveGraphicsSheet = true;
project.workspace_settings.show_grid = true;
project.metadata.description = "Graphics and sprite modification project";
project.metadata.tags = {"graphics", "sprites", "palettes"};
} else if (template_name == "Complete Overhaul") {
project.feature_flags.overworld.kLoadCustomOverworld = true;
project.feature_flags.overworld.kApplyZSCustomOverworldASM = true;
project.feature_flags.kSaveDungeonMaps = true;
project.feature_flags.kSaveGraphicsSheet = true;
project.metadata.description = "Complete ROM overhaul project";
project.metadata.tags = {"complete", "overhaul", "full-mod"};
}
status = project.Save();
if (!status.ok()) {
return status;
}
return project;
}
std::vector<std::string> ProjectManager::FindProjectsInDirectory(const std::string& directory) {
std::vector<std::string> projects;
try {
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (entry.is_regular_file()) {
std::string filename = entry.path().filename().string();
if (filename.ends_with(".yaze") || filename.ends_with(".zsproj")) {
projects.push_back(entry.path().string());
}
}
}
} catch (const std::filesystem::filesystem_error& e) {
// Directory doesn't exist or can't be accessed
}
return projects;
}
absl::Status ProjectManager::BackupProject(const YazeProject& project) {
if (project.filepath.empty()) {
return absl::InvalidArgumentError("Project has no file path");
}
std::filesystem::path project_path(project.filepath);
std::filesystem::path backup_dir = project_path.parent_path() / "backups";
std::filesystem::create_directories(backup_dir);
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
std::string backup_filename = project.name + "_backup_" + ss.str() + ".yaze";
std::filesystem::path backup_path = backup_dir / backup_filename;
try {
std::filesystem::copy_file(project.filepath, backup_path);
} catch (const std::filesystem::filesystem_error& e) {
return absl::InternalError(absl::StrFormat("Failed to backup project: %s", e.what()));
}
return absl::OkStatus();
}
absl::Status ProjectManager::ValidateProjectStructure(const YazeProject& project) {
return project.Validate();
}
std::vector<std::string> ProjectManager::GetRecommendedFixesForProject(const YazeProject& project) {
std::vector<std::string> recommendations;
if (project.rom_filename.empty()) {
recommendations.push_back("Add a ROM file to begin editing");
}
if (project.code_folder.empty()) {
recommendations.push_back("Set up a code folder for assembly patches");
}
if (project.labels_filename.empty()) {
recommendations.push_back("Create a labels file for better organization");
}
if (project.metadata.description.empty()) {
recommendations.push_back("Add a project description for documentation");
}
if (project.git_repository.empty() && project.track_changes) {
recommendations.push_back("Consider setting up version control for your project");
}
auto missing_files = project.GetMissingFiles();
if (!missing_files.empty()) {
recommendations.push_back("Some project files are missing - use Project > Repair to fix");
}
return recommendations;
}
// Compatibility implementations for ResourceLabelManager and related classes
bool ResourceLabelManager::LoadLabels(const std::string& filename) { bool ResourceLabelManager::LoadLabels(const std::string& filename) {
filename_ = filename;
std::ifstream file(filename); std::ifstream file(filename);
if (!file.is_open()) { if (!file.is_open()) {
// Create the file if it does not exist labels_loaded_ = false;
std::ofstream create_file(filename); return false;
if (!create_file.is_open()) {
return false;
}
create_file.close();
file.open(filename);
if (!file.is_open()) {
return false;
}
} }
filename_ = filename;
labels_.clear();
std::string line; std::string line;
std::string current_type = "";
while (std::getline(file, line)) { while (std::getline(file, line)) {
std::istringstream iss(line); if (line.empty() || line[0] == '#') continue;
std::string type, key, value;
if (std::getline(iss, type, ',') && std::getline(iss, key, ',') && // Check for type headers [type_name]
std::getline(iss, value)) { if (line[0] == '[' && line.back() == ']') {
labels_[type][key] = value; current_type = line.substr(1, line.length() - 2);
continue;
}
// Parse key=value pairs
size_t eq_pos = line.find('=');
if (eq_pos != std::string::npos && !current_type.empty()) {
std::string key = line.substr(0, eq_pos);
std::string value = line.substr(eq_pos + 1);
labels_[current_type][key] = value;
} }
} }
file.close();
labels_loaded_ = true; labels_loaded_ = true;
return true; return true;
} }
bool ResourceLabelManager::SaveLabels() { bool ResourceLabelManager::SaveLabels() {
if (!labels_loaded_) { if (filename_.empty()) return false;
return false;
}
std::ofstream file(filename_); std::ofstream file(filename_);
if (!file.is_open()) { if (!file.is_open()) return false;
return false;
} file << "# YAZE Resource Labels\n";
for (const auto& type_pair : labels_) { file << "# Format: [type] followed by key=value pairs\n\n";
for (const auto& label_pair : type_pair.second) {
file << type_pair.first << "," << label_pair.first << "," for (const auto& [type, type_labels] : labels_) {
<< label_pair.second << std::endl; if (!type_labels.empty()) {
file << "[" << type << "]\n";
for (const auto& [key, value] : type_labels) {
file << key << "=" << value << "\n";
}
file << "\n";
} }
} }
file.close(); file.close();
return true; return true;
} }
void ResourceLabelManager::DisplayLabels(bool* p_open) { void ResourceLabelManager::DisplayLabels(bool* p_open) {
if (!labels_loaded_) { if (!p_open || !*p_open) return;
ImGui::Text("No labels loaded.");
return;
}
// Basic implementation - can be enhanced later
if (ImGui::Begin("Resource Labels", p_open)) { if (ImGui::Begin("Resource Labels", p_open)) {
for (const auto& type_pair : labels_) { ImGui::Text("Resource Labels Manager");
if (ImGui::TreeNode(type_pair.first.c_str())) { ImGui::Text("Labels loaded: %s", labels_loaded_ ? "Yes" : "No");
for (const auto& label_pair : type_pair.second) { ImGui::Text("Total types: %zu", labels_.size());
std::string label_id = type_pair.first + "_" + label_pair.first;
ImGui::Text("%s: %s", label_pair.first.c_str(), for (const auto& [type, type_labels] : labels_) {
label_pair.second.c_str()); if (ImGui::TreeNode(type.c_str())) {
ImGui::Text("Labels: %zu", type_labels.size());
for (const auto& [key, value] : type_labels) {
ImGui::Text("%s = %s", key.c_str(), value.c_str());
} }
ImGui::TreePop(); ImGui::TreePop();
} }
} }
if (ImGui::Button("Update Labels")) {
if (SaveLabels()) {
ImGui::Text("Labels updated successfully!");
} else {
ImGui::Text("Failed to update labels.");
}
}
} }
ImGui::End(); ImGui::End();
} }
void ResourceLabelManager::EditLabel(const std::string& type, void ResourceLabelManager::EditLabel(const std::string& type, const std::string& key,
const std::string& key, const std::string& newValue) {
const std::string& newValue) {
labels_[type][key] = newValue; labels_[type][key] = newValue;
} }
void ResourceLabelManager::SelectableLabelWithNameEdit( void ResourceLabelManager::SelectableLabelWithNameEdit(bool selected, const std::string& type,
bool selected, const std::string& type, const std::string& key, const std::string& key,
const std::string& defaultValue) { const std::string& defaultValue) {
std::string label = CreateOrGetLabel(type, key, defaultValue); // Basic implementation
ImGui::Selectable(label.c_str(), selected, if (ImGui::Selectable(absl::StrFormat("%s: %s", key.c_str(), GetLabel(type, key).c_str()).c_str(), selected)) {
ImGuiSelectableFlags_AllowDoubleClick); // Handle selection
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())) {
std::string* new_label = &labels_[type][key];
if (ImGui::InputText("##Label", new_label,
ImGuiInputTextFlags_EnterReturnsTrue)) {
labels_[type][key] = *new_label;
}
if (ImGui::Button(ICON_MD_CLOSE)) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
} }
} }
std::string ResourceLabelManager::GetLabel(const std::string& type, std::string ResourceLabelManager::GetLabel(const std::string& type, const std::string& key) {
const std::string& key) { auto type_it = labels_.find(type);
return labels_[type][key]; if (type_it == labels_.end()) return "";
auto label_it = type_it->second.find(key);
if (label_it == type_it->second.end()) return "";
return label_it->second;
} }
std::string ResourceLabelManager::CreateOrGetLabel( std::string ResourceLabelManager::CreateOrGetLabel(const std::string& type, const std::string& key,
const std::string& type, const std::string& key, const std::string& defaultValue) {
const std::string& defaultValue) { auto existing = GetLabel(type, key);
if (labels_.find(type) == labels_.end()) { if (!existing.empty()) return existing;
labels_[type] = std::unordered_map<std::string, std::string>();
} labels_[type][key] = defaultValue;
if (labels_[type].find(key) == labels_[type].end()) { return defaultValue;
labels_[type][key] = defaultValue;
}
return labels_[type][key];
} }
} // namespace app } // namespace core
} // namespace yaze } // namespace yaze

View File

@@ -2,62 +2,190 @@
#define YAZE_APP_CORE_PROJECT_H #define YAZE_APP_CORE_PROJECT_H
#include <algorithm> #include <algorithm>
#include <chrono>
#include <fstream> #include <fstream>
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
#include <unordered_map>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/common.h" #include "absl/status/statusor.h"
#include "app/core/utils/file_util.h" #include "app/core/features.h"
namespace yaze { namespace yaze {
namespace app { namespace core {
const std::string kRecentFilesFilename = "recent_files.txt";
constexpr char kEndOfProjectFile[] = "EndOfProjectFile";
/** /**
* @struct Project * @enum ProjectFormat
* @brief Represents a project in the application. * @brief Supported project file formats
*
* A project is a collection of files and resources that are used in the
* creation of a Zelda3 hack that can be saved and loaded. This makes it so the
* user can have different rom file names for a single project and keep track of
* backups.
*/ */
struct Project : public core::ExperimentFlags { enum class ProjectFormat {
absl::Status Create(const std::string& project_name) { kYazeNative, // .yaze - YAZE native format
name = project_name; kZScreamCompat // .zsproj - ZScream compatibility format
project_opened_ = true;
return absl::OkStatus();
}
absl::Status CheckForEmptyFields() {
if (name.empty() || filepath.empty() || rom_filename_.empty() ||
code_folder_.empty() || labels_filename_.empty()) {
return absl::InvalidArgumentError(
"Project fields cannot be empty. Please load a rom file, set your "
"code folder, and set your labels file. See HELP for more details.");
}
return absl::OkStatus();
}
absl::Status Open(const std::string &project_path);
absl::Status Save();
bool project_opened_ = false;
std::string name;
std::string flags = "";
std::string filepath;
std::string rom_filename_ = "";
std::string code_folder_ = "";
std::string labels_filename_ = "";
std::string keybindings_file = "";
}; };
// Default types /**
static constexpr absl::string_view kDefaultTypes[] = { * @struct ProjectMetadata
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"}; * @brief Enhanced metadata for project tracking
*/
struct ProjectMetadata {
std::string version = "2.0";
std::string created_by = "YAZE";
std::string created_date;
std::string last_modified;
std::string yaze_version;
std::string description;
std::vector<std::string> tags;
std::string author;
std::string license;
// ZScream compatibility
bool zscream_compatible = false;
std::string zscream_version;
};
/**
* @struct WorkspaceSettings
* @brief Consolidated workspace and UI settings
*/
struct WorkspaceSettings {
// Display settings
float font_global_scale = 1.0f;
bool dark_mode = true;
std::string ui_theme = "default";
// Layout settings
std::string last_layout_preset;
std::vector<std::string> saved_layouts;
std::string window_layout_data; // ImGui .ini data
// Editor preferences
bool autosave_enabled = true;
float autosave_interval_secs = 300.0f; // 5 minutes
bool backup_on_save = true;
bool show_grid = true;
bool show_collision = false;
// Advanced settings
std::map<std::string, std::string> custom_keybindings;
std::vector<std::string> recent_files;
std::map<std::string, bool> editor_visibility;
};
/**
* @struct YazeProject
* @brief Modern project structure with comprehensive settings consolidation
*/
struct YazeProject {
// Basic project info
ProjectMetadata metadata;
std::string name;
std::string filepath;
ProjectFormat format = ProjectFormat::kYazeNative;
// ROM and resources
std::string rom_filename;
std::string rom_backup_folder;
std::vector<std::string> additional_roms; // For multi-ROM projects
// Code and assets
std::string code_folder;
std::string assets_folder;
std::string patches_folder;
std::string labels_filename;
std::string symbols_filename;
// Consolidated settings (previously scattered across multiple files)
FeatureFlags::Flags feature_flags;
WorkspaceSettings workspace_settings;
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> resource_labels;
// Build and deployment
std::string build_script;
std::string output_folder;
std::vector<std::string> build_configurations;
// Version control integration
std::string git_repository;
bool track_changes = true;
// ZScream compatibility (for importing existing projects)
std::string zscream_project_file; // Path to original .zsproj if importing
std::map<std::string, std::string> zscream_mappings; // Field mappings
// Methods
absl::Status Create(const std::string& project_name, const std::string& base_path);
absl::Status Open(const std::string& project_path);
absl::Status Save();
absl::Status SaveAs(const std::string& new_path);
absl::Status ImportZScreamProject(const std::string& zscream_project_path);
absl::Status ExportForZScream(const std::string& target_path);
// Settings management
absl::Status LoadAllSettings();
absl::Status SaveAllSettings();
absl::Status ResetToDefaults();
// Validation and integrity
absl::Status Validate() const;
std::vector<std::string> GetMissingFiles() const;
absl::Status RepairProject();
// Utilities
std::string GetDisplayName() const;
std::string GetRelativePath(const std::string& absolute_path) const;
std::string GetAbsolutePath(const std::string& relative_path) const;
bool IsEmpty() const;
// Project state
bool project_opened() const { return !name.empty() && !filepath.empty(); }
private:
absl::Status LoadFromYazeFormat(const std::string& project_path);
absl::Status SaveToYazeFormat();
absl::Status ImportFromZScreamFormat(const std::string& project_path);
void InitializeDefaults();
std::string GenerateProjectId() const;
};
/**
* @class ProjectManager
* @brief Enhanced project management with templates and validation
*/
class ProjectManager {
public:
// Project templates
struct ProjectTemplate {
std::string name;
std::string description;
std::string icon;
YazeProject template_project;
};
static std::vector<ProjectTemplate> GetProjectTemplates();
static absl::StatusOr<YazeProject> CreateFromTemplate(
const std::string& template_name,
const std::string& project_name,
const std::string& base_path);
// Project discovery and management
static std::vector<std::string> FindProjectsInDirectory(const std::string& directory);
static absl::Status BackupProject(const YazeProject& project);
static absl::Status RestoreProject(const std::string& backup_path);
// Format conversion utilities
static absl::Status ConvertProject(const std::string& source_path,
const std::string& target_path,
ProjectFormat target_format);
// Validation and repair
static absl::Status ValidateProjectStructure(const YazeProject& project);
static std::vector<std::string> GetRecommendedFixesForProject(const YazeProject& project);
};
// Compatibility - ResourceLabelManager (still used by ROM class)
struct ResourceLabelManager { struct ResourceLabelManager {
bool LoadLabels(const std::string& filename); bool LoadLabels(const std::string& filename);
bool SaveLabels(); bool SaveLabels();
@@ -82,6 +210,9 @@ struct ResourceLabelManager {
labels_; labels_;
}; };
// Compatibility - RecentFilesManager
const std::string kRecentFilesFilename = "recent_files.txt";
class RecentFilesManager { class RecentFilesManager {
public: public:
RecentFilesManager() : RecentFilesManager(kRecentFilesFilename) {} RecentFilesManager() : RecentFilesManager(kRecentFilesFilename) {}
@@ -130,7 +261,7 @@ class RecentFilesManager {
std::vector<std::string> recent_files_; std::vector<std::string> recent_files_;
}; };
} // namespace app } // namespace core
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_CORE_PROJECT_H #endif // YAZE_APP_CORE_PROJECT_H

View File

@@ -1,74 +0,0 @@
#include "file_util.h"
#if defined(_WIN32)
#include <windows.h>
#else
#include <dirent.h>
#include <sys/stat.h>
#endif
#include <fstream>
#include <sstream>
namespace yaze {
namespace app {
namespace core {
std::string GetFileExtension(const std::string &filename) {
size_t dot = filename.find_last_of(".");
if (dot == std::string::npos) {
return "";
}
return filename.substr(dot + 1);
}
std::string GetFileName(const std::string &filename) {
size_t slash = filename.find_last_of("/");
if (slash == std::string::npos) {
return filename;
}
return filename.substr(slash + 1);
}
std::string LoadFile(const std::string &filename, Platform platform) {
std::string contents;
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
std::ifstream file(filepath);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
contents = buffer.str();
file.close();
}
return contents;
}
void SaveFile(const std::string &filename, const std::string &contents,
Platform platform) {
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
std::ofstream file(filepath);
if (file.is_open()) {
file << contents;
file.close();
}
}
std::string GetConfigDirectory(Platform platform) {
std::string config_directory = ".yaze";
switch (platform) {
case Platform::kWindows:
config_directory = "~/AppData/Roaming/yaze";
break;
case Platform::kMacOS:
case Platform::kLinux:
config_directory = "~/.config/yaze";
break;
default:
break;
}
return config_directory;
}
} // namespace core
} // namespace app
} // namespace yaze

View File

@@ -1,24 +0,0 @@
#ifndef YAZE_APP_CORE_UTILS_FILE_UTIL_H
#define YAZE_APP_CORE_UTILS_FILE_UTIL_H
#include <string>
namespace yaze {
namespace app {
namespace core {
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
std::string GetFileExtension(const std::string &filename);
std::string GetFileName(const std::string &filename);
std::string LoadFile(const std::string &filename, Platform platform);
std::string GetConfigDirectory(Platform platform);
void SaveFile(const std::string &filename, const std::string &data,
Platform platform);
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_UTILS_FILE_UTIL_H

156
src/app/core/window.cc Normal file
View File

@@ -0,0 +1,156 @@
#include "app/core/window.h"
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/core/platform/font_loader.h"
#include "app/core/platform/sdl_deleter.h"
#include "app/gfx/arena.h"
#include "app/gui/style.h"
#include "app/gui/theme_manager.h"
#include "app/test/test_manager.h"
#include "util/log.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
namespace yaze {
namespace core {
absl::Status CreateWindow(Window& window, int flags) {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) {
return absl::InternalError(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
}
SDL_DisplayMode display_mode;
SDL_GetCurrentDisplayMode(0, &display_mode);
int screen_width = display_mode.w * 0.8;
int screen_height = display_mode.h * 0.8;
window.window_ = std::unique_ptr<SDL_Window, SDL_Deleter>(
SDL_CreateWindow("Yet Another Zelda3 Editor", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height,
flags),
SDL_Deleter());
if (window.window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
RETURN_IF_ERROR(Renderer::Get().CreateRenderer(window.window_.get()));
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
ImGui_ImplSDL2_InitForSDLRenderer(window.window_.get(),
Renderer::Get().renderer());
ImGui_ImplSDLRenderer2_Init(Renderer::Get().renderer());
RETURN_IF_ERROR(LoadPackageFonts());
// Apply original YAZE colors as fallback, then try to load theme system
gui::ColorsYaze();
// Try to initialize theme system (will fallback to ColorsYaze if files fail)
auto& theme_manager = gui::ThemeManager::Get();
auto status = theme_manager.LoadTheme("YAZE Classic");
if (!status.ok()) {
// Theme system failed, stick with original ColorsYaze()
util::logf("Theme system failed, using original ColorsYaze(): %s", status.message().data());
}
const int audio_frequency = 48000;
SDL_AudioSpec want, have;
SDL_memset(&want, 0, sizeof(want));
want.freq = audio_frequency;
want.format = AUDIO_S16;
want.channels = 2;
want.samples = 2048;
want.callback = NULL; // Uses the queue
window.audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (window.audio_device_ == 0) {
throw std::runtime_error(
absl::StrFormat("Failed to open audio: %s\n", SDL_GetError()));
}
window.audio_buffer_ = std::make_shared<int16_t>(audio_frequency / 50 * 4);
SDL_PauseAudioDevice(window.audio_device_, 0);
return absl::OkStatus();
}
absl::Status ShutdownWindow(Window& window) {
SDL_PauseAudioDevice(window.audio_device_, 1);
SDL_CloseAudioDevice(window.audio_device_);
// Stop test engine WHILE ImGui context is still valid
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
test::TestManager::Get().StopUITesting();
#endif
// Shutdown ImGui implementations
ImGui_ImplSDL2_Shutdown();
ImGui_ImplSDLRenderer2_Shutdown();
// Destroy ImGui context
ImGui::DestroyContext();
// NOW destroy test engine context (after ImGui context is destroyed)
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
test::TestManager::Get().DestroyUITestingContext();
#endif
// Shutdown graphics arena BEFORE destroying SDL contexts
gfx::Arena::Get().Shutdown();
SDL_DestroyRenderer(Renderer::Get().renderer());
SDL_DestroyWindow(window.window_.get());
SDL_Quit();
return absl::OkStatus();
}
absl::Status HandleEvents(Window& window) {
SDL_Event event;
ImGuiIO& io = ImGui::GetIO();
SDL_WaitEvent(&event);
ImGui_ImplSDL2_ProcessEvent(&event);
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP: {
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);
break;
}
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
window.active_ = false;
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
io.DisplaySize.x = static_cast<float>(event.window.data1);
io.DisplaySize.y = static_cast<float>(event.window.data2);
break;
}
break;
}
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);
int wheel = 0;
io.MouseWheel = static_cast<float>(wheel);
return absl::OkStatus();
}
} // namespace core
} // namespace yaze

View File

@@ -1,5 +1,5 @@
#ifndef YAZE_APP_CORE_PLATFORM_RENDERER_H #ifndef YAZE_CORE_WINDOW_H_
#define YAZE_APP_CORE_PLATFORM_RENDERER_H #define YAZE_CORE_WINDOW_H_
#include <SDL.h> #include <SDL.h>
@@ -7,13 +7,23 @@
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/core/utils/sdl_deleter.h" #include "app/core/platform/sdl_deleter.h"
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
namespace yaze { namespace yaze {
namespace app {
namespace core { namespace core {
struct Window {
std::shared_ptr<SDL_Window> window_;
SDL_AudioDeviceID audio_device_;
std::shared_ptr<int16_t> audio_buffer_;
bool active_ = true;
};
absl::Status CreateWindow(Window &window, int flags);
absl::Status HandleEvents(Window &window);
absl::Status ShutdownWindow(Window &window);
/** /**
* @class Renderer * @class Renderer
* @brief The Renderer class represents the renderer for the Yaze application. * @brief The Renderer class represents the renderer for the Yaze application.
@@ -24,14 +34,14 @@ namespace core {
*/ */
class Renderer { class Renderer {
public: public:
static Renderer &GetInstance() { static Renderer &Get() {
static Renderer instance; static Renderer instance;
return instance; return instance;
} }
absl::Status CreateRenderer(SDL_Window *window) { absl::Status CreateRenderer(SDL_Window *window) {
renderer_ = std::unique_ptr<SDL_Renderer, SDL_Deleter>(SDL_CreateRenderer( renderer_ = std::unique_ptr<SDL_Renderer, SDL_Deleter>(
window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED)); SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED));
if (renderer_ == nullptr) { if (renderer_ == nullptr) {
return absl::InternalError( return absl::InternalError(
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError())); absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
@@ -43,30 +53,29 @@ class Renderer {
auto renderer() -> SDL_Renderer * { return renderer_.get(); } auto renderer() -> SDL_Renderer * { return renderer_.get(); }
/**
* @brief Used to render a bitmap to the screen.
*/
void RenderBitmap(gfx::Bitmap *bitmap) { void RenderBitmap(gfx::Bitmap *bitmap) {
bitmap->CreateTexture(renderer_.get()); bitmap->CreateTexture(renderer_.get());
} }
/**
* @brief Used to update a bitmap on the screen.
*/
void UpdateBitmap(gfx::Bitmap *bitmap) { void UpdateBitmap(gfx::Bitmap *bitmap) {
bitmap->UpdateTexture(renderer_.get()); bitmap->UpdateTexture(renderer_.get());
} }
absl::Status CreateAndRenderBitmap(int width, int height, int depth, void CreateAndRenderBitmap(int width, int height, int depth,
const std::vector<uint8_t> &data, const std::vector<uint8_t> &data,
gfx::Bitmap &bitmap, gfx::Bitmap &bitmap, gfx::SnesPalette &palette) {
gfx::SnesPalette &palette) {
bitmap.Create(width, height, depth, data); bitmap.Create(width, height, depth, data);
RETURN_IF_ERROR(bitmap.ApplyPalette(palette)); bitmap.SetPalette(palette);
RenderBitmap(&bitmap); RenderBitmap(&bitmap);
return absl::OkStatus();
} }
void Clear() {
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
SDL_RenderClear(renderer_.get());
}
void Present() { SDL_RenderPresent(renderer_.get()); }
private: private:
Renderer() = default; Renderer() = default;
@@ -77,7 +86,5 @@ class Renderer {
}; };
} // namespace core } // namespace core
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_CORE_WINDOW_H_
#endif

View File

@@ -1,14 +1,15 @@
#include "assembly_editor.h" #include "assembly_editor.h"
#include "ImGuiColorTextEdit/TextEditor.h" #include <fstream>
#include "ImGuiFileDialog/ImGuiFileDialog.h" #include <string>
#include <vector>
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h" #include "app/gui/icons.h"
#include "app/gui/modules/text_editor.h"
namespace yaze { namespace yaze::editor {
namespace app {
namespace editor {
using core::FileDialogWrapper; using core::FileDialogWrapper;
@@ -20,22 +21,21 @@ std::vector<std::string> RemoveIgnoredFiles(
std::vector<std::string> filtered_files; std::vector<std::string> filtered_files;
for (const auto& file : files) { for (const auto& file : files) {
// Remove subdirectory files // Remove subdirectory files
if (file.find('/') != std::string::npos) { if (file.contains('/')) {
continue; continue;
} }
// Make sure the file has an extension // Make sure the file has an extension
if (file.find('.') == std::string::npos) { if (!file.contains('.')) {
continue; continue;
} }
if (std::find(ignored_files.begin(), ignored_files.end(), file) == if (std::ranges::find(ignored_files, file) == ignored_files.end()) {
ignored_files.end()) {
filtered_files.push_back(file); filtered_files.push_back(file);
} }
} }
return filtered_files; return filtered_files;
} }
core::FolderItem LoadFolder(const std::string& folder) { FolderItem LoadFolder(const std::string& folder) {
// Check if .gitignore exists in the folder // Check if .gitignore exists in the folder
std::ifstream gitignore(folder + "/.gitignore"); std::ifstream gitignore(folder + "/.gitignore");
std::vector<std::string> ignored_files; std::vector<std::string> ignored_files;
@@ -53,28 +53,27 @@ core::FolderItem LoadFolder(const std::string& folder) {
} }
} }
core::FolderItem current_folder; FolderItem current_folder;
current_folder.name = folder; current_folder.name = folder;
auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name); auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
current_folder.files = RemoveIgnoredFiles(root_files, ignored_files); current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
for (const auto& subfolder : for (const auto& subfolder :
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) { FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
core::FolderItem folder_item; FolderItem folder_item;
folder_item.name = subfolder; folder_item.name = subfolder;
std::string full_folder = current_folder.name + "/" + subfolder; std::string full_folder = current_folder.name + "/" + subfolder;
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder); auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
for (const auto& files : folder_files) { for (const auto& files : folder_files) {
// Remove subdirectory files // Remove subdirectory files
if (files.find('/') != std::string::npos) { if (files.contains('/')) {
continue; continue;
} }
// Make sure the file has an extension // Make sure the file has an extension
if (files.find('.') == std::string::npos) { if (!files.contains('.')) {
continue; continue;
} }
if (std::find(ignored_files.begin(), ignored_files.end(), files) != if (std::ranges::find(ignored_files, files) != ignored_files.end()) {
ignored_files.end()) {
continue; continue;
} }
folder_item.files.push_back(files); folder_item.files.push_back(files);
@@ -82,7 +81,7 @@ core::FolderItem LoadFolder(const std::string& folder) {
for (const auto& subdir : for (const auto& subdir :
FileDialogWrapper::GetSubdirectoriesInFolder(full_folder)) { FileDialogWrapper::GetSubdirectoriesInFolder(full_folder)) {
core::FolderItem subfolder_item; FolderItem subfolder_item;
subfolder_item.name = subdir; subfolder_item.name = subdir;
subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir); subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir);
folder_item.subfolders.push_back(subfolder_item); folder_item.subfolders.push_back(subfolder_item);
@@ -95,6 +94,12 @@ core::FolderItem LoadFolder(const std::string& folder) {
} // namespace } // namespace
void AssemblyEditor::Initialize() {
// Set the language definition
}
absl::Status AssemblyEditor::Load() { return absl::OkStatus(); }
void AssemblyEditor::OpenFolder(const std::string& folder_path) { void AssemblyEditor::OpenFolder(const std::string& folder_path) {
current_folder_ = LoadFolder(folder_path); current_folder_ = LoadFolder(folder_path);
} }
@@ -121,7 +126,6 @@ void AssemblyEditor::Update(bool& is_loaded) {
} }
void AssemblyEditor::InlineUpdate() { void AssemblyEditor::InlineUpdate() {
ChangeActiveFile("assets/asm/template_song.asm");
auto cpos = text_editor_.GetCursorPosition(); auto cpos = text_editor_.GetCursorPosition();
SetEditorText(); SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1, ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
@@ -227,8 +231,8 @@ void AssemblyEditor::DrawFileTabView() {
if (ImGui::BeginTabBar("AssemblyFileTabBar", ImGuiTabBarFlags_None)) { if (ImGui::BeginTabBar("AssemblyFileTabBar", ImGuiTabBarFlags_None)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_None)) { if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_None)) {
if (std::find(active_files_.begin(), active_files_.end(), if (std::ranges::find(active_files_, current_file_id_) !=
current_file_id_) != active_files_.end()) { active_files_.end()) {
// Room is already open // Room is already open
next_tab_id++; next_tab_id++;
} }
@@ -278,21 +282,14 @@ void AssemblyEditor::DrawFileTabView() {
void AssemblyEditor::DrawFileMenu() { void AssemblyEditor::DrawFileMenu() {
if (ImGui::BeginMenu("File")) { if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) { if (ImGui::MenuItem("Open", "Ctrl+O")) {
ImGuiFileDialog::Instance()->OpenDialog( auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", "."); ChangeActiveFile(filename);
} }
if (ImGui::MenuItem("Save", "Ctrl+S")) { if (ImGui::MenuItem("Save", "Ctrl+S")) {
// TODO: Implement this // TODO: Implement this
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
if (ImGuiFileDialog::Instance()->IsOk()) {
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
}
ImGuiFileDialog::Instance()->Close();
}
} }
void AssemblyEditor::DrawEditMenu() { void AssemblyEditor::DrawEditMenu() {
@@ -363,6 +360,4 @@ absl::Status AssemblyEditor::Redo() {
absl::Status AssemblyEditor::Update() { return absl::OkStatus(); } absl::Status AssemblyEditor::Update() { return absl::OkStatus(); }
} // namespace editor } // namespace yaze::editor
} // namespace app
} // namespace yaze

View File

@@ -3,22 +3,27 @@
#include <string> #include <string>
#include "ImGuiColorTextEdit/TextEditor.h"
#include "app/core/common.h"
#include "app/editor/editor.h" #include "app/editor/editor.h"
#include "app/gui/modules/text_editor.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/rom.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
struct FolderItem {
std::string name;
std::vector<FolderItem> subfolders;
std::vector<std::string> files;
};
/** /**
* @class AssemblyEditor * @class AssemblyEditor
* @brief Text editor for modifying assembly code. * @brief Text editor for modifying assembly code.
*/ */
class AssemblyEditor : public Editor { class AssemblyEditor : public Editor {
public: public:
AssemblyEditor() { explicit AssemblyEditor(Rom* rom = nullptr) : rom_(rom) {
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef()); text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
text_editor_.SetPalette(TextEditor::GetDarkPalette()); text_editor_.SetPalette(TextEditor::GetDarkPalette());
text_editor_.SetShowWhitespaces(false); text_editor_.SetShowWhitespaces(false);
@@ -29,6 +34,8 @@ class AssemblyEditor : public Editor {
file_is_loaded_ = false; file_is_loaded_ = false;
} }
void Initialize() override;
absl::Status Load() override;
void Update(bool &is_loaded); void Update(bool &is_loaded);
void InlineUpdate(); void InlineUpdate();
@@ -44,32 +51,35 @@ class AssemblyEditor : public Editor {
absl::Status Update() override; absl::Status Update() override;
absl::Status Save() override { return absl::UnimplementedError("Save"); }
void OpenFolder(const std::string &folder_path); void OpenFolder(const std::string &folder_path);
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
private: private:
void DrawFileMenu(); void DrawFileMenu();
void DrawEditMenu(); void DrawEditMenu();
void SetEditorText(); void SetEditorText();
void DrawCurrentFolder(); void DrawCurrentFolder();
void DrawFileTabView(); void DrawFileTabView();
bool file_is_loaded_ = false; bool file_is_loaded_ = false;
int current_file_id_ = 0;
std::vector<std::string> files_; std::vector<std::string> files_;
std::vector<TextEditor> open_files_; std::vector<TextEditor> open_files_;
ImVector<int> active_files_; ImVector<int> active_files_;
int current_file_id_ = 0;
std::string current_file_; std::string current_file_;
core::FolderItem current_folder_; FolderItem current_folder_;
TextEditor text_editor_; TextEditor text_editor_;
Rom* rom_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif #endif

View File

@@ -1,40 +1,23 @@
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H #ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H #define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#include "absl/status/status.h"
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h"
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/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/input.h"
#include "app/gui/style.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/snes.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h" #include "imgui_memory_editor.h"
#include "util/macro.h"
namespace yaze { namespace yaze {
namespace app {
namespace editor { namespace editor {
using ImGui::SameLine; using ImGui::SameLine;
using ImGui::Text; using ImGui::Text;
struct MemoryEditorWithDiffChecker : public SharedRom { struct MemoryEditorWithDiffChecker {
explicit MemoryEditorWithDiffChecker(Rom* rom = nullptr) : rom_(rom) {}
void Update(bool &show_memory_editor) { void Update(bool &show_memory_editor) {
static MemoryEditor mem_edit; static MemoryEditor mem_edit;
static MemoryEditor comp_edit; static MemoryEditor comp_edit;
@@ -50,7 +33,7 @@ struct MemoryEditorWithDiffChecker : public SharedRom {
static uint64_t convert_address = 0; static uint64_t convert_address = 0;
gui::InputHex("SNES to PC", (int *)&convert_address, 6, 200.f); gui::InputHex("SNES to PC", (int *)&convert_address, 6, 200.f);
SameLine(); SameLine();
Text("%x", core::SnesToPc(convert_address)); Text("%x", SnesToPc(convert_address));
// mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size()); // mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
BEGIN_TABLE("Memory Comparison", 2, ImGuiTableFlags_Resizable); BEGIN_TABLE("Memory Comparison", 2, ImGuiTableFlags_Resizable);
@@ -75,10 +58,18 @@ struct MemoryEditorWithDiffChecker : public SharedRom {
ImGui::End(); ImGui::End();
} }
// Set the ROM pointer
void set_rom(Rom* rom) { rom_ = rom; }
// Get the ROM pointer
Rom* rom() const { return rom_; }
private:
Rom* rom_;
}; };
} // namespace editor } // namespace editor
} // namespace app
} // namespace yaze } // namespace yaze
#endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H #endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H

View File

@@ -0,0 +1,706 @@
#include "dungeon_canvas_viewer.h"
#include "absl/strings/str_format.h"
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/sprite/sprite.h"
#include "imgui/imgui.h"
namespace yaze::editor {
using ImGui::Button;
using ImGui::Separator;
void DungeonCanvasViewer::DrawDungeonTabView() {
static int next_tab_id = 0;
if (ImGui::BeginTabBar("MyTabBar", ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_TabListPopupButton)) {
if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
if (std::find(active_rooms_.begin(), active_rooms_.end(), current_active_room_tab_) != active_rooms_.end()) {
next_tab_id++;
}
active_rooms_.push_back(next_tab_id++);
}
// Submit our regular tabs
for (int n = 0; n < active_rooms_.Size;) {
bool open = true;
if (active_rooms_[n] > sizeof(zelda3::kRoomNames) / 4) {
active_rooms_.erase(active_rooms_.Data + n);
continue;
}
if (ImGui::BeginTabItem(zelda3::kRoomNames[active_rooms_[n]].data(), &open, ImGuiTabItemFlags_None)) {
current_active_room_tab_ = n;
DrawDungeonCanvas(active_rooms_[n]);
ImGui::EndTabItem();
}
if (!open)
active_rooms_.erase(active_rooms_.Data + n);
else
n++;
}
ImGui::EndTabBar();
}
Separator();
}
void DungeonCanvasViewer::Draw(int room_id) {
DrawDungeonCanvas(room_id);
}
void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
// Validate room_id and ROM
if (room_id < 0 || room_id >= 128) {
ImGui::Text("Invalid room ID: %d", room_id);
return;
}
if (!rom_ || !rom_->is_loaded()) {
ImGui::Text("ROM not loaded");
return;
}
ImGui::BeginGroup();
if (rooms_) {
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();
if (Button("Load Room Graphics")) {
(void)LoadAndRenderRoomGraphics(room_id);
}
}
ImGui::EndGroup();
canvas_.DrawBackground();
canvas_.DrawContextMenu();
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
// Automatically load room graphics if not already loaded
if (room.blocks().empty()) {
(void)LoadAndRenderRoomGraphics(room_id);
}
// Load room objects if not already loaded
if (room.GetTileObjects().empty()) {
room.LoadObjects();
}
// Render background layers with proper positioning
RenderRoomBackgroundLayers(room_id);
// Render room objects with proper graphics
if (current_palette_id_ < current_palette_group_.size()) {
auto room_palette = current_palette_group_[current_palette_id_];
// Render regular objects with proper graphics
for (const auto& object : room.GetTileObjects()) {
RenderObjectInCanvas(object, room_palette);
}
// Render special objects with primitive shapes
RenderStairObjects(room, room_palette);
RenderChests(room);
RenderDoorObjects(room);
RenderWallObjects(room);
RenderPotObjects(room);
// Render sprites as simple 16x16 squares with labels
RenderSprites(room);
}
}
canvas_.DrawGrid();
canvas_.DrawOverlay();
// Draw layer information overlay
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
std::string layer_info = absl::StrFormat(
"Room %03X - Objects: %zu, Sprites: %zu\n"
"Layers are game concept: Objects exist on different levels\n"
"connected by stair objects for player navigation",
room_id, room.GetTileObjects().size(), room.GetSprites().size());
canvas_.DrawText(layer_info, 10, canvas_.height() - 60);
}
}
void DungeonCanvasViewer::RenderObjectInCanvas(const zelda3::RoomObject &object,
const gfx::SnesPalette &palette) {
// Validate ROM is loaded
if (!rom_ || !rom_->is_loaded()) {
return;
}
// Convert room coordinates to canvas coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Check if object is within canvas bounds
if (!IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
return; // Skip objects outside visible area
}
// Create a mutable copy of the object to ensure tiles are loaded
auto mutable_object = object;
mutable_object.set_rom(rom_);
mutable_object.EnsureTilesLoaded();
// Try to render the object with proper graphics
auto render_result = object_renderer_.RenderObject(mutable_object, palette);
if (render_result.ok()) {
auto object_bitmap = std::move(render_result.value());
// Ensure the bitmap is valid and has content
if (object_bitmap.width() > 0 && object_bitmap.height() > 0) {
object_bitmap.SetPalette(palette);
core::Renderer::Get().RenderBitmap(&object_bitmap);
canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}
// Fallback: Draw object as colored rectangle with ID if rendering fails
ImVec4 object_color;
// Color-code objects based on layer
switch (object.layer_) {
case zelda3::RoomObject::LayerType::BG1:
object_color = ImVec4(0.8f, 0.4f, 0.4f, 0.8f); // Red-ish for BG1
break;
case zelda3::RoomObject::LayerType::BG2:
object_color = ImVec4(0.4f, 0.8f, 0.4f, 0.8f); // Green-ish for BG2
break;
case zelda3::RoomObject::LayerType::BG3:
object_color = ImVec4(0.4f, 0.4f, 0.8f, 0.8f); // Blue-ish for BG3
break;
default:
object_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f); // Gray for unknown
break;
}
// Calculate object size (16x16 is base, size affects width/height)
int object_width = 16 + (object.size_ & 0x0F) * 8;
int object_height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
canvas_.DrawRect(canvas_x, canvas_y, object_width, object_height, object_color);
canvas_.DrawRect(canvas_x, canvas_y, object_width, object_height,
ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); // Black border
// Draw object ID
std::string object_text = absl::StrFormat("0x%X", object.id_);
canvas_.DrawText(object_text, canvas_x + object_width + 2, canvas_y);
}
void DungeonCanvasViewer::DisplayObjectInfo(const zelda3::RoomObject &object,
int canvas_x, int canvas_y) {
// Display object information as text overlay
std::string info_text = absl::StrFormat("ID:%d X:%d Y:%d S:%d", object.id_,
object.x_, object.y_, object.size_);
// Draw text at the object position
canvas_.DrawText(info_text, canvas_x, canvas_y - 12);
}
void DungeonCanvasViewer::RenderStairObjects(const zelda3::Room& room,
const gfx::SnesPalette& palette) {
// Render stair objects with special highlighting to show they enable layer transitions
// Stair object IDs from room.h: {0x139, 0x138, 0x13B, 0x12E, 0x12D}
constexpr uint16_t stair_ids[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D};
for (const auto& object : room.GetTileObjects()) {
bool is_stair = false;
for (uint16_t stair_id : stair_ids) {
if (object.id_ == stair_id) {
is_stair = true;
break;
}
}
if (is_stair) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Draw stair object with special highlighting
canvas_.DrawRect(canvas_x - 2, canvas_y - 2, 20, 20,
ImVec4(1.0f, 1.0f, 0.0f, 0.8f)); // Yellow highlight
// Draw text label
std::string stair_text = absl::StrFormat("STAIR\n0x%X", object.id_);
canvas_.DrawText(stair_text, canvas_x + 22, canvas_y);
}
}
}
}
void DungeonCanvasViewer::RenderSprites(const zelda3::Room& room) {
// Render sprites as simple 16x16 squares with sprite name/ID
for (const auto& sprite : room.GetSprites()) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(sprite.x(), sprite.y());
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Draw 16x16 square for sprite
ImVec4 sprite_color;
// Color-code sprites based on layer
if (sprite.layer() == 0) {
sprite_color = ImVec4(0.2f, 0.8f, 0.2f, 0.8f); // Green for layer 0
} else {
sprite_color = ImVec4(0.2f, 0.2f, 0.8f, 0.8f); // Blue for layer 1
}
canvas_.DrawRect(canvas_x, canvas_y, 16, 16, sprite_color);
// Draw sprite border
canvas_.DrawRect(canvas_x, canvas_y, 16, 16, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw sprite ID and name
std::string sprite_text;
if (sprite.id() >= 0) { // sprite.id() is uint8_t so always < 256
// Extract just the sprite name part (remove ID prefix)
std::string full_name = zelda3::kSpriteDefaultNames[sprite.id()];
auto space_pos = full_name.find(' ');
if (space_pos != std::string::npos && space_pos < full_name.length() - 1) {
std::string sprite_name = full_name.substr(space_pos + 1);
// Truncate long names
if (sprite_name.length() > 8) {
sprite_name = sprite_name.substr(0, 8) + "...";
}
sprite_text = absl::StrFormat("%02X\n%s", sprite.id(), sprite_name.c_str());
} else {
sprite_text = absl::StrFormat("%02X", sprite.id());
}
} else {
sprite_text = absl::StrFormat("%02X", sprite.id());
}
canvas_.DrawText(sprite_text, canvas_x + 18, canvas_y);
}
}
}
void DungeonCanvasViewer::RenderChests(const zelda3::Room& room) {
// Render chest objects from tile objects - chests are objects with IDs 0xF9, 0xFA
for (const auto& object : room.GetTileObjects()) {
if (object.id_ == 0xF9 || object.id_ == 0xFA) { // Chest object IDs
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Determine if it's a big chest based on object ID
bool is_big_chest = (object.id_ == 0xFA);
// Draw chest base
ImVec4 chest_color = is_big_chest ?
ImVec4(0.8f, 0.6f, 0.2f, 0.9f) : // Gold for big chest
ImVec4(0.6f, 0.4f, 0.2f, 0.9f); // Brown for small chest
int chest_size = is_big_chest ? 24 : 16; // Big chests are larger
canvas_.DrawRect(canvas_x, canvas_y + 8, chest_size, 8, chest_color);
// Draw chest lid (slightly lighter)
ImVec4 lid_color = is_big_chest ?
ImVec4(0.9f, 0.7f, 0.3f, 0.9f) :
ImVec4(0.7f, 0.5f, 0.3f, 0.9f);
canvas_.DrawRect(canvas_x, canvas_y + 4, chest_size, 6, lid_color);
// Draw chest borders
canvas_.DrawRect(canvas_x, canvas_y + 4, chest_size, 12, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string chest_text = is_big_chest ? "BIG\nCHEST" : "CHEST";
canvas_.DrawText(chest_text, canvas_x + chest_size + 2, canvas_y + 6);
}
}
}
}
void DungeonCanvasViewer::RenderDoorObjects(const zelda3::Room& room) {
// Render door objects from tile objects based on IDs from assembly constants
constexpr uint16_t door_ids[] = {0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E};
for (const auto& object : room.GetTileObjects()) {
bool is_door = false;
for (uint16_t door_id : door_ids) {
if (object.id_ == door_id) {
is_door = true;
break;
}
}
if (is_door) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Draw door frame
canvas_.DrawRect(canvas_x, canvas_y, 32, 32, ImVec4(0.5f, 0.3f, 0.2f, 0.8f)); // Brown frame
// Draw door opening (darker)
canvas_.DrawRect(canvas_x + 4, canvas_y + 4, 24, 24, ImVec4(0.1f, 0.1f, 0.1f, 0.9f));
// Draw door border
canvas_.DrawRect(canvas_x, canvas_y, 32, 32, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string door_text = absl::StrFormat("DOOR\n0x%X", object.id_);
canvas_.DrawText(door_text, canvas_x + 34, canvas_y + 8);
}
}
}
}
void DungeonCanvasViewer::RenderWallObjects(const zelda3::Room& room) {
// Render wall objects with proper dimensions based on properties
for (const auto& object : room.GetTileObjects()) {
if (object.id_ >= 0x10 && object.id_ <= 0x1F) { // Wall objects range
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Different wall types based on ID
ImVec4 wall_color;
std::string wall_type;
switch (object.id_) {
case 0x10: // Basic wall
wall_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f);
wall_type = "WALL";
break;
case 0x11: // Corner wall
wall_color = ImVec4(0.7f, 0.7f, 0.6f, 0.8f);
wall_type = "CORNER";
break;
case 0x12: // Decorative wall
wall_color = ImVec4(0.8f, 0.7f, 0.6f, 0.8f);
wall_type = "DEC_WALL";
break;
default:
wall_color = ImVec4(0.5f, 0.5f, 0.5f, 0.8f);
wall_type = "WALL";
break;
}
// Calculate wall size with proper length handling
int wall_width, wall_height;
// For walls, use the size field to determine length
if (object.id_ >= 0x10 && object.id_ <= 0x1F) {
uint8_t size_x = object.size_ & 0x0F;
uint8_t size_y = (object.size_ >> 4) & 0x0F;
if (size_x > size_y) {
// Horizontal wall
wall_width = 16 + size_x * 16;
wall_height = 16;
} else if (size_y > size_x) {
// Vertical wall
wall_width = 16;
wall_height = 16 + size_y * 16;
} else {
// Square wall or corner
wall_width = 16 + size_x * 8;
wall_height = 16 + size_y * 8;
}
} else {
wall_width = 16 + (object.size_ & 0x0F) * 8;
wall_height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
}
wall_width = std::min(wall_width, 256);
wall_height = std::min(wall_height, 256);
canvas_.DrawRect(canvas_x, canvas_y, wall_width, wall_height, wall_color);
canvas_.DrawRect(canvas_x, canvas_y, wall_width, wall_height, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Add stone block pattern
for (int i = 0; i < wall_width; i += 8) {
for (int j = 0; j < wall_height; j += 8) {
canvas_.DrawRect(canvas_x + i, canvas_y + j, 6, 6,
ImVec4(wall_color.x * 0.9f, wall_color.y * 0.9f, wall_color.z * 0.9f, wall_color.w));
}
}
// Draw text label
std::string wall_text = absl::StrFormat("%s\n0x%X\n%dx%d", wall_type.c_str(), object.id_, wall_width/16, wall_height/16);
canvas_.DrawText(wall_text, canvas_x + wall_width + 2, canvas_y + 4);
}
}
}
}
void DungeonCanvasViewer::RenderPotObjects(const zelda3::Room& room) {
// Render pot objects based on assembly constants - Object_Pot is 0x2F
for (const auto& object : room.GetTileObjects()) {
if (object.id_ == 0x2F || object.id_ == 0x2B) { // Pot objects from assembly
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Draw pot base (wider at bottom)
canvas_.DrawRect(canvas_x + 2, canvas_y + 10, 12, 6, ImVec4(0.7f, 0.5f, 0.3f, 0.8f)); // Brown base
// Draw pot middle
canvas_.DrawRect(canvas_x + 3, canvas_y + 6, 10, 6, ImVec4(0.8f, 0.6f, 0.4f, 0.8f)); // Lighter middle
// Draw pot rim
canvas_.DrawRect(canvas_x + 4, canvas_y + 4, 8, 4, ImVec4(0.9f, 0.7f, 0.5f, 0.8f)); // Lightest top
// Draw pot outline
canvas_.DrawRect(canvas_x + 2, canvas_y + 4, 12, 12, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string pot_text = absl::StrFormat("POT\n0x%X", object.id_);
canvas_.DrawText(pot_text, canvas_x + 18, canvas_y + 6);
}
}
}
}
// Coordinate conversion helper functions
std::pair<int, int> DungeonCanvasViewer::RoomToCanvasCoordinates(int room_x,
int room_y) const {
// Convert room coordinates (tile units) to canvas coordinates (pixels)
// Account for canvas scaling and offset
float scale = canvas_.global_scale();
int offset_x = static_cast<int>(canvas_.drawn_tile_position().x);
int offset_y = static_cast<int>(canvas_.drawn_tile_position().y);
return {static_cast<int>((room_x * 16 + offset_x) * scale),
static_cast<int>((room_y * 16 + offset_y) * scale)};
}
std::pair<int, int> DungeonCanvasViewer::CanvasToRoomCoordinates(int canvas_x,
int canvas_y) const {
// Convert canvas coordinates (pixels) to room coordinates (tile units)
// Account for canvas scaling and offset
float scale = canvas_.global_scale();
int offset_x = static_cast<int>(canvas_.drawn_tile_position().x);
int offset_y = static_cast<int>(canvas_.drawn_tile_position().y);
if (scale <= 0.0f) scale = 1.0f; // Prevent division by zero
return {static_cast<int>((canvas_x / scale - offset_x) / 16),
static_cast<int>((canvas_y / scale - offset_y) / 16)};
}
bool DungeonCanvasViewer::IsWithinCanvasBounds(int canvas_x, int canvas_y,
int margin) const {
// Check if coordinates are within canvas bounds with optional margin
auto canvas_width = canvas_.width();
auto canvas_height = canvas_.height();
return (canvas_x >= -margin && canvas_y >= -margin &&
canvas_x <= canvas_width + margin &&
canvas_y <= canvas_height + margin);
}
void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height) {
// Default base size
width = 16;
height = 16;
// For walls, use the size field to determine length and orientation
if (object.id_ >= 0x10 && object.id_ <= 0x1F) {
// Wall objects: size determines length and orientation
uint8_t size_x = object.size_ & 0x0F;
uint8_t size_y = (object.size_ >> 4) & 0x0F;
// Walls can be horizontal or vertical based on size parameters
if (size_x > size_y) {
// Horizontal wall
width = 16 + size_x * 16; // Each unit adds 16 pixels
height = 16;
} else if (size_y > size_x) {
// Vertical wall
width = 16;
height = 16 + size_y * 16;
} else {
// Square wall or corner
width = 16 + size_x * 8;
height = 16 + size_y * 8;
}
} else {
// For other objects, use standard size calculation
width = 16 + (object.size_ & 0x0F) * 8;
height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
}
// Clamp to reasonable limits
width = std::min(width, 256);
height = std::min(height, 256);
}
// Room graphics management methods
absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) {
if (room_id < 0 || room_id >= 128) {
return absl::InvalidArgumentError("Invalid room ID");
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (!rooms_) {
return absl::FailedPreconditionError("Room data not available");
}
auto& room = (*rooms_)[room_id];
// Load room graphics with proper blockset
room.LoadRoomGraphics(room.blockset);
// Load the room's palette with bounds checking
if (room.palette < rom_->paletteset_ids.size() &&
!rom_->paletteset_ids[room.palette].empty()) {
auto dungeon_palette_ptr = rom_->paletteset_ids[room.palette][0];
auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr);
if (palette_id.ok()) {
current_palette_group_id_ = palette_id.value() / 180;
if (current_palette_group_id_ < rom_->palette_group().dungeon_main.size()) {
auto full_palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
ASSIGN_OR_RETURN(current_palette_group_,
gfx::CreatePaletteGroupFromLargePalette(full_palette));
}
}
}
// Render the room graphics to the graphics arena
room.RenderRoomGraphics();
// Update the background layers with proper palette
RETURN_IF_ERROR(UpdateRoomBackgroundLayers(room_id));
return absl::OkStatus();
}
absl::Status DungeonCanvasViewer::UpdateRoomBackgroundLayers(int room_id) {
if (room_id < 0 || room_id >= 128) {
return absl::InvalidArgumentError("Invalid room ID");
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (!rooms_) {
return absl::FailedPreconditionError("Room data not available");
}
auto& room = (*rooms_)[room_id];
// Validate palette group access
if (current_palette_group_id_ >= rom_->palette_group().dungeon_main.size()) {
return absl::FailedPreconditionError("Invalid palette group ID");
}
// Get the current room's palette
auto current_palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
// Update BG1 (background layer 1) with proper palette
if (room.blocks().size() >= 8) {
for (int i = 0; i < 8; i++) {
int block = room.blocks()[i];
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
if (current_palette_id_ < current_palette_group_.size()) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
current_palette_group_[current_palette_id_], 0);
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
}
}
}
}
// Update BG2 (background layer 2) with sprite auxiliary palette
if (room.blocks().size() >= 16) {
auto sprites_aux1_pal_group = rom_->palette_group().sprites_aux1;
if (current_palette_id_ < sprites_aux1_pal_group.size()) {
for (int i = 8; i < 16; i++) {
int block = room.blocks()[i];
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
sprites_aux1_pal_group[current_palette_id_], 0);
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
}
}
}
}
return absl::OkStatus();
}
void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
if (room_id < 0 || room_id >= 128) {
return;
}
if (!rom_ || !rom_->is_loaded()) {
return;
}
if (!rooms_) {
return;
}
// Get canvas dimensions to limit rendering
int canvas_width = canvas_.width();
int canvas_height = canvas_.height();
// Validate canvas dimensions
if (canvas_width <= 0 || canvas_height <= 0) {
return;
}
// Render the room's background layers using the graphics arena
// BG1 (background layer 1) - main room graphics
auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap();
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
// Scale the background to fit the canvas
float scale_x = static_cast<float>(canvas_width) / bg1_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg1_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg1_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg1_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_.DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255);
}
// BG2 (background layer 2) - sprite graphics (overlay)
auto& bg2_bitmap = gfx::Arena::Get().bg2().bitmap();
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
// Scale the background to fit the canvas
float scale_x = static_cast<float>(canvas_width) / bg2_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg2_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg2_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg2_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_.DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200); // Semi-transparent overlay
}
}
} // namespace yaze::editor

View File

@@ -0,0 +1,105 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/gfx/snes_palette.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
/**
* @brief Handles the main dungeon canvas rendering and interaction
*
* In Link to the Past, dungeon "layers" are not separate visual layers
* but a game concept where objects exist on different logical levels.
* Players move between these levels using stair objects that act as
* transitions between the different object planes.
*/
class DungeonCanvasViewer {
public:
explicit DungeonCanvasViewer(Rom* rom = nullptr) : rom_(rom), object_renderer_(rom) {}
void DrawDungeonTabView();
void DrawDungeonCanvas(int room_id);
void Draw(int room_id);
void SetRom(Rom* rom) {
rom_ = rom;
object_renderer_.SetROM(rom);
}
Rom* rom() const { return rom_; }
// Room data access
void SetRooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
void set_active_rooms(const ImVector<int>& rooms) { active_rooms_ = rooms; }
void set_current_active_room_tab(int tab) { current_active_room_tab_ = tab; }
// Palette access
void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; }
void SetCurrentPaletteId(uint64_t id) { current_palette_id_ = id; }
void SetCurrentPaletteGroup(const gfx::PaletteGroup& group) { current_palette_group_ = group; }
// Canvas access
gui::Canvas& canvas() { return canvas_; }
const gui::Canvas& canvas() const { return canvas_; }
private:
void RenderObjectInCanvas(const zelda3::RoomObject &object,
const gfx::SnesPalette &palette);
void DisplayObjectInfo(const zelda3::RoomObject &object, int canvas_x,
int canvas_y);
void RenderStairObjects(const zelda3::Room& room,
const gfx::SnesPalette& palette);
void RenderSprites(const zelda3::Room& room);
void RenderChests(const zelda3::Room& room);
void RenderDoorObjects(const zelda3::Room& room);
void RenderWallObjects(const zelda3::Room& room);
void RenderPotObjects(const zelda3::Room& room);
// Coordinate conversion helpers
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const;
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
// Object dimension calculation
void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height);
// Room graphics management
absl::Status LoadAndRenderRoomGraphics(int room_id);
absl::Status UpdateRoomBackgroundLayers(int room_id);
void RenderRoomBackgroundLayers(int room_id);
Rom* rom_ = nullptr;
gui::Canvas canvas_{"##DungeonCanvas", ImVec2(0x200, 0x200)};
zelda3::ObjectRenderer object_renderer_;
// Room data
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
ImVector<int> active_rooms_;
int current_active_room_tab_ = 0;
// Palette data
uint64_t current_palette_group_id_ = 0;
uint64_t current_palette_id_ = 0;
gfx::PaletteGroup current_palette_group_;
// Object rendering cache
struct ObjectRenderCache {
int object_id;
int object_x, object_y, object_size;
uint64_t palette_hash;
gfx::Bitmap rendered_bitmap;
bool is_valid;
};
std::vector<ObjectRenderCache> object_render_cache_;
uint64_t last_palette_hash_ = 0;
};
} // namespace editor
} // namespace yaze
#endif

Some files were not shown because too many files have changed in this diff Show More