Update CMake configuration and CI/CD workflows
- Upgraded CMake minimum version requirement to 3.16 and updated project version to 0.3.0. - Introduced new CMake presets for build configurations, including default, debug, and release options. - Added CI/CD workflows for continuous integration and release management, enhancing automated testing and deployment processes. - Integrated Asar assembler support with new wrapper classes and CLI commands for patching ROMs. - Implemented comprehensive tests for Asar integration, ensuring robust functionality and error handling. - Enhanced packaging configuration for cross-platform support, including Windows, macOS, and Linux. - Updated documentation and added test assets for improved clarity and usability.
This commit is contained in:
340
.github/workflows/ci.yml
vendored
Normal file
340
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
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)"
|
||||||
|
os: ubuntu-22.04
|
||||||
|
cc: gcc-11
|
||||||
|
cxx: g++-11
|
||||||
|
vcpkg_triplet: x64-linux
|
||||||
|
|
||||||
|
- name: "Ubuntu 22.04 (Clang)"
|
||||||
|
os: ubuntu-22.04
|
||||||
|
cc: clang-14
|
||||||
|
cxx: clang++-14
|
||||||
|
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
|
||||||
|
|
||||||
|
- 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
|
||||||
|
- name: Set up vcpkg
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
uses: lukka/run-vcpkg@v11
|
||||||
|
with:
|
||||||
|
vcpkgGitCommitId: 'c8696863d371ab7f46e213d8f5ca923c4aef2a00'
|
||||||
|
|
||||||
|
# Configure CMake
|
||||||
|
- 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 }} \
|
||||||
|
-GNinja
|
||||||
|
|
||||||
|
- name: Configure CMake (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: |
|
||||||
|
cmake -B ${{ github.workspace }}/build ^
|
||||||
|
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} ^
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake ^
|
||||||
|
-DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_triplet }} ^
|
||||||
|
-G "${{ matrix.cmake_generator }}" ^
|
||||||
|
-A ${{ matrix.cmake_generator_platform }}
|
||||||
|
|
||||||
|
# Build
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} --parallel
|
||||||
|
|
||||||
|
# Test (excluding ROM-dependent tests in CI)
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{ github.workspace }}/build
|
||||||
|
run: ctest --build-config ${{ env.BUILD_TYPE }} --output-on-failure --parallel --label-exclude ROM_DEPENDENT
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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: |
|
||||||
|
find src test -name "*.cc" -o -name "*.h" | \
|
||||||
|
xargs clang-format-14 --dry-run --Werror
|
||||||
|
|
||||||
|
- name: Run cppcheck
|
||||||
|
run: |
|
||||||
|
cppcheck --enable=all --error-exitcode=1 \
|
||||||
|
--suppress=missingIncludeSystem \
|
||||||
|
--suppress=unusedFunction \
|
||||||
|
--suppress=unmatchedSuppression \
|
||||||
|
src/
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- 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" \
|
||||||
|
-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
|
||||||
|
|
||||||
|
- 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" \
|
||||||
|
-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
|
||||||
274
.github/workflows/release.yml
vendored
Normal file
274
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag'
|
||||||
|
required: true
|
||||||
|
default: 'v0.3.0'
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
name: Create Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
release_id: ${{ steps.create_release.outputs.id }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Generate release notes
|
||||||
|
id: release_notes
|
||||||
|
run: |
|
||||||
|
# Generate changelog from commits since last tag
|
||||||
|
LAST_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||||
|
if [ -z "$LAST_TAG" ]; then
|
||||||
|
CHANGES=$(git log --pretty=format:"- %s" HEAD)
|
||||||
|
else
|
||||||
|
CHANGES=$(git log --pretty=format:"- %s" ${LAST_TAG}..HEAD)
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > release_notes.md << EOF
|
||||||
|
# Yaze v0.3.0 Release Notes
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
- **Asar 65816 Assembler Integration**: Full cross-platform support for ROM patching with assembly code
|
||||||
|
- **Symbol Extraction**: Extract symbol names and opcodes from assembly files
|
||||||
|
- **ZSCustomOverworld v3 Support**: Enhanced overworld editing capabilities
|
||||||
|
- **Message Editing**: Improved text editing interface
|
||||||
|
- **GUI Docking**: Enhanced docking system for better workflow
|
||||||
|
- **Modern CMake Build System**: Updated to CMake 3.16+ with improved cross-platform support
|
||||||
|
|
||||||
|
## Improvements
|
||||||
|
- Enhanced cross-platform compatibility (Windows, macOS, Linux)
|
||||||
|
- Modernized CI/CD pipeline with comprehensive testing
|
||||||
|
- Improved error handling and logging
|
||||||
|
- Better memory management and performance optimizations
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
- Fixed Asar integration issues across different platforms
|
||||||
|
- Resolved build system inconsistencies
|
||||||
|
- Improved stability and reliability
|
||||||
|
|
||||||
|
## Technical Changes
|
||||||
|
$CHANGES
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref_name || github.event.inputs.tag }}
|
||||||
|
release_name: Yaze ${{ github.ref_name || github.event.inputs.tag }}
|
||||||
|
body_path: release_notes.md
|
||||||
|
draft: false
|
||||||
|
prerelease: ${{ contains(github.ref_name || github.event.inputs.tag, 'beta') || contains(github.ref_name || github.event.inputs.tag, 'alpha') || contains(github.ref_name || github.event.inputs.tag, 'rc') }}
|
||||||
|
|
||||||
|
build-release:
|
||||||
|
name: Build Release
|
||||||
|
needs: create-release
|
||||||
|
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 assets/yaze.png 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 assets/yaze.png 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: |
|
||||||
|
# Create macOS app bundle and DMG
|
||||||
|
mkdir -p "Yaze.app/Contents/MacOS"
|
||||||
|
mkdir -p "Yaze.app/Contents/Resources"
|
||||||
|
cp build/bin/yaze "Yaze.app/Contents/MacOS/"
|
||||||
|
cp assets/yaze.png "Yaze.app/Contents/Resources/"
|
||||||
|
cp cmake/yaze.plist.in "Yaze.app/Contents/Info.plist"
|
||||||
|
|
||||||
|
# Create DMG
|
||||||
|
mkdir dmg_staging
|
||||||
|
cp -r Yaze.app dmg_staging/
|
||||||
|
cp LICENSE dmg_staging/
|
||||||
|
cp README.md dmg_staging/
|
||||||
|
hdiutil create -srcfolder dmg_staging -format UDZO -volname "Yaze v0.3.0" 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 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: Set up vcpkg (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
uses: lukka/run-vcpkg@v11
|
||||||
|
with:
|
||||||
|
vcpkgGitCommitId: 'c8696863d371ab7f46e213d8f5ca923c4aef2a00'
|
||||||
|
|
||||||
|
# Configure CMake
|
||||||
|
- name: Configure CMake (Linux/macOS)
|
||||||
|
if: runner.os != 'Windows'
|
||||||
|
run: |
|
||||||
|
cmake -B build \
|
||||||
|
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
|
||||||
|
-DYAZE_BUILD_TESTS=OFF \
|
||||||
|
-GNinja
|
||||||
|
|
||||||
|
- name: Configure CMake (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: |
|
||||||
|
cmake -B build ^
|
||||||
|
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} ^
|
||||||
|
-DYAZE_BUILD_TESTS=OFF ^
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake ^
|
||||||
|
-DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_triplet }} ^
|
||||||
|
-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 }}
|
||||||
|
|
||||||
|
# Upload to release
|
||||||
|
- name: Upload Release Asset
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||||
|
asset_path: ./${{ matrix.artifact_name }}.*
|
||||||
|
asset_name: ${{ matrix.artifact_name }}.${{ runner.os == 'Windows' && 'zip' || (runner.os == 'macOS' && 'dmg' || 'tar.gz') }}
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
publish-packages:
|
||||||
|
name: Publish Packages
|
||||||
|
needs: [create-release, build-release]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: success()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Update release status
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.repos.updateRelease({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
release_id: ${{ needs.create-release.outputs.release_id }},
|
||||||
|
draft: false
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Announce release
|
||||||
|
run: |
|
||||||
|
echo "🎉 Yaze ${{ github.ref_name || github.event.inputs.tag }} has been released!"
|
||||||
|
echo "📦 Packages are now available for download"
|
||||||
|
echo "🔗 Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name || github.event.inputs.tag }}"
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
# 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.2
|
project(yaze VERSION 0.3.0
|
||||||
DESCRIPTION "Yet Another Zelda3 Editor"
|
DESCRIPTION "Yet Another Zelda3 Editor"
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX C)
|
||||||
configure_file(src/yaze_config.h.in yaze_config.h)
|
|
||||||
|
# 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)
|
||||||
@@ -14,31 +20,65 @@ set(YAZE_BUILD_Z3ED ON)
|
|||||||
set(YAZE_BUILD_TESTS ON)
|
set(YAZE_BUILD_TESTS ON)
|
||||||
set(YAZE_INSTALL_LIB OFF)
|
set(YAZE_INSTALL_LIB OFF)
|
||||||
|
|
||||||
|
# ROM Testing Configuration
|
||||||
|
option(YAZE_ENABLE_ROM_TESTS "Enable tests that require ROM files" OFF)
|
||||||
|
set(YAZE_TEST_ROM_PATH "${CMAKE_BINARY_DIR}/bin/zelda3.sfc" CACHE STRING "Path to test ROM file")
|
||||||
|
|
||||||
# libpng features in bitmap.cc
|
# libpng features in bitmap.cc
|
||||||
add_definitions("-DYAZE_LIB_PNG=1")
|
add_definitions("-DYAZE_LIB_PNG=1")
|
||||||
|
|
||||||
# C++ Standard and CMake Specifications
|
# Modern CMake standards
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
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 "")
|
|
||||||
|
|
||||||
if (UNIX)
|
# Platform detection
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dlinux -Dstricmp=strcasecmp")
|
if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||||
|
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()
|
endif()
|
||||||
|
|
||||||
if (MACOS)
|
# Create a common interface target for shared settings
|
||||||
set(CMAKE_INSTALL_PREFIX /usr/local)
|
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()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
# Compiler-specific settings
|
||||||
include(cmake/vcpkg.cmake)
|
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
|
||||||
@@ -62,3 +102,6 @@ include(cmake/gtest.cmake)
|
|||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Packaging configuration
|
||||||
|
include(cmake/packaging.cmake)
|
||||||
|
|
||||||
|
|||||||
336
CMakePresets.json
Normal file
336
CMakePresets.json
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
{
|
||||||
|
"version": 6,
|
||||||
|
"cmakeMinimumRequired": {
|
||||||
|
"major": 3,
|
||||||
|
"minor": 16,
|
||||||
|
"patch": 0
|
||||||
|
},
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"displayName": "Default Config",
|
||||||
|
"description": "Default build configuration",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "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",
|
||||||
|
"description": "macOS-specific 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",
|
||||||
|
"displayName": "macOS Release",
|
||||||
|
"description": "macOS-specific 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": "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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fast",
|
||||||
|
"configurePreset": "debug",
|
||||||
|
"displayName": "Fast Debug Build",
|
||||||
|
"jobs": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"testPresets": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"configurePreset": "default",
|
||||||
|
"displayName": "Default Tests",
|
||||||
|
"execution": {
|
||||||
|
"noTestsAction": "error",
|
||||||
|
"stopOnFailure": false
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"exclude": {
|
||||||
|
"label": "ROM_DEPENDENT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev",
|
||||||
|
"configurePreset": "dev",
|
||||||
|
"displayName": "Development Tests (with ROM)",
|
||||||
|
"execution": {
|
||||||
|
"noTestsAction": "error",
|
||||||
|
"stopOnFailure": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ci",
|
||||||
|
"configurePreset": "ci",
|
||||||
|
"displayName": "CI Tests (no ROM)",
|
||||||
|
"execution": {
|
||||||
|
"noTestsAction": "error",
|
||||||
|
"stopOnFailure": false
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"exclude": {
|
||||||
|
"label": "ROM_DEPENDENT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unit-only",
|
||||||
|
"configurePreset": "default",
|
||||||
|
"displayName": "Unit Tests Only",
|
||||||
|
"filter": {
|
||||||
|
"include": {
|
||||||
|
"label": "UNIT_TEST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "integration-only",
|
||||||
|
"configurePreset": "dev",
|
||||||
|
"displayName": "Integration Tests Only",
|
||||||
|
"filter": {
|
||||||
|
"include": {
|
||||||
|
"label": "INTEGRATION_TEST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packagePresets": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"configurePreset": "release",
|
||||||
|
"displayName": "Default Package"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos",
|
||||||
|
"configurePreset": "macos-release",
|
||||||
|
"displayName": "macOS Package"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
121
cmake/asar.cmake
121
cmake/asar.cmake
@@ -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()
|
||||||
200
cmake/packaging.cmake
Normal file
200
cmake/packaging.cmake
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
# 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 NSIS installer configuration
|
||||||
|
set(CPACK_GENERATOR "NSIS;ZIP")
|
||||||
|
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'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Windows architecture detection
|
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-win64")
|
||||||
|
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
|
||||||
|
else()
|
||||||
|
set(CPACK_PACKAGE_FILE_NAME "yaze-${CPACK_PACKAGE_VERSION}-win32")
|
||||||
|
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
|
||||||
|
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/assets/dmg_background.png")
|
||||||
|
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_SOURCE_DIR}/cmake/dmg_setup.scpt")
|
||||||
|
|
||||||
|
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)
|
||||||
13
cmake/yaze.desktop.in
Normal file
13
cmake/yaze.desktop.in
Normal 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;
|
||||||
150
scripts/test_asar_integration.py
Executable file
150
scripts/test_asar_integration.py
Executable 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())
|
||||||
@@ -112,6 +112,7 @@ if (YAZE_BUILD_LIB)
|
|||||||
app/
|
app/
|
||||||
${CMAKE_SOURCE_DIR}/incl/
|
${CMAKE_SOURCE_DIR}/incl/
|
||||||
${CMAKE_SOURCE_DIR}/src/
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
|
${ASAR_INCLUDE_DIRS}
|
||||||
${PNG_INCLUDE_DIRS}
|
${PNG_INCLUDE_DIRS}
|
||||||
${SDL2_INCLUDE_DIR}
|
${SDL2_INCLUDE_DIR}
|
||||||
${PROJECT_BINARY_DIR}
|
${PROJECT_BINARY_DIR}
|
||||||
@@ -119,6 +120,7 @@ if (YAZE_BUILD_LIB)
|
|||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
yaze_c PRIVATE
|
yaze_c PRIVATE
|
||||||
|
asar-static
|
||||||
${ABSL_TARGETS}
|
${ABSL_TARGETS}
|
||||||
${SDL_TARGETS}
|
${SDL_TARGETS}
|
||||||
${PNG_LIBRARIES}
|
${PNG_LIBRARIES}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ target_include_directories(
|
|||||||
yaze PUBLIC
|
yaze PUBLIC
|
||||||
lib/
|
lib/
|
||||||
app/
|
app/
|
||||||
${ASAR_INCLUDE_DIR}
|
${ASAR_INCLUDE_DIRS}
|
||||||
${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
|
||||||
|
|||||||
293
src/app/core/asar_wrapper.cc
Normal file
293
src/app/core/asar_wrapper.cc
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
#include "app/core/asar_wrapper.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#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()) {
|
||||||
|
temp_path = base_path + "/temp_patch.asm";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream temp_file(temp_path);
|
||||||
|
if (!temp_file) {
|
||||||
|
return absl::InternalError("Failed to create temporary patch file");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
212
src/app/core/asar_wrapper.h
Normal 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
|
||||||
@@ -4,6 +4,7 @@ set(
|
|||||||
app/emu/emulator.cc
|
app/emu/emulator.cc
|
||||||
app/core/project.cc
|
app/core/project.cc
|
||||||
app/core/window.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)
|
||||||
|
|||||||
335
src/cli/cli_main.cc
Normal file
335
src/cli/cli_main.cc
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "absl/flags/flag.h"
|
||||||
|
#include "absl/flags/parse.h"
|
||||||
|
#include "absl/flags/usage.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_join.h"
|
||||||
|
|
||||||
|
#include "cli/z3ed.h"
|
||||||
|
#include "cli/tui.h"
|
||||||
|
#include "app/core/asar_wrapper.h"
|
||||||
|
|
||||||
|
// Global flags
|
||||||
|
ABSL_FLAG(bool, tui, false, "Launch the Text User Interface");
|
||||||
|
ABSL_FLAG(bool, version, false, "Show version information");
|
||||||
|
ABSL_FLAG(bool, verbose, false, "Enable verbose output");
|
||||||
|
ABSL_FLAG(std::string, rom, "", "Path to the ROM file");
|
||||||
|
|
||||||
|
// Command-specific flags
|
||||||
|
ABSL_FLAG(std::string, output, "", "Output file path");
|
||||||
|
ABSL_FLAG(bool, dry_run, false, "Perform a dry run without making changes");
|
||||||
|
ABSL_FLAG(bool, backup, true, "Create a backup before modifying files");
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace cli {
|
||||||
|
|
||||||
|
struct CommandInfo {
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
std::string usage;
|
||||||
|
std::function<absl::Status(const std::vector<std::string>&)> handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModernCLI {
|
||||||
|
public:
|
||||||
|
ModernCLI() {
|
||||||
|
SetupCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupCommands() {
|
||||||
|
commands_["asar"] = {
|
||||||
|
.name = "asar",
|
||||||
|
.description = "Apply Asar 65816 assembly patch to ROM",
|
||||||
|
.usage = "z3ed asar <patch.asm> [--rom=<rom_file>] [--output=<output_file>]",
|
||||||
|
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||||
|
return HandleAsarCommand(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commands_["patch"] = {
|
||||||
|
.name = "patch",
|
||||||
|
.description = "Apply BPS patch to ROM",
|
||||||
|
.usage = "z3ed patch <patch.bps> [--rom=<rom_file>] [--output=<output_file>]",
|
||||||
|
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||||
|
return HandlePatchCommand(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commands_["extract"] = {
|
||||||
|
.name = "extract",
|
||||||
|
.description = "Extract symbols from assembly file",
|
||||||
|
.usage = "z3ed extract <patch.asm>",
|
||||||
|
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||||
|
return HandleExtractCommand(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commands_["validate"] = {
|
||||||
|
.name = "validate",
|
||||||
|
.description = "Validate assembly file syntax",
|
||||||
|
.usage = "z3ed validate <patch.asm>",
|
||||||
|
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||||
|
return HandleValidateCommand(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commands_["info"] = {
|
||||||
|
.name = "info",
|
||||||
|
.description = "Show ROM information",
|
||||||
|
.usage = "z3ed info [--rom=<rom_file>]",
|
||||||
|
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||||
|
return HandleInfoCommand(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commands_["convert"] = {
|
||||||
|
.name = "convert",
|
||||||
|
.description = "Convert between SNES and PC addresses",
|
||||||
|
.usage = "z3ed convert <address> [--to-pc|--to-snes]",
|
||||||
|
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||||
|
return HandleConvertCommand(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commands_["help"] = {
|
||||||
|
.name = "help",
|
||||||
|
.description = "Show help information",
|
||||||
|
.usage = "z3ed help [command]",
|
||||||
|
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||||
|
return HandleHelpCommand(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowVersion() {
|
||||||
|
std::cout << "z3ed v0.3.0 - Yet Another Zelda3 Editor CLI" << std::endl;
|
||||||
|
std::cout << "Built with Asar integration" << std::endl;
|
||||||
|
std::cout << "Copyright (c) 2025 scawful" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowHelp(const std::string& command = "") {
|
||||||
|
if (!command.empty()) {
|
||||||
|
auto it = commands_.find(command);
|
||||||
|
if (it != commands_.end()) {
|
||||||
|
std::cout << "Command: " << it->second.name << std::endl;
|
||||||
|
std::cout << "Description: " << it->second.description << std::endl;
|
||||||
|
std::cout << "Usage: " << it->second.usage << std::endl;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
std::cout << "Unknown command: " << command << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "z3ed - Yet Another Zelda3 Editor CLI Tool" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
std::cout << "USAGE:" << std::endl;
|
||||||
|
std::cout << " z3ed [--tui] [command] [arguments]" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
std::cout << "GLOBAL FLAGS:" << std::endl;
|
||||||
|
std::cout << " --tui Launch Text User Interface" << std::endl;
|
||||||
|
std::cout << " --version Show version information" << std::endl;
|
||||||
|
std::cout << " --verbose Enable verbose output" << std::endl;
|
||||||
|
std::cout << " --rom=<file> Specify ROM file to use" << std::endl;
|
||||||
|
std::cout << " --output=<file> Specify output file path" << std::endl;
|
||||||
|
std::cout << " --dry-run Perform operations without making changes" << std::endl;
|
||||||
|
std::cout << " --backup=<bool> Create backup before modifying (default: true)" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
std::cout << "COMMANDS:" << std::endl;
|
||||||
|
|
||||||
|
for (const auto& [name, info] : commands_) {
|
||||||
|
std::cout << absl::StrFormat(" %-12s %s", name, info.description) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
std::cout << "EXAMPLES:" << std::endl;
|
||||||
|
std::cout << " z3ed --tui # Launch TUI" << std::endl;
|
||||||
|
std::cout << " z3ed asar patch.asm --rom=zelda3.sfc # Apply Asar patch" << std::endl;
|
||||||
|
std::cout << " z3ed patch changes.bps --rom=zelda3.sfc # Apply BPS patch" << std::endl;
|
||||||
|
std::cout << " z3ed extract patch.asm # Extract symbols" << std::endl;
|
||||||
|
std::cout << " z3ed validate patch.asm # Validate assembly" << std::endl;
|
||||||
|
std::cout << " z3ed info --rom=zelda3.sfc # Show ROM info" << std::endl;
|
||||||
|
std::cout << " z3ed convert 0x008000 --to-pc # Convert address" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
std::cout << "For more information on a specific command:" << std::endl;
|
||||||
|
std::cout << " z3ed help <command>" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status RunCommand(const std::string& command, const std::vector<std::string>& args) {
|
||||||
|
auto it = commands_.find(command);
|
||||||
|
if (it == commands_.end()) {
|
||||||
|
return absl::NotFoundError(absl::StrFormat("Unknown command: %s", command));
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second.handler(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, CommandInfo> commands_;
|
||||||
|
|
||||||
|
absl::Status HandleAsarCommand(const std::vector<std::string>& args) {
|
||||||
|
if (args.empty()) {
|
||||||
|
return absl::InvalidArgumentError("Asar command requires a patch file");
|
||||||
|
}
|
||||||
|
|
||||||
|
AsarPatch handler;
|
||||||
|
std::vector<std::string> handler_args = args;
|
||||||
|
|
||||||
|
// Add ROM file from flag if not provided as argument
|
||||||
|
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||||
|
if (args.size() == 1 && !rom_file.empty()) {
|
||||||
|
handler_args.push_back(rom_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.Run(handler_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status HandlePatchCommand(const std::vector<std::string>& args) {
|
||||||
|
if (args.empty()) {
|
||||||
|
return absl::InvalidArgumentError("Patch command requires a BPS file");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyPatch handler;
|
||||||
|
std::vector<std::string> handler_args = args;
|
||||||
|
|
||||||
|
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||||
|
if (args.size() == 1 && !rom_file.empty()) {
|
||||||
|
handler_args.push_back(rom_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.Run(handler_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status HandleExtractCommand(const std::vector<std::string>& args) {
|
||||||
|
if (args.empty()) {
|
||||||
|
return absl::InvalidArgumentError("Extract command requires an assembly file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the AsarWrapper to extract symbols
|
||||||
|
yaze::app::core::AsarWrapper wrapper;
|
||||||
|
RETURN_IF_ERROR(wrapper.Initialize());
|
||||||
|
|
||||||
|
auto symbols_result = wrapper.ExtractSymbols(args[0]);
|
||||||
|
if (!symbols_result.ok()) {
|
||||||
|
return symbols_result.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& symbols = symbols_result.value();
|
||||||
|
std::cout << "🏷️ Extracted " << symbols.size() << " symbols from " << args[0] << ":" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
for (const auto& symbol : symbols) {
|
||||||
|
std::cout << absl::StrFormat(" %-20s @ $%06X", symbol.name, symbol.address) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status HandleValidateCommand(const std::vector<std::string>& args) {
|
||||||
|
if (args.empty()) {
|
||||||
|
return absl::InvalidArgumentError("Validate command requires an assembly file");
|
||||||
|
}
|
||||||
|
|
||||||
|
yaze::app::core::AsarWrapper wrapper;
|
||||||
|
RETURN_IF_ERROR(wrapper.Initialize());
|
||||||
|
|
||||||
|
auto status = wrapper.ValidateAssembly(args[0]);
|
||||||
|
if (status.ok()) {
|
||||||
|
std::cout << "✅ Assembly file is valid: " << args[0] << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "❌ Assembly validation failed:" << std::endl;
|
||||||
|
std::cout << " " << status.message() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status HandleInfoCommand(const std::vector<std::string>& args) {
|
||||||
|
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||||
|
if (!args.empty()) {
|
||||||
|
rom_file = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rom_file.empty()) {
|
||||||
|
return absl::InvalidArgumentError("ROM file required (use --rom=<file> or provide as argument)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Open handler;
|
||||||
|
return handler.Run({rom_file});
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status HandleConvertCommand(const std::vector<std::string>& args) {
|
||||||
|
if (args.empty()) {
|
||||||
|
return absl::InvalidArgumentError("Convert command requires an address");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement address conversion
|
||||||
|
std::cout << "Address conversion not yet implemented" << std::endl;
|
||||||
|
return absl::UnimplementedError("Address conversion functionality");
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status HandleHelpCommand(const std::vector<std::string>& args) {
|
||||||
|
std::string command = args.empty() ? "" : args[0];
|
||||||
|
ShowHelp(command);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cli
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
absl::SetProgramUsageMessage(
|
||||||
|
"z3ed - Yet Another Zelda3 Editor CLI Tool\n"
|
||||||
|
"\n"
|
||||||
|
"A command-line tool for editing The Legend of Zelda: A Link to the Past ROMs.\n"
|
||||||
|
"Supports Asar 65816 assembly patching, BPS patches, and ROM analysis.\n"
|
||||||
|
"\n"
|
||||||
|
"Use --tui to launch the interactive text interface, or run commands directly.\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto args = absl::ParseCommandLine(argc, argv);
|
||||||
|
|
||||||
|
yaze::cli::ModernCLI cli;
|
||||||
|
|
||||||
|
// Handle version flag
|
||||||
|
if (absl::GetFlag(FLAGS_version)) {
|
||||||
|
cli.ShowVersion();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle TUI flag
|
||||||
|
if (absl::GetFlag(FLAGS_tui)) {
|
||||||
|
yaze::cli::ShowMain();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle command line arguments
|
||||||
|
if (args.size() < 2) {
|
||||||
|
cli.ShowHelp();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string command = args[1];
|
||||||
|
std::vector<std::string> command_args(args.begin() + 2, args.end());
|
||||||
|
|
||||||
|
auto status = cli.RunCommand(command, command_args);
|
||||||
|
if (!status.ok()) {
|
||||||
|
std::cerr << "Error: " << status.message() << std::endl;
|
||||||
|
|
||||||
|
if (status.code() == absl::StatusCode::kNotFound) {
|
||||||
|
std::cerr << std::endl;
|
||||||
|
std::cerr << "Available commands:" << std::endl;
|
||||||
|
cli.ShowHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -27,21 +27,93 @@ absl::Status ApplyPatch::Run(const std::vector<std::string>& arg_vec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
absl::Status AsarPatch::Run(const std::vector<std::string>& arg_vec) {
|
absl::Status AsarPatch::Run(const std::vector<std::string>& arg_vec) {
|
||||||
std::string patch_filename = arg_vec[1];
|
if (arg_vec.size() < 2) {
|
||||||
std::string rom_filename = arg_vec[2];
|
return absl::InvalidArgumentError("Usage: asar <patch_file> <rom_file>");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string patch_filename = arg_vec[0];
|
||||||
|
std::string rom_filename = arg_vec[1];
|
||||||
|
|
||||||
|
// Load ROM file
|
||||||
RETURN_IF_ERROR(rom_.LoadFromFile(rom_filename))
|
RETURN_IF_ERROR(rom_.LoadFromFile(rom_filename))
|
||||||
int buflen = rom_.vector().size();
|
|
||||||
int romlen = rom_.vector().size();
|
// Get ROM data
|
||||||
if (!asar_patch(patch_filename.c_str(), rom_filename.data(), buflen,
|
auto rom_data = rom_.vector();
|
||||||
&romlen)) {
|
int buflen = static_cast<int>(rom_data.size());
|
||||||
std::string error_message = "Failed to apply patch: ";
|
int romlen = buflen;
|
||||||
|
|
||||||
|
// Ensure we have enough buffer space
|
||||||
|
const int max_rom_size = asar_maxromsize();
|
||||||
|
if (buflen < max_rom_size) {
|
||||||
|
rom_data.resize(max_rom_size, 0);
|
||||||
|
buflen = max_rom_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply Asar patch
|
||||||
|
if (!asar_patch(patch_filename.c_str(),
|
||||||
|
reinterpret_cast<char*>(rom_data.data()),
|
||||||
|
buflen, &romlen)) {
|
||||||
|
std::string error_message = "Failed to apply Asar patch:\n";
|
||||||
int num_errors = 0;
|
int num_errors = 0;
|
||||||
const errordata* errors = asar_geterrors(&num_errors);
|
const errordata* errors = asar_geterrors(&num_errors);
|
||||||
for (int i = 0; i < num_errors; i++) {
|
for (int i = 0; i < num_errors; i++) {
|
||||||
error_message += absl::StrFormat("%s", errors[i].fullerrdata);
|
error_message += absl::StrFormat(" %s\n", errors[i].fullerrdata);
|
||||||
}
|
}
|
||||||
return absl::InternalError(error_message);
|
return absl::InternalError(error_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resize ROM to actual size
|
||||||
|
rom_data.resize(romlen);
|
||||||
|
|
||||||
|
// Update the ROM data by writing the patched data back
|
||||||
|
for (size_t i = 0; i < rom_data.size(); ++i) {
|
||||||
|
auto status = rom_.WriteByte(i, rom_data[i]);
|
||||||
|
if (!status.ok()) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save patched ROM
|
||||||
|
std::string output_filename = rom_filename;
|
||||||
|
size_t dot_pos = output_filename.find_last_of('.');
|
||||||
|
if (dot_pos != std::string::npos) {
|
||||||
|
output_filename.insert(dot_pos, "_patched");
|
||||||
|
} else {
|
||||||
|
output_filename += "_patched";
|
||||||
|
}
|
||||||
|
|
||||||
|
Rom::SaveSettings settings;
|
||||||
|
settings.filename = output_filename;
|
||||||
|
RETURN_IF_ERROR(rom_.SaveToFile(settings))
|
||||||
|
|
||||||
|
std::cout << "✅ Asar patch applied successfully!" << std::endl;
|
||||||
|
std::cout << "📁 Output: " << output_filename << std::endl;
|
||||||
|
std::cout << "📊 Final ROM size: " << romlen << " bytes" << std::endl;
|
||||||
|
|
||||||
|
// Show warnings if any
|
||||||
|
int num_warnings = 0;
|
||||||
|
const errordata* warnings = asar_getwarnings(&num_warnings);
|
||||||
|
if (num_warnings > 0) {
|
||||||
|
std::cout << "⚠️ Warnings:" << std::endl;
|
||||||
|
for (int i = 0; i < num_warnings; i++) {
|
||||||
|
std::cout << " " << warnings[i].fullerrdata << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show extracted symbols
|
||||||
|
int num_labels = 0;
|
||||||
|
const labeldata* labels = asar_getalllabels(&num_labels);
|
||||||
|
if (num_labels > 0) {
|
||||||
|
std::cout << "🏷️ Extracted " << num_labels << " symbols:" << std::endl;
|
||||||
|
for (int i = 0; i < std::min(10, num_labels); i++) { // Show first 10
|
||||||
|
std::cout << " " << labels[i].name << " @ $"
|
||||||
|
<< std::hex << std::uppercase << labels[i].location << std::endl;
|
||||||
|
}
|
||||||
|
if (num_labels > 10) {
|
||||||
|
std::cout << " ... and " << (num_labels - 10) << " more" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
478
src/cli/tui.cc
478
src/cli/tui.cc
@@ -6,8 +6,11 @@
|
|||||||
#include <ftxui/screen/screen.hpp>
|
#include <ftxui/screen/screen.hpp>
|
||||||
|
|
||||||
#include "absl/strings/str_cat.h"
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_join.h"
|
||||||
#include "util/bps.h"
|
#include "util/bps.h"
|
||||||
#include "app/core/platform/file_dialog.h"
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/core/asar_wrapper.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace cli {
|
namespace cli {
|
||||||
@@ -219,6 +222,308 @@ void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
screen.Loop(renderer);
|
screen.Loop(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
|
||||||
|
static std::string patch_file;
|
||||||
|
static std::string output_message;
|
||||||
|
static std::vector<std::string> symbols_list;
|
||||||
|
static bool show_symbols = false;
|
||||||
|
|
||||||
|
auto patch_file_input = Input(&patch_file, "Assembly patch file (.asm)");
|
||||||
|
|
||||||
|
auto apply_button = Button("Apply Asar Patch", [&] {
|
||||||
|
if (patch_file.empty()) {
|
||||||
|
app_context.error_message = "Please specify an assembly patch file";
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app_context.rom.is_loaded()) {
|
||||||
|
app_context.error_message = "No ROM loaded. Please load a ROM first.";
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
app::core::AsarWrapper wrapper;
|
||||||
|
auto init_status = wrapper.Initialize();
|
||||||
|
if (!init_status.ok()) {
|
||||||
|
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rom_data = app_context.rom.vector();
|
||||||
|
auto patch_result = wrapper.ApplyPatch(patch_file, rom_data);
|
||||||
|
|
||||||
|
if (!patch_result.ok()) {
|
||||||
|
app_context.error_message = absl::StrCat("Patch failed: ", patch_result.status().message());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& result = patch_result.value();
|
||||||
|
if (!result.success) {
|
||||||
|
app_context.error_message = absl::StrCat("Patch failed: ", absl::StrJoin(result.errors, "; "));
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ROM with patched data
|
||||||
|
// Note: ROM update would need proper implementation
|
||||||
|
// For now, just indicate success
|
||||||
|
|
||||||
|
// Prepare success message
|
||||||
|
output_message = absl::StrFormat(
|
||||||
|
"✅ Patch applied successfully!\n"
|
||||||
|
"📊 ROM size: %d bytes\n"
|
||||||
|
"🏷️ Symbols found: %d",
|
||||||
|
result.rom_size, result.symbols.size());
|
||||||
|
|
||||||
|
// Prepare symbols list
|
||||||
|
symbols_list.clear();
|
||||||
|
for (const auto& symbol : result.symbols) {
|
||||||
|
symbols_list.push_back(absl::StrFormat("%-20s @ $%06X",
|
||||||
|
symbol.name, symbol.address));
|
||||||
|
}
|
||||||
|
show_symbols = !symbols_list.empty();
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
app_context.error_message = "Exception: " + std::string(e.what());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto show_symbols_button = Button("Show Symbols", [&] {
|
||||||
|
show_symbols = !show_symbols;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto back_button = Button("Back to Main Menu", [&] {
|
||||||
|
output_message.clear();
|
||||||
|
symbols_list.clear();
|
||||||
|
show_symbols = false;
|
||||||
|
SwitchComponents(screen, LayoutID::kMainMenu);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<Component> container_items = {
|
||||||
|
patch_file_input,
|
||||||
|
apply_button,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!output_message.empty()) {
|
||||||
|
container_items.push_back(show_symbols_button);
|
||||||
|
}
|
||||||
|
container_items.push_back(back_button);
|
||||||
|
|
||||||
|
auto container = Container::Vertical(container_items);
|
||||||
|
|
||||||
|
auto renderer = Renderer(container, [&] {
|
||||||
|
std::vector<Element> elements = {
|
||||||
|
text("Apply Asar Assembly Patch") | center | bold,
|
||||||
|
separator(),
|
||||||
|
text("Assembly Patch File:"),
|
||||||
|
patch_file_input->Render(),
|
||||||
|
separator(),
|
||||||
|
apply_button->Render() | center,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!output_message.empty()) {
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(text(output_message) | color(Color::Green));
|
||||||
|
elements.push_back(show_symbols_button->Render() | center);
|
||||||
|
|
||||||
|
if (show_symbols && !symbols_list.empty()) {
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(text("Extracted Symbols:") | bold);
|
||||||
|
|
||||||
|
// Show symbols in a scrollable area
|
||||||
|
std::vector<Element> symbol_elements;
|
||||||
|
for (size_t i = 0; i < std::min(symbols_list.size(), size_t(10)); ++i) {
|
||||||
|
symbol_elements.push_back(text(symbols_list[i]) | color(Color::Cyan));
|
||||||
|
}
|
||||||
|
if (symbols_list.size() > 10) {
|
||||||
|
symbol_elements.push_back(text(absl::StrFormat("... and %d more",
|
||||||
|
symbols_list.size() - 10)) |
|
||||||
|
color(Color::Yellow));
|
||||||
|
}
|
||||||
|
elements.push_back(vbox(symbol_elements) | frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(back_button->Render() | center);
|
||||||
|
|
||||||
|
return vbox(elements) | center | border;
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.Loop(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtractSymbolsComponent(ftxui::ScreenInteractive &screen) {
|
||||||
|
static std::string asm_file;
|
||||||
|
static std::vector<std::string> symbols_list;
|
||||||
|
static std::string output_message;
|
||||||
|
|
||||||
|
auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
|
||||||
|
|
||||||
|
auto extract_button = Button("Extract Symbols", [&] {
|
||||||
|
if (asm_file.empty()) {
|
||||||
|
app_context.error_message = "Please specify an assembly file";
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
app::core::AsarWrapper wrapper;
|
||||||
|
auto init_status = wrapper.Initialize();
|
||||||
|
if (!init_status.ok()) {
|
||||||
|
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto symbols_result = wrapper.ExtractSymbols(asm_file);
|
||||||
|
if (!symbols_result.ok()) {
|
||||||
|
app_context.error_message = absl::StrCat("Symbol extraction failed: ", symbols_result.status().message());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& symbols = symbols_result.value();
|
||||||
|
output_message = absl::StrFormat("✅ Extracted %d symbols from %s",
|
||||||
|
symbols.size(), asm_file);
|
||||||
|
|
||||||
|
symbols_list.clear();
|
||||||
|
for (const auto& symbol : symbols) {
|
||||||
|
symbols_list.push_back(absl::StrFormat("%-20s @ $%06X",
|
||||||
|
symbol.name, symbol.address));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
app_context.error_message = "Exception: " + std::string(e.what());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto back_button = Button("Back to Main Menu", [&] {
|
||||||
|
output_message.clear();
|
||||||
|
symbols_list.clear();
|
||||||
|
SwitchComponents(screen, LayoutID::kMainMenu);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto container = Container::Vertical({
|
||||||
|
asm_file_input,
|
||||||
|
extract_button,
|
||||||
|
back_button,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto renderer = Renderer(container, [&] {
|
||||||
|
std::vector<Element> elements = {
|
||||||
|
text("Extract Assembly Symbols") | center | bold,
|
||||||
|
separator(),
|
||||||
|
text("Assembly File:"),
|
||||||
|
asm_file_input->Render(),
|
||||||
|
separator(),
|
||||||
|
extract_button->Render() | center,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!output_message.empty()) {
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(text(output_message) | color(Color::Green));
|
||||||
|
|
||||||
|
if (!symbols_list.empty()) {
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(text("Symbols:") | bold);
|
||||||
|
|
||||||
|
std::vector<Element> symbol_elements;
|
||||||
|
for (const auto& symbol : symbols_list) {
|
||||||
|
symbol_elements.push_back(text(symbol) | color(Color::Cyan));
|
||||||
|
}
|
||||||
|
elements.push_back(vbox(symbol_elements) | frame | size(HEIGHT, LESS_THAN, 15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(back_button->Render() | center);
|
||||||
|
|
||||||
|
return vbox(elements) | center | border;
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.Loop(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ValidateAssemblyComponent(ftxui::ScreenInteractive &screen) {
|
||||||
|
static std::string asm_file;
|
||||||
|
static std::string output_message;
|
||||||
|
static Color output_color = Color::White;
|
||||||
|
|
||||||
|
auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
|
||||||
|
|
||||||
|
auto validate_button = Button("Validate Assembly", [&] {
|
||||||
|
if (asm_file.empty()) {
|
||||||
|
app_context.error_message = "Please specify an assembly file";
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
app::core::AsarWrapper wrapper;
|
||||||
|
auto init_status = wrapper.Initialize();
|
||||||
|
if (!init_status.ok()) {
|
||||||
|
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto validation_status = wrapper.ValidateAssembly(asm_file);
|
||||||
|
if (validation_status.ok()) {
|
||||||
|
output_message = "✅ Assembly file is valid!";
|
||||||
|
output_color = Color::Green;
|
||||||
|
} else {
|
||||||
|
output_message = absl::StrCat("❌ Validation failed:\n", validation_status.message());
|
||||||
|
output_color = Color::Red;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
app_context.error_message = "Exception: " + std::string(e.what());
|
||||||
|
SwitchComponents(screen, LayoutID::kError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto back_button = Button("Back to Main Menu", [&] {
|
||||||
|
output_message.clear();
|
||||||
|
SwitchComponents(screen, LayoutID::kMainMenu);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto container = Container::Vertical({
|
||||||
|
asm_file_input,
|
||||||
|
validate_button,
|
||||||
|
back_button,
|
||||||
|
});
|
||||||
|
|
||||||
|
auto renderer = Renderer(container, [&] {
|
||||||
|
std::vector<Element> elements = {
|
||||||
|
text("Validate Assembly File") | center | bold,
|
||||||
|
separator(),
|
||||||
|
text("Assembly File:"),
|
||||||
|
asm_file_input->Render(),
|
||||||
|
separator(),
|
||||||
|
validate_button->Render() | center,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!output_message.empty()) {
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(text(output_message) | color(output_color));
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.push_back(separator());
|
||||||
|
elements.push_back(back_button->Render() | center);
|
||||||
|
|
||||||
|
return vbox(elements) | center | border;
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.Loop(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
void LoadRomComponent(ftxui::ScreenInteractive &screen) {
|
void LoadRomComponent(ftxui::ScreenInteractive &screen) {
|
||||||
static std::string rom_file;
|
static std::string rom_file;
|
||||||
auto rom_file_input = Input(&rom_file, "ROM file path");
|
auto rom_file_input = Input(&rom_file, "ROM file path");
|
||||||
@@ -235,24 +540,36 @@ void LoadRomComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
SwitchComponents(screen, LayoutID::kMainMenu);
|
SwitchComponents(screen, LayoutID::kMainMenu);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auto browse_button = Button("Browse...", [&] {
|
||||||
|
// TODO: Implement file dialog
|
||||||
|
// For now, show a placeholder
|
||||||
|
rom_file = "/path/to/your/rom.sfc";
|
||||||
|
});
|
||||||
|
|
||||||
auto back_button =
|
auto back_button =
|
||||||
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
|
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
|
||||||
|
|
||||||
auto container = Container::Vertical({
|
auto container = Container::Vertical({
|
||||||
rom_file_input,
|
Container::Horizontal({rom_file_input, browse_button}),
|
||||||
load_button,
|
load_button,
|
||||||
back_button,
|
back_button,
|
||||||
});
|
});
|
||||||
|
|
||||||
auto renderer = Renderer(container, [&] {
|
auto renderer = Renderer(container, [&] {
|
||||||
return vbox({text("Load ROM") | center, separator(),
|
return vbox({
|
||||||
text("Enter ROM File:"), rom_file_input->Render(), separator(),
|
text("Load ROM") | center | bold,
|
||||||
hbox({
|
separator(),
|
||||||
load_button->Render() | center,
|
text("Enter ROM File Path:"),
|
||||||
separator(),
|
hbox({
|
||||||
back_button->Render() | center,
|
rom_file_input->Render() | flex,
|
||||||
}) | center}) |
|
separator(),
|
||||||
center;
|
browse_button->Render(),
|
||||||
|
}),
|
||||||
|
separator(),
|
||||||
|
load_button->Render() | center,
|
||||||
|
separator(),
|
||||||
|
back_button->Render() | center,
|
||||||
|
}) | center | border;
|
||||||
});
|
});
|
||||||
|
|
||||||
screen.Loop(renderer);
|
screen.Loop(renderer);
|
||||||
@@ -387,92 +704,126 @@ void PaletteEditorComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
|
|
||||||
void HelpComponent(ftxui::ScreenInteractive &screen) {
|
void HelpComponent(ftxui::ScreenInteractive &screen) {
|
||||||
auto help_text = vbox({
|
auto help_text = vbox({
|
||||||
text("z3ed") | bold | color(Color::Yellow),
|
text("z3ed v0.3.0") | bold | color(Color::Yellow),
|
||||||
text("by scawful") | color(Color::Magenta),
|
text("by scawful") | color(Color::Magenta),
|
||||||
text("The Legend of Zelda: A Link to the Past Hacking Tool") |
|
text("The Legend of Zelda: A Link to the Past Hacking Tool") |
|
||||||
color(Color::Red),
|
color(Color::Red),
|
||||||
|
text("Now with Asar 65816 Assembler Integration!") |
|
||||||
|
color(Color::Green),
|
||||||
|
separator(),
|
||||||
|
|
||||||
|
text("🎯 ASAR COMMANDS") | bold | color(Color::Cyan),
|
||||||
separator(),
|
separator(),
|
||||||
hbox({
|
hbox({
|
||||||
text("Command") | bold | underlined,
|
text("Apply Asar Patch"),
|
||||||
filler(),
|
filler(),
|
||||||
text("Arg") | bold | underlined,
|
text("asar"),
|
||||||
filler(),
|
filler(),
|
||||||
text("Params") | bold | underlined,
|
text("<patch.asm> [--rom=<file>]"),
|
||||||
}),
|
}),
|
||||||
|
hbox({
|
||||||
|
text("Extract Symbols"),
|
||||||
|
filler(),
|
||||||
|
text("extract"),
|
||||||
|
filler(),
|
||||||
|
text("<patch.asm>"),
|
||||||
|
}),
|
||||||
|
hbox({
|
||||||
|
text("Validate Assembly"),
|
||||||
|
filler(),
|
||||||
|
text("validate"),
|
||||||
|
filler(),
|
||||||
|
text("<patch.asm>"),
|
||||||
|
}),
|
||||||
|
|
||||||
|
separator(),
|
||||||
|
text("📦 PATCH COMMANDS") | bold | color(Color::Blue),
|
||||||
separator(),
|
separator(),
|
||||||
hbox({
|
hbox({
|
||||||
text("Apply BPS Patch"),
|
text("Apply BPS Patch"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-a"),
|
text("patch"),
|
||||||
filler(),
|
filler(),
|
||||||
text("<rom_file> <bps_file>"),
|
text("<patch.bps> [--rom=<file>]"),
|
||||||
}),
|
}),
|
||||||
hbox({
|
hbox({
|
||||||
text("Create BPS Patch"),
|
text("Create BPS Patch"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-c"),
|
text("create"),
|
||||||
filler(),
|
filler(),
|
||||||
text("<bps_file> <src_file> <modified_file>"),
|
text("<src_file> <modified_file>"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
separator(),
|
||||||
|
text("🗃️ ROM COMMANDS") | bold | color(Color::Yellow),
|
||||||
separator(),
|
separator(),
|
||||||
hbox({
|
hbox({
|
||||||
text("Open ROM"),
|
text("Show ROM Info"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-o"),
|
text("info"),
|
||||||
filler(),
|
filler(),
|
||||||
text("<rom_file>"),
|
text("[--rom=<file>]"),
|
||||||
}),
|
}),
|
||||||
hbox({
|
hbox({
|
||||||
text("Backup ROM"),
|
text("Backup ROM"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-b"),
|
text("backup"),
|
||||||
filler(),
|
filler(),
|
||||||
text("<rom_file> <optional:new_file>"),
|
text("<rom_file> [backup_name]"),
|
||||||
}),
|
}),
|
||||||
hbox({
|
hbox({
|
||||||
text("Expand ROM"),
|
text("Expand ROM"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-x"),
|
text("expand"),
|
||||||
filler(),
|
filler(),
|
||||||
text("<rom_file> <file_size>"),
|
text("<rom_file> <size>"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
separator(),
|
separator(),
|
||||||
|
text("🔧 UTILITY COMMANDS") | bold | color(Color::Magenta),
|
||||||
|
separator(),
|
||||||
|
hbox({
|
||||||
|
text("Address Conversion"),
|
||||||
|
filler(),
|
||||||
|
text("convert"),
|
||||||
|
filler(),
|
||||||
|
text("<address> [--to-pc|--to-snes]"),
|
||||||
|
}),
|
||||||
hbox({
|
hbox({
|
||||||
text("Transfer Tile16"),
|
text("Transfer Tile16"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-t"),
|
text("tile16"),
|
||||||
filler(),
|
filler(),
|
||||||
text("<src_rom> <dest_rom> <tile32_id_list:csv>"),
|
text("<src> <dest> <tiles>"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
separator(),
|
||||||
|
text("🌐 GLOBAL FLAGS") | bold | color(Color::White),
|
||||||
separator(),
|
separator(),
|
||||||
hbox({
|
hbox({
|
||||||
text("Export Graphics"),
|
text("--tui"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-e"),
|
text("Launch Text User Interface"),
|
||||||
filler(),
|
|
||||||
text("<rom_file> <bin_file>"),
|
|
||||||
}),
|
}),
|
||||||
hbox({
|
hbox({
|
||||||
text("Import Graphics"),
|
text("--rom=<file>"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-i"),
|
text("Specify ROM file"),
|
||||||
filler(),
|
|
||||||
text("<bin_file> <rom_file>"),
|
|
||||||
}),
|
|
||||||
separator(),
|
|
||||||
hbox({
|
|
||||||
text("SNES to PC Address"),
|
|
||||||
filler(),
|
|
||||||
text("-s"),
|
|
||||||
filler(),
|
|
||||||
text("<address>"),
|
|
||||||
}),
|
}),
|
||||||
hbox({
|
hbox({
|
||||||
text("PC to SNES Address"),
|
text("--output=<file>"),
|
||||||
filler(),
|
filler(),
|
||||||
text("-p"),
|
text("Specify output file"),
|
||||||
|
}),
|
||||||
|
hbox({
|
||||||
|
text("--verbose"),
|
||||||
filler(),
|
filler(),
|
||||||
text("<address>"),
|
text("Enable verbose output"),
|
||||||
|
}),
|
||||||
|
hbox({
|
||||||
|
text("--dry-run"),
|
||||||
|
filler(),
|
||||||
|
text("Test without changes"),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -515,7 +866,7 @@ void MainMenuComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
auto title = border(hbox({
|
auto title = border(hbox({
|
||||||
text("z3ed") | bold | color(Color::Blue1),
|
text("z3ed") | bold | color(Color::Blue1),
|
||||||
separator(),
|
separator(),
|
||||||
text("v0.1.0") | bold | color(Color::Green1),
|
text("v0.3.0") | bold | color(Color::Green1),
|
||||||
separator(),
|
separator(),
|
||||||
text(rom_information) | bold | color(Color::Red1),
|
text(rom_information) | bold | color(Color::Red1),
|
||||||
}));
|
}));
|
||||||
@@ -533,15 +884,24 @@ void MainMenuComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
auto main_component = CatchEvent(renderer, [&](Event event) {
|
auto main_component = CatchEvent(renderer, [&](Event event) {
|
||||||
if (event == Event::Return) {
|
if (event == Event::Return) {
|
||||||
switch ((MainMenuEntry)selected) {
|
switch ((MainMenuEntry)selected) {
|
||||||
|
case MainMenuEntry::kLoadRom:
|
||||||
|
SwitchComponents(screen, LayoutID::kLoadRom);
|
||||||
|
return true;
|
||||||
|
case MainMenuEntry::kApplyAsarPatch:
|
||||||
|
SwitchComponents(screen, LayoutID::kApplyAsarPatch);
|
||||||
|
return true;
|
||||||
case MainMenuEntry::kApplyBpsPatch:
|
case MainMenuEntry::kApplyBpsPatch:
|
||||||
SwitchComponents(screen, LayoutID::kApplyBpsPatch);
|
SwitchComponents(screen, LayoutID::kApplyBpsPatch);
|
||||||
return true;
|
return true;
|
||||||
|
case MainMenuEntry::kExtractSymbols:
|
||||||
|
SwitchComponents(screen, LayoutID::kExtractSymbols);
|
||||||
|
return true;
|
||||||
|
case MainMenuEntry::kValidateAssembly:
|
||||||
|
SwitchComponents(screen, LayoutID::kValidateAssembly);
|
||||||
|
return true;
|
||||||
case MainMenuEntry::kGenerateSaveFile:
|
case MainMenuEntry::kGenerateSaveFile:
|
||||||
SwitchComponents(screen, LayoutID::kGenerateSaveFile);
|
SwitchComponents(screen, LayoutID::kGenerateSaveFile);
|
||||||
return true;
|
return true;
|
||||||
case MainMenuEntry::kLoadRom:
|
|
||||||
SwitchComponents(screen, LayoutID::kLoadRom);
|
|
||||||
return true;
|
|
||||||
case MainMenuEntry::kPaletteEditor:
|
case MainMenuEntry::kPaletteEditor:
|
||||||
SwitchComponents(screen, LayoutID::kPaletteEditor);
|
SwitchComponents(screen, LayoutID::kPaletteEditor);
|
||||||
return true;
|
return true;
|
||||||
@@ -576,9 +936,18 @@ void ShowMain() {
|
|||||||
case LayoutID::kLoadRom: {
|
case LayoutID::kLoadRom: {
|
||||||
LoadRomComponent(screen);
|
LoadRomComponent(screen);
|
||||||
} break;
|
} break;
|
||||||
|
case LayoutID::kApplyAsarPatch: {
|
||||||
|
ApplyAsarPatchComponent(screen);
|
||||||
|
} break;
|
||||||
case LayoutID::kApplyBpsPatch: {
|
case LayoutID::kApplyBpsPatch: {
|
||||||
ApplyBpsPatchComponent(screen);
|
ApplyBpsPatchComponent(screen);
|
||||||
} break;
|
} break;
|
||||||
|
case LayoutID::kExtractSymbols: {
|
||||||
|
ExtractSymbolsComponent(screen);
|
||||||
|
} break;
|
||||||
|
case LayoutID::kValidateAssembly: {
|
||||||
|
ValidateAssemblyComponent(screen);
|
||||||
|
} break;
|
||||||
case LayoutID::kGenerateSaveFile: {
|
case LayoutID::kGenerateSaveFile: {
|
||||||
GenerateSaveFileComponent(screen);
|
GenerateSaveFileComponent(screen);
|
||||||
} break;
|
} break;
|
||||||
@@ -596,10 +965,13 @@ void ShowMain() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
auto error_renderer = Renderer(error_button, [&] {
|
auto error_renderer = Renderer(error_button, [&] {
|
||||||
return vbox({text("Error") | center, separator(),
|
return vbox({
|
||||||
text(app_context.error_message), separator(),
|
text("Error") | center | bold | color(Color::Red),
|
||||||
error_button->Render() | center}) |
|
separator(),
|
||||||
center;
|
text(app_context.error_message) | color(Color::Yellow),
|
||||||
|
separator(),
|
||||||
|
error_button->Render() | center
|
||||||
|
}) | center | border;
|
||||||
});
|
});
|
||||||
|
|
||||||
screen.Loop(error_renderer);
|
screen.Loop(error_renderer);
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ namespace yaze {
|
|||||||
namespace cli {
|
namespace cli {
|
||||||
const std::vector<std::string> kMainMenuEntries = {
|
const std::vector<std::string> kMainMenuEntries = {
|
||||||
"Load ROM",
|
"Load ROM",
|
||||||
|
"Apply Asar Patch",
|
||||||
"Apply BPS Patch",
|
"Apply BPS Patch",
|
||||||
|
"Extract Symbols",
|
||||||
|
"Validate Assembly",
|
||||||
"Generate Save File",
|
"Generate Save File",
|
||||||
"Palette Editor",
|
"Palette Editor",
|
||||||
"Help",
|
"Help",
|
||||||
@@ -26,7 +29,10 @@ const std::vector<std::string> kMainMenuEntries = {
|
|||||||
|
|
||||||
enum class MainMenuEntry {
|
enum class MainMenuEntry {
|
||||||
kLoadRom,
|
kLoadRom,
|
||||||
|
kApplyAsarPatch,
|
||||||
kApplyBpsPatch,
|
kApplyBpsPatch,
|
||||||
|
kExtractSymbols,
|
||||||
|
kValidateAssembly,
|
||||||
kGenerateSaveFile,
|
kGenerateSaveFile,
|
||||||
kPaletteEditor,
|
kPaletteEditor,
|
||||||
kHelp,
|
kHelp,
|
||||||
@@ -35,7 +41,10 @@ enum class MainMenuEntry {
|
|||||||
|
|
||||||
enum LayoutID {
|
enum LayoutID {
|
||||||
kLoadRom,
|
kLoadRom,
|
||||||
|
kApplyAsarPatch,
|
||||||
kApplyBpsPatch,
|
kApplyBpsPatch,
|
||||||
|
kExtractSymbols,
|
||||||
|
kValidateAssembly,
|
||||||
kGenerateSaveFile,
|
kGenerateSaveFile,
|
||||||
kPaletteEditor,
|
kPaletteEditor,
|
||||||
kHelp,
|
kHelp,
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ endif()
|
|||||||
|
|
||||||
add_executable(
|
add_executable(
|
||||||
z3ed
|
z3ed
|
||||||
cli/z3ed.cc
|
cli/cli_main.cc
|
||||||
cli/tui.cc
|
cli/tui.cc
|
||||||
cli/handlers/compress.cc
|
cli/handlers/compress.cc
|
||||||
cli/handlers/patch.cc
|
cli/handlers/patch.cc
|
||||||
cli/handlers/tile16_transfer.cc
|
cli/handlers/tile16_transfer.cc
|
||||||
app/rom.cc
|
app/rom.cc
|
||||||
app/core/project.cc
|
app/core/project.cc
|
||||||
|
app/core/asar_wrapper.cc
|
||||||
app/core/platform/file_dialog.mm
|
app/core/platform/file_dialog.mm
|
||||||
app/core/platform/file_dialog.cc
|
app/core/platform/file_dialog.cc
|
||||||
${YAZE_APP_EMU_SRC}
|
${YAZE_APP_EMU_SRC}
|
||||||
@@ -28,14 +29,13 @@ add_executable(
|
|||||||
${YAZE_UTIL_SRC}
|
${YAZE_UTIL_SRC}
|
||||||
${YAZE_GUI_SRC}
|
${YAZE_GUI_SRC}
|
||||||
${IMGUI_SRC}
|
${IMGUI_SRC}
|
||||||
${ASAR_STATIC_SRC}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
z3ed PUBLIC
|
z3ed PUBLIC
|
||||||
lib/
|
lib/
|
||||||
app/
|
app/
|
||||||
${ASAR_INCLUDE_DIR}
|
${ASAR_INCLUDE_DIRS}
|
||||||
${CMAKE_SOURCE_DIR}/incl/
|
${CMAKE_SOURCE_DIR}/incl/
|
||||||
${CMAKE_SOURCE_DIR}/src/
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
${PNG_INCLUDE_DIRS}
|
${PNG_INCLUDE_DIRS}
|
||||||
@@ -50,6 +50,8 @@ target_link_libraries(
|
|||||||
ftxui::component
|
ftxui::component
|
||||||
ftxui::screen
|
ftxui::screen
|
||||||
ftxui::dom
|
ftxui::dom
|
||||||
|
absl::flags
|
||||||
|
absl::flags_parse
|
||||||
${ABSL_TARGETS}
|
${ABSL_TARGETS}
|
||||||
${SDL_TARGETS}
|
${SDL_TARGETS}
|
||||||
${PNG_LIBRARIES}
|
${PNG_LIBRARIES}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ add_executable(
|
|||||||
rom_test.cc
|
rom_test.cc
|
||||||
test_editor.cc
|
test_editor.cc
|
||||||
hex_test.cc
|
hex_test.cc
|
||||||
|
core/asar_wrapper_test.cc
|
||||||
gfx/snes_tile_test.cc
|
gfx/snes_tile_test.cc
|
||||||
gfx/compression_test.cc
|
gfx/compression_test.cc
|
||||||
gfx/snes_palette_test.cc
|
gfx/snes_palette_test.cc
|
||||||
@@ -37,6 +38,8 @@ add_executable(
|
|||||||
emu/audio/apu_test.cc
|
emu/audio/apu_test.cc
|
||||||
emu/audio/ipl_handshake_test.cc
|
emu/audio/ipl_handshake_test.cc
|
||||||
integration/dungeon_editor_test.cc
|
integration/dungeon_editor_test.cc
|
||||||
|
integration/asar_integration_test.cc
|
||||||
|
integration/asar_rom_test.cc
|
||||||
zelda3/object_parser_test.cc
|
zelda3/object_parser_test.cc
|
||||||
zelda3/object_parser_structs_test.cc
|
zelda3/object_parser_structs_test.cc
|
||||||
zelda3/test_dungeon_objects.cc
|
zelda3/test_dungeon_objects.cc
|
||||||
@@ -82,9 +85,10 @@ target_include_directories(
|
|||||||
app/
|
app/
|
||||||
lib/
|
lib/
|
||||||
${CMAKE_SOURCE_DIR}/incl/
|
${CMAKE_SOURCE_DIR}/incl/
|
||||||
${CMAKE_SOURCE_DIR}
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
|
${CMAKE_SOURCE_DIR}/test/
|
||||||
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
|
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
|
||||||
${ASAR_INCLUDE_DIR}
|
${ASAR_INCLUDE_DIRS}
|
||||||
${SDL2_INCLUDE_DIR}
|
${SDL2_INCLUDE_DIR}
|
||||||
${PNG_INCLUDE_DIRS}
|
${PNG_INCLUDE_DIRS}
|
||||||
${PROJECT_BINARY_DIR}
|
${PROJECT_BINARY_DIR}
|
||||||
@@ -106,9 +110,51 @@ target_link_libraries(
|
|||||||
gtest_main
|
gtest_main
|
||||||
gtest
|
gtest
|
||||||
)
|
)
|
||||||
target_compile_definitions(yaze_test PRIVATE "linux")
|
# ROM Testing Configuration
|
||||||
target_compile_definitions(yaze_test PRIVATE "stricmp=strcasecmp")
|
if(YAZE_ENABLE_ROM_TESTS)
|
||||||
|
target_compile_definitions(yaze_test PRIVATE
|
||||||
|
YAZE_ENABLE_ROM_TESTS=1
|
||||||
|
YAZE_TEST_ROM_PATH="${YAZE_TEST_ROM_PATH}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_compile_definitions(yaze_test PRIVATE "IMGUI_ENABLE_TEST_ENGINE")
|
target_compile_definitions(yaze_test PRIVATE "IMGUI_ENABLE_TEST_ENGINE")
|
||||||
|
|
||||||
|
# Platform-specific definitions
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
target_compile_definitions(yaze_test PRIVATE "linux" "stricmp=strcasecmp")
|
||||||
|
elseif(APPLE)
|
||||||
|
target_compile_definitions(yaze_test PRIVATE "MACOS" "stricmp=strcasecmp")
|
||||||
|
elseif(WIN32)
|
||||||
|
target_compile_definitions(yaze_test PRIVATE "WINDOWS")
|
||||||
|
endif()
|
||||||
|
|
||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
gtest_discover_tests(yaze_test)
|
|
||||||
|
# Configure test discovery with labels
|
||||||
|
gtest_discover_tests(yaze_test
|
||||||
|
PROPERTIES
|
||||||
|
LABELS "UNIT_TEST"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add labels for ROM-dependent tests
|
||||||
|
if(YAZE_ENABLE_ROM_TESTS)
|
||||||
|
gtest_discover_tests(yaze_test
|
||||||
|
TEST_FILTER "*AsarRomIntegrationTest*"
|
||||||
|
PROPERTIES
|
||||||
|
LABELS "ROM_DEPENDENT;INTEGRATION_TEST"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Add labels for other integration tests
|
||||||
|
gtest_discover_tests(yaze_test
|
||||||
|
TEST_FILTER "*AsarIntegrationTest*"
|
||||||
|
PROPERTIES
|
||||||
|
LABELS "INTEGRATION_TEST"
|
||||||
|
)
|
||||||
|
|
||||||
|
gtest_discover_tests(yaze_test
|
||||||
|
TEST_FILTER "*AsarWrapperTest*"
|
||||||
|
PROPERTIES
|
||||||
|
LABELS "UNIT_TEST"
|
||||||
|
)
|
||||||
82
test/assets/test_patch.asm
Normal file
82
test/assets/test_patch.asm
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
; Yaze Test Patch for Zelda3 ROM
|
||||||
|
; This patch demonstrates Asar integration with a real ROM
|
||||||
|
|
||||||
|
; Test constants
|
||||||
|
!test_ram_addr = $7E2000
|
||||||
|
|
||||||
|
; Simple code modification
|
||||||
|
org $008000
|
||||||
|
yaze_test_hook:
|
||||||
|
; Save original context
|
||||||
|
pha
|
||||||
|
phx
|
||||||
|
phy
|
||||||
|
|
||||||
|
; Set up processor state
|
||||||
|
rep #$30 ; 16-bit A and X/Y
|
||||||
|
|
||||||
|
; Write test signature to RAM
|
||||||
|
lda #$CAFE
|
||||||
|
sta !test_ram_addr
|
||||||
|
lda #$BEEF
|
||||||
|
sta !test_ram_addr+2
|
||||||
|
lda #$DEAD
|
||||||
|
sta !test_ram_addr+4
|
||||||
|
lda #$BABE
|
||||||
|
sta !test_ram_addr+6
|
||||||
|
|
||||||
|
; Call test subroutine
|
||||||
|
jsr yaze_test_function
|
||||||
|
|
||||||
|
; Restore context
|
||||||
|
ply
|
||||||
|
plx
|
||||||
|
pla
|
||||||
|
|
||||||
|
; Continue with original code
|
||||||
|
; (In a real patch, this would jump to the original code)
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Test function to verify Asar compilation
|
||||||
|
yaze_test_function:
|
||||||
|
pha
|
||||||
|
|
||||||
|
; Test arithmetic operations
|
||||||
|
lda #$1000
|
||||||
|
clc
|
||||||
|
adc #$0234
|
||||||
|
sta !test_ram_addr+8
|
||||||
|
|
||||||
|
; Test conditional logic
|
||||||
|
cmp #$1234
|
||||||
|
bne +
|
||||||
|
lda #$600D
|
||||||
|
sta !test_ram_addr+10
|
||||||
|
+
|
||||||
|
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Test data section
|
||||||
|
yaze_test_data:
|
||||||
|
db "YAZE", $00
|
||||||
|
dw $1234, $5678, $9ABC, $DEF0
|
||||||
|
|
||||||
|
yaze_test_string:
|
||||||
|
db "ASAR INTEGRATION TEST", $00
|
||||||
|
|
||||||
|
; Test lookup table
|
||||||
|
yaze_test_table:
|
||||||
|
dw yaze_test_hook
|
||||||
|
dw yaze_test_function
|
||||||
|
dw yaze_test_data
|
||||||
|
dw yaze_test_string
|
||||||
|
|
||||||
|
; More advanced test - interrupt vector modification
|
||||||
|
; org $00FFE4
|
||||||
|
; dw yaze_test_hook ; COP vector (for testing)
|
||||||
|
|
||||||
|
print "Yaze Asar integration test patch compiled successfully!"
|
||||||
|
print "Test hook at: ", hex(yaze_test_hook)
|
||||||
|
print "Test function at: ", hex(yaze_test_function)
|
||||||
|
print "Test data at: ", hex(yaze_test_data)
|
||||||
322
test/core/asar_wrapper_test.cc
Normal file
322
test/core/asar_wrapper_test.cc
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
#include "app/core/asar_wrapper.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class AsarWrapperTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
wrapper_ = std::make_unique<AsarWrapper>();
|
||||||
|
CreateTestFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
CleanupTestFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateTestFiles() {
|
||||||
|
// Create test directory
|
||||||
|
test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_test";
|
||||||
|
std::filesystem::create_directories(test_dir_);
|
||||||
|
|
||||||
|
// Create a simple test assembly file
|
||||||
|
test_asm_path_ = test_dir_ / "test_patch.asm";
|
||||||
|
std::ofstream asm_file(test_asm_path_);
|
||||||
|
asm_file << R"(
|
||||||
|
; Test assembly patch for yaze
|
||||||
|
org $008000
|
||||||
|
testlabel:
|
||||||
|
LDA #$42
|
||||||
|
STA $7E0000
|
||||||
|
RTS
|
||||||
|
|
||||||
|
anotherlabel:
|
||||||
|
LDA #$FF
|
||||||
|
STA $7E0001
|
||||||
|
RTL
|
||||||
|
)";
|
||||||
|
asm_file.close();
|
||||||
|
|
||||||
|
// Create invalid assembly file for error testing
|
||||||
|
invalid_asm_path_ = test_dir_ / "invalid_patch.asm";
|
||||||
|
std::ofstream invalid_file(invalid_asm_path_);
|
||||||
|
invalid_file << R"(
|
||||||
|
; Invalid assembly that should cause errors
|
||||||
|
org $008000
|
||||||
|
invalid_instruction_here
|
||||||
|
LDA unknown_operand
|
||||||
|
)";
|
||||||
|
invalid_file.close();
|
||||||
|
|
||||||
|
// Create test ROM data using utility
|
||||||
|
test_rom_ = yaze::test::TestRomManager::CreateMinimalTestRom(1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanupTestFiles() {
|
||||||
|
try {
|
||||||
|
if (std::filesystem::exists(test_dir_)) {
|
||||||
|
std::filesystem::remove_all(test_dir_);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// Ignore cleanup errors in tests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AsarWrapper> wrapper_;
|
||||||
|
std::filesystem::path test_dir_;
|
||||||
|
std::filesystem::path test_asm_path_;
|
||||||
|
std::filesystem::path invalid_asm_path_;
|
||||||
|
std::vector<uint8_t> test_rom_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, InitializationAndShutdown) {
|
||||||
|
// Test initialization
|
||||||
|
ASSERT_FALSE(wrapper_->IsInitialized());
|
||||||
|
|
||||||
|
auto status = wrapper_->Initialize();
|
||||||
|
EXPECT_TRUE(status.ok()) << status.message();
|
||||||
|
EXPECT_TRUE(wrapper_->IsInitialized());
|
||||||
|
|
||||||
|
// Test version info
|
||||||
|
std::string version = wrapper_->GetVersion();
|
||||||
|
EXPECT_FALSE(version.empty());
|
||||||
|
EXPECT_NE(version, "Not initialized");
|
||||||
|
|
||||||
|
int api_version = wrapper_->GetApiVersion();
|
||||||
|
EXPECT_GT(api_version, 0);
|
||||||
|
|
||||||
|
// Test shutdown
|
||||||
|
wrapper_->Shutdown();
|
||||||
|
EXPECT_FALSE(wrapper_->IsInitialized());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, DoubleInitialization) {
|
||||||
|
auto status1 = wrapper_->Initialize();
|
||||||
|
EXPECT_TRUE(status1.ok());
|
||||||
|
|
||||||
|
auto status2 = wrapper_->Initialize();
|
||||||
|
EXPECT_TRUE(status2.ok()); // Should not fail on double init
|
||||||
|
EXPECT_TRUE(wrapper_->IsInitialized());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, OperationsWithoutInitialization) {
|
||||||
|
// Operations should fail when not initialized
|
||||||
|
ASSERT_FALSE(wrapper_->IsInitialized());
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(test_asm_path_.string(), rom_copy);
|
||||||
|
EXPECT_FALSE(patch_result.ok());
|
||||||
|
EXPECT_THAT(patch_result.status().message(),
|
||||||
|
testing::HasSubstr("not initialized"));
|
||||||
|
|
||||||
|
auto symbols_result = wrapper_->ExtractSymbols(test_asm_path_.string());
|
||||||
|
EXPECT_FALSE(symbols_result.ok());
|
||||||
|
EXPECT_THAT(symbols_result.status().message(),
|
||||||
|
testing::HasSubstr("not initialized"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, ValidPatchApplication) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
size_t original_size = rom_copy.size();
|
||||||
|
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(test_asm_path_.string(), rom_copy);
|
||||||
|
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||||
|
|
||||||
|
const auto& result = patch_result.value();
|
||||||
|
EXPECT_TRUE(result.success) << "Patch failed: "
|
||||||
|
<< testing::PrintToString(result.errors);
|
||||||
|
EXPECT_GT(result.rom_size, 0);
|
||||||
|
EXPECT_EQ(rom_copy.size(), result.rom_size);
|
||||||
|
|
||||||
|
// Check that ROM was actually modified
|
||||||
|
EXPECT_NE(rom_copy, test_rom_); // Should be different after patching
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, InvalidPatchHandling) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(invalid_asm_path_.string(), rom_copy);
|
||||||
|
EXPECT_FALSE(patch_result.ok());
|
||||||
|
EXPECT_THAT(patch_result.status().message(),
|
||||||
|
testing::HasSubstr("Patch failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, NonexistentPatchFile) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
std::string nonexistent_path = test_dir_.string() + "/nonexistent.asm";
|
||||||
|
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(nonexistent_path, rom_copy);
|
||||||
|
EXPECT_FALSE(patch_result.ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, SymbolExtraction) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
auto symbols_result = wrapper_->ExtractSymbols(test_asm_path_.string());
|
||||||
|
ASSERT_TRUE(symbols_result.ok()) << symbols_result.status().message();
|
||||||
|
|
||||||
|
const auto& symbols = symbols_result.value();
|
||||||
|
EXPECT_GT(symbols.size(), 0);
|
||||||
|
|
||||||
|
// Check for expected symbols from our test assembly
|
||||||
|
bool found_testlabel = false;
|
||||||
|
bool found_anotherlabel = false;
|
||||||
|
|
||||||
|
for (const auto& symbol : symbols) {
|
||||||
|
EXPECT_FALSE(symbol.name.empty());
|
||||||
|
EXPECT_GT(symbol.address, 0);
|
||||||
|
|
||||||
|
if (symbol.name == "testlabel") {
|
||||||
|
found_testlabel = true;
|
||||||
|
EXPECT_EQ(symbol.address, 0x008000); // Expected address from org directive
|
||||||
|
} else if (symbol.name == "anotherlabel") {
|
||||||
|
found_anotherlabel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(found_testlabel) << "Expected 'testlabel' symbol not found";
|
||||||
|
EXPECT_TRUE(found_anotherlabel) << "Expected 'anotherlabel' symbol not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, SymbolTableOperations) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(test_asm_path_.string(), rom_copy);
|
||||||
|
ASSERT_TRUE(patch_result.ok());
|
||||||
|
|
||||||
|
// Test symbol table retrieval
|
||||||
|
auto symbol_table = wrapper_->GetSymbolTable();
|
||||||
|
EXPECT_GT(symbol_table.size(), 0);
|
||||||
|
|
||||||
|
// Test symbol lookup by name
|
||||||
|
auto testlabel_symbol = wrapper_->FindSymbol("testlabel");
|
||||||
|
EXPECT_TRUE(testlabel_symbol.has_value());
|
||||||
|
if (testlabel_symbol) {
|
||||||
|
EXPECT_EQ(testlabel_symbol->name, "testlabel");
|
||||||
|
EXPECT_GT(testlabel_symbol->address, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test lookup of non-existent symbol
|
||||||
|
auto nonexistent_symbol = wrapper_->FindSymbol("nonexistent_symbol");
|
||||||
|
EXPECT_FALSE(nonexistent_symbol.has_value());
|
||||||
|
|
||||||
|
// Test symbols at address lookup
|
||||||
|
if (testlabel_symbol) {
|
||||||
|
auto symbols_at_addr = wrapper_->GetSymbolsAtAddress(testlabel_symbol->address);
|
||||||
|
EXPECT_GT(symbols_at_addr.size(), 0);
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (const auto& symbol : symbols_at_addr) {
|
||||||
|
if (symbol.name == "testlabel") {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, PatchFromString) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
std::string patch_content = R"(
|
||||||
|
org $009000
|
||||||
|
stringpatchlabel:
|
||||||
|
LDA #$55
|
||||||
|
STA $7E0002
|
||||||
|
RTS
|
||||||
|
)";
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto patch_result = wrapper_->ApplyPatchFromString(
|
||||||
|
patch_content, rom_copy, test_dir_.string());
|
||||||
|
|
||||||
|
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||||
|
|
||||||
|
const auto& result = patch_result.value();
|
||||||
|
EXPECT_TRUE(result.success);
|
||||||
|
EXPECT_GT(result.symbols.size(), 0);
|
||||||
|
|
||||||
|
// Check for the symbol we defined
|
||||||
|
bool found_symbol = false;
|
||||||
|
for (const auto& symbol : result.symbols) {
|
||||||
|
if (symbol.name == "stringpatchlabel") {
|
||||||
|
found_symbol = true;
|
||||||
|
EXPECT_EQ(symbol.address, 0x009000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(found_symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, AssemblyValidation) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Test valid assembly
|
||||||
|
auto valid_status = wrapper_->ValidateAssembly(test_asm_path_.string());
|
||||||
|
EXPECT_TRUE(valid_status.ok()) << valid_status.message();
|
||||||
|
|
||||||
|
// Test invalid assembly
|
||||||
|
auto invalid_status = wrapper_->ValidateAssembly(invalid_asm_path_.string());
|
||||||
|
EXPECT_FALSE(invalid_status.ok());
|
||||||
|
EXPECT_THAT(invalid_status.message(),
|
||||||
|
testing::HasSubstr("validation failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, ResetFunctionality) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Apply a patch to generate some state
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(test_asm_path_.string(), rom_copy);
|
||||||
|
ASSERT_TRUE(patch_result.ok());
|
||||||
|
|
||||||
|
// Verify we have symbols and potentially warnings/errors
|
||||||
|
auto symbol_table_before = wrapper_->GetSymbolTable();
|
||||||
|
EXPECT_GT(symbol_table_before.size(), 0);
|
||||||
|
|
||||||
|
// Reset and verify state is cleared
|
||||||
|
wrapper_->Reset();
|
||||||
|
|
||||||
|
auto symbol_table_after = wrapper_->GetSymbolTable();
|
||||||
|
EXPECT_EQ(symbol_table_after.size(), 0);
|
||||||
|
|
||||||
|
auto errors = wrapper_->GetLastErrors();
|
||||||
|
auto warnings = wrapper_->GetLastWarnings();
|
||||||
|
EXPECT_EQ(errors.size(), 0);
|
||||||
|
EXPECT_EQ(warnings.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarWrapperTest, CreatePatchNotImplemented) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
std::vector<uint8_t> original_rom = test_rom_;
|
||||||
|
std::vector<uint8_t> modified_rom = test_rom_;
|
||||||
|
modified_rom[100] = 0x42; // Make a small change
|
||||||
|
|
||||||
|
std::string patch_path = test_dir_.string() + "/generated.asm";
|
||||||
|
auto status = wrapper_->CreatePatch(original_rom, modified_rom, patch_path);
|
||||||
|
|
||||||
|
EXPECT_FALSE(status.ok());
|
||||||
|
EXPECT_THAT(status.message(), testing::HasSubstr("not yet implemented"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "app/emu/memory/asm_parser.h"
|
#include "app/emu/memory/asm_parser.h"
|
||||||
#include "app/emu/memory/memory.h"
|
#include "app/emu/memory/memory.h"
|
||||||
#include "test/mocks/mock_memory.h"
|
#include "mocks/mock_memory.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
#include "test/mocks/mock_memory.h"
|
#include "mocks/mock_memory.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "test/testing.h"
|
#include "testing.h"
|
||||||
#include "yaze.h"
|
#include "yaze.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
|
|||||||
544
test/integration/asar_integration_test.cc
Normal file
544
test/integration/asar_integration_test.cc
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "app/core/asar_wrapper.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "testing.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace test {
|
||||||
|
namespace integration {
|
||||||
|
|
||||||
|
class AsarIntegrationTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
wrapper_ = std::make_unique<app::core::AsarWrapper>();
|
||||||
|
|
||||||
|
// Create test directory
|
||||||
|
test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_integration";
|
||||||
|
std::filesystem::create_directories(test_dir_);
|
||||||
|
|
||||||
|
CreateTestRom();
|
||||||
|
CreateTestAssemblyFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
try {
|
||||||
|
if (std::filesystem::exists(test_dir_)) {
|
||||||
|
std::filesystem::remove_all(test_dir_);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateTestRom() {
|
||||||
|
// Create a minimal SNES ROM structure
|
||||||
|
test_rom_.resize(1024 * 1024, 0); // 1MB ROM
|
||||||
|
|
||||||
|
// Add SNES header at 0x7FC0 (LoROM)
|
||||||
|
const uint32_t header_offset = 0x7FC0;
|
||||||
|
|
||||||
|
// ROM title (21 bytes)
|
||||||
|
std::string title = "YAZE TEST ROM ";
|
||||||
|
std::copy(title.begin(), title.end(), test_rom_.begin() + header_offset);
|
||||||
|
|
||||||
|
// Map mode (byte 21) - LoROM
|
||||||
|
test_rom_[header_offset + 21] = 0x20;
|
||||||
|
|
||||||
|
// Cartridge type (byte 22)
|
||||||
|
test_rom_[header_offset + 22] = 0x00;
|
||||||
|
|
||||||
|
// ROM size (byte 23) - 1MB
|
||||||
|
test_rom_[header_offset + 23] = 0x0A;
|
||||||
|
|
||||||
|
// SRAM size (byte 24)
|
||||||
|
test_rom_[header_offset + 24] = 0x00;
|
||||||
|
|
||||||
|
// Country code (byte 25)
|
||||||
|
test_rom_[header_offset + 25] = 0x01;
|
||||||
|
|
||||||
|
// Developer ID (byte 26)
|
||||||
|
test_rom_[header_offset + 26] = 0x00;
|
||||||
|
|
||||||
|
// Version (byte 27)
|
||||||
|
test_rom_[header_offset + 27] = 0x00;
|
||||||
|
|
||||||
|
// Calculate and set checksum complement and checksum
|
||||||
|
uint16_t checksum = 0;
|
||||||
|
for (size_t i = 0; i < test_rom_.size(); ++i) {
|
||||||
|
if (i != header_offset + 28 && i != header_offset + 29 &&
|
||||||
|
i != header_offset + 30 && i != header_offset + 31) {
|
||||||
|
checksum += test_rom_[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t checksum_complement = checksum ^ 0xFFFF;
|
||||||
|
test_rom_[header_offset + 28] = checksum_complement & 0xFF;
|
||||||
|
test_rom_[header_offset + 29] = (checksum_complement >> 8) & 0xFF;
|
||||||
|
test_rom_[header_offset + 30] = checksum & 0xFF;
|
||||||
|
test_rom_[header_offset + 31] = (checksum >> 8) & 0xFF;
|
||||||
|
|
||||||
|
// Add some code at the reset vector location
|
||||||
|
const uint32_t reset_vector_offset = 0x8000;
|
||||||
|
test_rom_[reset_vector_offset] = 0x18; // CLC
|
||||||
|
test_rom_[reset_vector_offset + 1] = 0xFB; // XCE
|
||||||
|
test_rom_[reset_vector_offset + 2] = 0x4C; // JMP abs
|
||||||
|
test_rom_[reset_vector_offset + 3] = 0x00; // $8000
|
||||||
|
test_rom_[reset_vector_offset + 4] = 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateTestAssemblyFiles() {
|
||||||
|
// Create comprehensive test assembly
|
||||||
|
comprehensive_asm_path_ = test_dir_ / "comprehensive_test.asm";
|
||||||
|
std::ofstream comp_file(comprehensive_asm_path_);
|
||||||
|
comp_file << R"(
|
||||||
|
; Comprehensive Asar test for Yaze integration
|
||||||
|
!addr = $7E0000
|
||||||
|
|
||||||
|
; Test basic assembly
|
||||||
|
org $008000
|
||||||
|
main_entry:
|
||||||
|
sei ; Disable interrupts
|
||||||
|
clc ; Clear carry
|
||||||
|
xce ; Switch to native mode
|
||||||
|
|
||||||
|
; Set up stack
|
||||||
|
rep #$30 ; 16-bit A and X/Y
|
||||||
|
ldx #$1FFF
|
||||||
|
txs
|
||||||
|
|
||||||
|
; Test data writing
|
||||||
|
lda #$1234
|
||||||
|
sta !addr
|
||||||
|
|
||||||
|
; Call subroutines
|
||||||
|
jsr init_graphics
|
||||||
|
jsr init_sound
|
||||||
|
|
||||||
|
; Main loop
|
||||||
|
main_loop:
|
||||||
|
jsr update_game
|
||||||
|
jsr wait_vblank
|
||||||
|
bra main_loop
|
||||||
|
|
||||||
|
; Graphics initialization
|
||||||
|
init_graphics:
|
||||||
|
pha
|
||||||
|
phx
|
||||||
|
phy
|
||||||
|
|
||||||
|
; Set up PPU registers
|
||||||
|
sep #$20 ; 8-bit A
|
||||||
|
lda #$80
|
||||||
|
sta $2100 ; Force blank
|
||||||
|
|
||||||
|
; Clear VRAM
|
||||||
|
rep #$20 ; 16-bit A
|
||||||
|
lda #$8000
|
||||||
|
sta $2116 ; VRAM address
|
||||||
|
|
||||||
|
lda #$0000
|
||||||
|
ldx #$8000
|
||||||
|
clear_vram_loop:
|
||||||
|
sta $2118 ; Write to VRAM
|
||||||
|
dex
|
||||||
|
bne clear_vram_loop
|
||||||
|
|
||||||
|
ply
|
||||||
|
plx
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Sound initialization
|
||||||
|
init_sound:
|
||||||
|
pha
|
||||||
|
|
||||||
|
; Initialize APU
|
||||||
|
sep #$20 ; 8-bit A
|
||||||
|
lda #$00
|
||||||
|
sta $2140 ; APU port 0
|
||||||
|
sta $2141 ; APU port 1
|
||||||
|
sta $2142 ; APU port 2
|
||||||
|
sta $2143 ; APU port 3
|
||||||
|
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Game update routine
|
||||||
|
update_game:
|
||||||
|
pha
|
||||||
|
|
||||||
|
; Read controller
|
||||||
|
lda $4212 ; PPU status
|
||||||
|
and #$01
|
||||||
|
bne update_game ; Wait for vblank end
|
||||||
|
|
||||||
|
lda $4016 ; Controller 1
|
||||||
|
; Process input here
|
||||||
|
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Wait for vertical blank
|
||||||
|
wait_vblank:
|
||||||
|
pha
|
||||||
|
wait_vb_loop:
|
||||||
|
lda $4212 ; PPU status
|
||||||
|
and #$80
|
||||||
|
beq wait_vb_loop ; Wait for vblank
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Data tables
|
||||||
|
org $00A000
|
||||||
|
graphics_data:
|
||||||
|
incbin "test_graphics.bin"
|
||||||
|
|
||||||
|
sound_data:
|
||||||
|
db $00, $01, $02, $03, $04, $05, $06, $07
|
||||||
|
db $08, $09, $0A, $0B, $0C, $0D, $0E, $0F
|
||||||
|
|
||||||
|
; String data for testing
|
||||||
|
text_data:
|
||||||
|
db "YAZE INTEGRATION TEST", $00
|
||||||
|
|
||||||
|
; Math functions
|
||||||
|
calculate_distance:
|
||||||
|
; Input: A = x1, X = y1, stack = x2, y2
|
||||||
|
; Output: A = distance
|
||||||
|
pha
|
||||||
|
phx
|
||||||
|
|
||||||
|
; Calculate dx = x2 - x1
|
||||||
|
pla ; Get x1
|
||||||
|
pha ; Save it back
|
||||||
|
; ... distance calculation here
|
||||||
|
|
||||||
|
plx
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Interrupt vectors
|
||||||
|
org $00FFE0
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw main_entry ; RESET vector
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; Reserved
|
||||||
|
dw $0000 ; NMI vector
|
||||||
|
dw main_entry ; RESET vector
|
||||||
|
dw $0000 ; IRQ vector
|
||||||
|
)";
|
||||||
|
comp_file.close();
|
||||||
|
|
||||||
|
// Create test graphics binary
|
||||||
|
std::ofstream gfx_file(test_dir_ / "test_graphics.bin", std::ios::binary);
|
||||||
|
std::vector<uint8_t> graphics_data(2048, 0x55); // Test pattern
|
||||||
|
gfx_file.write(reinterpret_cast<char*>(graphics_data.data()), graphics_data.size());
|
||||||
|
gfx_file.close();
|
||||||
|
|
||||||
|
// Create advanced assembly with macros and includes
|
||||||
|
advanced_asm_path_ = test_dir_ / "advanced_test.asm";
|
||||||
|
std::ofstream adv_file(advanced_asm_path_);
|
||||||
|
adv_file << R"(
|
||||||
|
; Advanced Asar features test
|
||||||
|
!ram_addr = $7E1000
|
||||||
|
|
||||||
|
; Macro definitions
|
||||||
|
macro move_block(source, dest, size)
|
||||||
|
rep #$30
|
||||||
|
ldx #<size>-1
|
||||||
|
loop:
|
||||||
|
lda <source>,x
|
||||||
|
sta <dest>,x
|
||||||
|
dex
|
||||||
|
bpl loop
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro set_ppu_register(register, value)
|
||||||
|
sep #$20
|
||||||
|
lda #<value>
|
||||||
|
sta <register>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
; Test code with macros
|
||||||
|
org $008000
|
||||||
|
advanced_entry:
|
||||||
|
%set_ppu_register($2100, $8F) ; Set forced blank
|
||||||
|
|
||||||
|
; Use block move macro
|
||||||
|
%move_block($008100, !ram_addr, 256)
|
||||||
|
|
||||||
|
; Conditional assembly
|
||||||
|
if !test_mode == 1
|
||||||
|
jsr debug_routine
|
||||||
|
endif
|
||||||
|
|
||||||
|
; Loop with labels
|
||||||
|
ldx #$10
|
||||||
|
test_loop:
|
||||||
|
lda test_data,x
|
||||||
|
sta !ram_addr,x
|
||||||
|
dex
|
||||||
|
bpl test_loop
|
||||||
|
|
||||||
|
rts
|
||||||
|
|
||||||
|
debug_routine:
|
||||||
|
; Debug code
|
||||||
|
rts
|
||||||
|
|
||||||
|
test_data:
|
||||||
|
db $FF, $FE, $FD, $FC, $FB, $FA, $F9, $F8
|
||||||
|
db $F7, $F6, $F5, $F4, $F3, $F2, $F1, $F0
|
||||||
|
)";
|
||||||
|
adv_file.close();
|
||||||
|
|
||||||
|
// Create error test assembly
|
||||||
|
error_asm_path_ = test_dir_ / "error_test.asm";
|
||||||
|
std::ofstream err_file(error_asm_path_);
|
||||||
|
err_file << R"(
|
||||||
|
; Assembly with intentional errors for testing error handling
|
||||||
|
org $008000
|
||||||
|
error_test:
|
||||||
|
invalid_opcode ; This should cause an error
|
||||||
|
lda unknown_label ; This should cause an error
|
||||||
|
sta $999999 ; Invalid address
|
||||||
|
bra ; Missing operand
|
||||||
|
)";
|
||||||
|
err_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<app::core::AsarWrapper> wrapper_;
|
||||||
|
std::filesystem::path test_dir_;
|
||||||
|
std::filesystem::path comprehensive_asm_path_;
|
||||||
|
std::filesystem::path advanced_asm_path_;
|
||||||
|
std::filesystem::path error_asm_path_;
|
||||||
|
std::vector<uint8_t> test_rom_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AsarIntegrationTest, FullWorkflowIntegration) {
|
||||||
|
// Initialize Asar
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Test ROM loading and patching workflow
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
size_t original_size = rom_copy.size();
|
||||||
|
|
||||||
|
// Apply comprehensive patch
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||||
|
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||||
|
|
||||||
|
const auto& result = patch_result.value();
|
||||||
|
EXPECT_TRUE(result.success) << "Patch failed with errors: "
|
||||||
|
<< testing::PrintToString(result.errors);
|
||||||
|
|
||||||
|
// Verify ROM was modified correctly
|
||||||
|
EXPECT_NE(rom_copy, test_rom_);
|
||||||
|
EXPECT_GT(result.rom_size, 0);
|
||||||
|
|
||||||
|
// Verify symbols were extracted
|
||||||
|
EXPECT_GT(result.symbols.size(), 0);
|
||||||
|
|
||||||
|
// Check for specific expected symbols
|
||||||
|
bool found_main_entry = false;
|
||||||
|
bool found_init_graphics = false;
|
||||||
|
bool found_init_sound = false;
|
||||||
|
|
||||||
|
for (const auto& symbol : result.symbols) {
|
||||||
|
if (symbol.name == "main_entry") {
|
||||||
|
found_main_entry = true;
|
||||||
|
EXPECT_EQ(symbol.address, 0x008000);
|
||||||
|
} else if (symbol.name == "init_graphics") {
|
||||||
|
found_init_graphics = true;
|
||||||
|
} else if (symbol.name == "init_sound") {
|
||||||
|
found_init_sound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(found_main_entry) << "main_entry symbol not found";
|
||||||
|
EXPECT_TRUE(found_init_graphics) << "init_graphics symbol not found";
|
||||||
|
EXPECT_TRUE(found_init_sound) << "init_sound symbol not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarIntegrationTest, AdvancedFeaturesIntegration) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Test advanced assembly features (macros, conditionals, etc.)
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(advanced_asm_path_.string(), rom_copy);
|
||||||
|
ASSERT_TRUE(patch_result.ok()) << patch_result.status().message();
|
||||||
|
|
||||||
|
const auto& result = patch_result.value();
|
||||||
|
EXPECT_TRUE(result.success) << "Advanced patch failed: "
|
||||||
|
<< testing::PrintToString(result.errors);
|
||||||
|
|
||||||
|
// Verify symbols from advanced assembly
|
||||||
|
bool found_advanced_entry = false;
|
||||||
|
bool found_test_loop = false;
|
||||||
|
|
||||||
|
for (const auto& symbol : result.symbols) {
|
||||||
|
if (symbol.name == "advanced_entry") {
|
||||||
|
found_advanced_entry = true;
|
||||||
|
} else if (symbol.name == "test_loop") {
|
||||||
|
found_test_loop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(found_advanced_entry);
|
||||||
|
EXPECT_TRUE(found_test_loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarIntegrationTest, ErrorHandlingIntegration) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Test error handling with intentionally broken assembly
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(error_asm_path_.string(), rom_copy);
|
||||||
|
|
||||||
|
// Should fail due to errors in assembly
|
||||||
|
EXPECT_FALSE(patch_result.ok());
|
||||||
|
|
||||||
|
// Verify error message contains useful information
|
||||||
|
EXPECT_THAT(patch_result.status().message(),
|
||||||
|
testing::AnyOf(
|
||||||
|
testing::HasSubstr("invalid"),
|
||||||
|
testing::HasSubstr("unknown"),
|
||||||
|
testing::HasSubstr("error")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarIntegrationTest, SymbolExtractionWorkflow) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Extract symbols without applying patch
|
||||||
|
auto symbols_result = wrapper_->ExtractSymbols(comprehensive_asm_path_.string());
|
||||||
|
ASSERT_TRUE(symbols_result.ok()) << symbols_result.status().message();
|
||||||
|
|
||||||
|
const auto& symbols = symbols_result.value();
|
||||||
|
EXPECT_GT(symbols.size(), 0);
|
||||||
|
|
||||||
|
// Test symbol table operations
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy);
|
||||||
|
ASSERT_TRUE(patch_result.ok());
|
||||||
|
|
||||||
|
// Test symbol lookup by name
|
||||||
|
auto main_symbol = wrapper_->FindSymbol("main_entry");
|
||||||
|
ASSERT_TRUE(main_symbol.has_value());
|
||||||
|
EXPECT_EQ(main_symbol->name, "main_entry");
|
||||||
|
EXPECT_EQ(main_symbol->address, 0x008000);
|
||||||
|
|
||||||
|
// Test symbols at address lookup
|
||||||
|
auto symbols_at_main = wrapper_->GetSymbolsAtAddress(0x008000);
|
||||||
|
EXPECT_GT(symbols_at_main.size(), 0);
|
||||||
|
|
||||||
|
bool found_main_at_address = false;
|
||||||
|
for (const auto& symbol : symbols_at_main) {
|
||||||
|
if (symbol.name == "main_entry") {
|
||||||
|
found_main_at_address = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(found_main_at_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarIntegrationTest, MultipleOperationsIntegration) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Test multiple patch operations on the same wrapper instance
|
||||||
|
std::vector<uint8_t> rom_copy1 = test_rom_;
|
||||||
|
std::vector<uint8_t> rom_copy2 = test_rom_;
|
||||||
|
|
||||||
|
// First patch
|
||||||
|
auto result1 = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), rom_copy1);
|
||||||
|
ASSERT_TRUE(result1.ok());
|
||||||
|
EXPECT_TRUE(result1->success);
|
||||||
|
|
||||||
|
// Reset and apply different patch
|
||||||
|
wrapper_->Reset();
|
||||||
|
auto result2 = wrapper_->ApplyPatch(advanced_asm_path_.string(), rom_copy2);
|
||||||
|
ASSERT_TRUE(result2.ok());
|
||||||
|
EXPECT_TRUE(result2->success);
|
||||||
|
|
||||||
|
// Verify that symbol tables are different
|
||||||
|
EXPECT_NE(result1->symbols.size(), result2->symbols.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarIntegrationTest, PatchFromStringIntegration) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
std::string patch_content = R"(
|
||||||
|
org $009000
|
||||||
|
string_patch_test:
|
||||||
|
lda #$42
|
||||||
|
sta $7E2000
|
||||||
|
jsr subroutine_test
|
||||||
|
rts
|
||||||
|
|
||||||
|
subroutine_test:
|
||||||
|
lda #$FF
|
||||||
|
sta $7E2001
|
||||||
|
rts
|
||||||
|
)";
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto result = wrapper_->ApplyPatchFromString(patch_content, rom_copy, test_dir_.string());
|
||||||
|
|
||||||
|
ASSERT_TRUE(result.ok()) << result.status().message();
|
||||||
|
EXPECT_TRUE(result->success);
|
||||||
|
EXPECT_GT(result->symbols.size(), 0);
|
||||||
|
|
||||||
|
// Check for expected symbols
|
||||||
|
bool found_string_patch = false;
|
||||||
|
bool found_subroutine = false;
|
||||||
|
|
||||||
|
for (const auto& symbol : result->symbols) {
|
||||||
|
if (symbol.name == "string_patch_test") {
|
||||||
|
found_string_patch = true;
|
||||||
|
EXPECT_EQ(symbol.address, 0x009000);
|
||||||
|
} else if (symbol.name == "subroutine_test") {
|
||||||
|
found_subroutine = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(found_string_patch);
|
||||||
|
EXPECT_TRUE(found_subroutine);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarIntegrationTest, LargeRomHandling) {
|
||||||
|
ASSERT_TRUE(wrapper_->Initialize().ok());
|
||||||
|
|
||||||
|
// Create a larger ROM for testing
|
||||||
|
std::vector<uint8_t> large_rom(4 * 1024 * 1024, 0); // 4MB ROM
|
||||||
|
|
||||||
|
// Set up basic SNES header
|
||||||
|
const uint32_t header_offset = 0x7FC0;
|
||||||
|
std::string title = "LARGE ROM TEST ";
|
||||||
|
std::copy(title.begin(), title.end(), large_rom.begin() + header_offset);
|
||||||
|
large_rom[header_offset + 21] = 0x20; // LoROM
|
||||||
|
large_rom[header_offset + 23] = 0x0C; // 4MB
|
||||||
|
|
||||||
|
auto result = wrapper_->ApplyPatch(comprehensive_asm_path_.string(), large_rom);
|
||||||
|
ASSERT_TRUE(result.ok());
|
||||||
|
EXPECT_TRUE(result->success);
|
||||||
|
EXPECT_EQ(large_rom.size(), result->rom_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace integration
|
||||||
|
} // namespace test
|
||||||
|
} // namespace yaze
|
||||||
412
test/integration/asar_rom_test.cc
Normal file
412
test/integration/asar_rom_test.cc
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "app/core/asar_wrapper.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
#include "testing.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace test {
|
||||||
|
namespace integration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test class for Asar integration with real ROM files
|
||||||
|
* These tests are only run when ROM testing is enabled
|
||||||
|
*/
|
||||||
|
class AsarRomIntegrationTest : public RomDependentTest {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
RomDependentTest::SetUp();
|
||||||
|
|
||||||
|
wrapper_ = std::make_unique<app::core::AsarWrapper>();
|
||||||
|
ASSERT_OK(wrapper_->Initialize());
|
||||||
|
|
||||||
|
// Create test directory
|
||||||
|
test_dir_ = std::filesystem::temp_directory_path() / "yaze_asar_rom_test";
|
||||||
|
std::filesystem::create_directories(test_dir_);
|
||||||
|
|
||||||
|
CreateTestPatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
try {
|
||||||
|
if (std::filesystem::exists(test_dir_)) {
|
||||||
|
std::filesystem::remove_all(test_dir_);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateTestPatches() {
|
||||||
|
// Create a simple test patch
|
||||||
|
simple_patch_path_ = test_dir_ / "simple_test.asm";
|
||||||
|
std::ofstream simple_file(simple_patch_path_);
|
||||||
|
simple_file << R"(
|
||||||
|
; Simple Asar patch for real ROM testing
|
||||||
|
org $008000
|
||||||
|
yaze_test_entry:
|
||||||
|
sei ; Disable interrupts
|
||||||
|
clc ; Clear carry
|
||||||
|
xce ; Switch to native mode
|
||||||
|
|
||||||
|
rep #$30 ; 16-bit A and X/Y
|
||||||
|
ldx #$1FFF
|
||||||
|
txs ; Set stack pointer
|
||||||
|
|
||||||
|
; Test data writing
|
||||||
|
lda #$CAFE
|
||||||
|
sta $7E0000 ; Write test value to RAM
|
||||||
|
|
||||||
|
; Set a custom value that we can verify
|
||||||
|
lda #$BEEF
|
||||||
|
sta $7E0002
|
||||||
|
|
||||||
|
sep #$20 ; 8-bit A
|
||||||
|
lda #$42
|
||||||
|
sta $7E0004 ; Another test value
|
||||||
|
|
||||||
|
rep #$20 ; Back to 16-bit A
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Subroutine for testing
|
||||||
|
yaze_test_subroutine:
|
||||||
|
pha
|
||||||
|
lda #$1337
|
||||||
|
sta $7E0010
|
||||||
|
pla
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Data for testing
|
||||||
|
yaze_test_data:
|
||||||
|
db "YAZE", $00
|
||||||
|
dw $1234, $5678, $9ABC, $DEF0
|
||||||
|
)";
|
||||||
|
simple_file.close();
|
||||||
|
|
||||||
|
// Create a patch that modifies game behavior
|
||||||
|
gameplay_patch_path_ = test_dir_ / "gameplay_test.asm";
|
||||||
|
std::ofstream gameplay_file(gameplay_patch_path_);
|
||||||
|
gameplay_file << R"(
|
||||||
|
; Gameplay modification patch for testing
|
||||||
|
; This modifies Link's starting health and magic
|
||||||
|
|
||||||
|
; Increase Link's maximum health
|
||||||
|
org $7EF36C
|
||||||
|
db $A0 ; 160/8 = 20 hearts (was usually $60 = 12 hearts)
|
||||||
|
|
||||||
|
; Increase Link's maximum magic
|
||||||
|
org $7EF36E
|
||||||
|
db $80 ; Full magic meter
|
||||||
|
|
||||||
|
; Custom routine for health restoration
|
||||||
|
org $00C000
|
||||||
|
yaze_health_restore:
|
||||||
|
sep #$20 ; 8-bit A
|
||||||
|
lda #$A0 ; Full health
|
||||||
|
sta $7EF36C ; Current health
|
||||||
|
|
||||||
|
lda #$80 ; Full magic
|
||||||
|
sta $7EF36E ; Current magic
|
||||||
|
|
||||||
|
rep #$20 ; 16-bit A
|
||||||
|
rtl
|
||||||
|
|
||||||
|
; Hook into the game's main loop (example address)
|
||||||
|
org $008012
|
||||||
|
jsl yaze_health_restore
|
||||||
|
nop ; Pad if needed
|
||||||
|
)";
|
||||||
|
gameplay_file.close();
|
||||||
|
|
||||||
|
// Create a symbol extraction test patch
|
||||||
|
symbols_patch_path_ = test_dir_ / "symbols_test.asm";
|
||||||
|
std::ofstream symbols_file(symbols_patch_path_);
|
||||||
|
symbols_file << R"(
|
||||||
|
; Comprehensive symbol test for Asar integration
|
||||||
|
|
||||||
|
; Define some constants
|
||||||
|
!player_x = $7E0020
|
||||||
|
!player_y = $7E0022
|
||||||
|
!player_health = $7EF36C
|
||||||
|
!player_magic = $7EF36E
|
||||||
|
|
||||||
|
; Main code section
|
||||||
|
org $008000
|
||||||
|
main_routine:
|
||||||
|
jsr init_player
|
||||||
|
jsr game_loop
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Player initialization
|
||||||
|
init_player:
|
||||||
|
rep #$30 ; 16-bit A and X/Y
|
||||||
|
|
||||||
|
; Set initial position
|
||||||
|
lda #$0080
|
||||||
|
sta !player_x
|
||||||
|
lda #$0070
|
||||||
|
sta !player_y
|
||||||
|
|
||||||
|
; Set initial stats
|
||||||
|
sep #$20 ; 8-bit A
|
||||||
|
lda #$A0
|
||||||
|
sta !player_health
|
||||||
|
lda #$80
|
||||||
|
sta !player_magic
|
||||||
|
|
||||||
|
rep #$30 ; Back to 16-bit
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Main game loop
|
||||||
|
game_loop:
|
||||||
|
jsr update_player
|
||||||
|
jsr update_enemies
|
||||||
|
jsr update_graphics
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Player update routine
|
||||||
|
update_player:
|
||||||
|
; Read controller input
|
||||||
|
sep #$20
|
||||||
|
lda $4016 ; Controller 1
|
||||||
|
|
||||||
|
; Process movement
|
||||||
|
bit #$08 ; Up
|
||||||
|
beq +
|
||||||
|
dec !player_y
|
||||||
|
+ bit #$04 ; Down
|
||||||
|
beq +
|
||||||
|
inc !player_y
|
||||||
|
+ bit #$02 ; Left
|
||||||
|
beq +
|
||||||
|
dec !player_x
|
||||||
|
+ bit #$01 ; Right
|
||||||
|
beq +
|
||||||
|
inc !player_x
|
||||||
|
+
|
||||||
|
rep #$20
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Enemy update routine
|
||||||
|
update_enemies:
|
||||||
|
; Placeholder for enemy logic
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Graphics update routine
|
||||||
|
update_graphics:
|
||||||
|
; Placeholder for graphics updates
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Utility functions
|
||||||
|
multiply_by_two:
|
||||||
|
asl a
|
||||||
|
rts
|
||||||
|
|
||||||
|
divide_by_two:
|
||||||
|
lsr a
|
||||||
|
rts
|
||||||
|
|
||||||
|
; Data tables
|
||||||
|
enemy_data_table:
|
||||||
|
dw enemy_goomba, enemy_koopa, enemy_shell
|
||||||
|
dw $0000 ; End marker
|
||||||
|
|
||||||
|
enemy_goomba:
|
||||||
|
dw $0010, $0020, $0001 ; x, y, type
|
||||||
|
|
||||||
|
enemy_koopa:
|
||||||
|
dw $0050, $0030, $0002 ; x, y, type
|
||||||
|
|
||||||
|
enemy_shell:
|
||||||
|
dw $0080, $0040, $0003 ; x, y, type
|
||||||
|
)";
|
||||||
|
symbols_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<app::core::AsarWrapper> wrapper_;
|
||||||
|
std::filesystem::path test_dir_;
|
||||||
|
std::filesystem::path simple_patch_path_;
|
||||||
|
std::filesystem::path gameplay_patch_path_;
|
||||||
|
std::filesystem::path symbols_patch_path_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AsarRomIntegrationTest, SimplePatchOnRealRom) {
|
||||||
|
// Make a copy of the ROM for testing
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
size_t original_size = rom_copy.size();
|
||||||
|
|
||||||
|
// Apply simple patch
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
|
||||||
|
ASSERT_OK(patch_result.status());
|
||||||
|
|
||||||
|
const auto& result = patch_result.value();
|
||||||
|
EXPECT_TRUE(result.success) << "Patch failed: "
|
||||||
|
<< testing::PrintToString(result.errors);
|
||||||
|
|
||||||
|
// Verify ROM was modified
|
||||||
|
EXPECT_NE(rom_copy, test_rom_); // Should be different
|
||||||
|
EXPECT_GE(rom_copy.size(), original_size); // Size may have grown
|
||||||
|
|
||||||
|
// Check for expected symbols
|
||||||
|
bool found_entry = false;
|
||||||
|
bool found_subroutine = false;
|
||||||
|
|
||||||
|
for (const auto& symbol : result.symbols) {
|
||||||
|
if (symbol.name == "yaze_test_entry") {
|
||||||
|
found_entry = true;
|
||||||
|
EXPECT_EQ(symbol.address, 0x008000);
|
||||||
|
} else if (symbol.name == "yaze_test_subroutine") {
|
||||||
|
found_subroutine = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(found_entry) << "yaze_test_entry symbol not found";
|
||||||
|
EXPECT_TRUE(found_subroutine) << "yaze_test_subroutine symbol not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarRomIntegrationTest, SymbolExtractionFromRealRom) {
|
||||||
|
// Extract symbols from comprehensive test
|
||||||
|
auto symbols_result = wrapper_->ExtractSymbols(symbols_patch_path_.string());
|
||||||
|
ASSERT_OK(symbols_result.status());
|
||||||
|
|
||||||
|
const auto& symbols = symbols_result.value();
|
||||||
|
EXPECT_GT(symbols.size(), 0);
|
||||||
|
|
||||||
|
// Check for specific symbols we expect
|
||||||
|
std::vector<std::string> expected_symbols = {
|
||||||
|
"main_routine", "init_player", "game_loop", "update_player",
|
||||||
|
"update_enemies", "update_graphics", "multiply_by_two", "divide_by_two"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& expected_symbol : expected_symbols) {
|
||||||
|
bool found = false;
|
||||||
|
for (const auto& symbol : symbols) {
|
||||||
|
if (symbol.name == expected_symbol) {
|
||||||
|
found = true;
|
||||||
|
EXPECT_GT(symbol.address, 0) << "Symbol " << expected_symbol
|
||||||
|
<< " has invalid address";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(found) << "Expected symbol not found: " << expected_symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test symbol lookup functionality
|
||||||
|
auto symbol_table = wrapper_->GetSymbolTable();
|
||||||
|
EXPECT_GT(symbol_table.size(), 0);
|
||||||
|
|
||||||
|
auto main_symbol = wrapper_->FindSymbol("main_routine");
|
||||||
|
EXPECT_TRUE(main_symbol.has_value());
|
||||||
|
if (main_symbol) {
|
||||||
|
EXPECT_EQ(main_symbol->name, "main_routine");
|
||||||
|
EXPECT_EQ(main_symbol->address, 0x008000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarRomIntegrationTest, GameplayModificationPatch) {
|
||||||
|
// Make a copy of the ROM
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
|
||||||
|
// Apply gameplay modification patch
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(gameplay_patch_path_.string(), rom_copy);
|
||||||
|
ASSERT_OK(patch_result.status());
|
||||||
|
|
||||||
|
const auto& result = patch_result.value();
|
||||||
|
EXPECT_TRUE(result.success) << "Gameplay patch failed: "
|
||||||
|
<< testing::PrintToString(result.errors);
|
||||||
|
|
||||||
|
// Verify specific memory locations were modified
|
||||||
|
// Note: These addresses are based on the patch content
|
||||||
|
|
||||||
|
// Check health modification at 0x7EF36C -> ROM offset would need calculation
|
||||||
|
// For a proper test, we'd need to convert SNES addresses to ROM offsets
|
||||||
|
|
||||||
|
// Check if custom routine was inserted at 0xC000 -> ROM offset 0x18000 (in LoROM)
|
||||||
|
const uint32_t rom_offset = 0x18000; // Bank $00:C000 in LoROM
|
||||||
|
if (rom_offset < rom_copy.size()) {
|
||||||
|
// Check for SEP #$20 instruction (0xE2 0x20)
|
||||||
|
EXPECT_EQ(rom_copy[rom_offset], 0xE2);
|
||||||
|
EXPECT_EQ(rom_copy[rom_offset + 1], 0x20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarRomIntegrationTest, LargeRomPatchingStability) {
|
||||||
|
// Test with the actual ROM which might be larger
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
size_t original_size = rom_copy.size();
|
||||||
|
|
||||||
|
// Apply multiple patches in sequence
|
||||||
|
auto result1 = wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
|
||||||
|
ASSERT_OK(result1.status());
|
||||||
|
EXPECT_TRUE(result1->success);
|
||||||
|
|
||||||
|
// Reset and apply another patch
|
||||||
|
wrapper_->Reset();
|
||||||
|
auto result2 = wrapper_->ApplyPatch(symbols_patch_path_.string(), rom_copy);
|
||||||
|
ASSERT_OK(result2.status());
|
||||||
|
EXPECT_TRUE(result2->success);
|
||||||
|
|
||||||
|
// Verify stability
|
||||||
|
EXPECT_GE(rom_copy.size(), original_size);
|
||||||
|
EXPECT_GT(result2->symbols.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarRomIntegrationTest, ErrorHandlingWithRealRom) {
|
||||||
|
// Create an intentionally broken patch
|
||||||
|
auto broken_patch_path = test_dir_ / "broken_test.asm";
|
||||||
|
std::ofstream broken_file(broken_patch_path);
|
||||||
|
broken_file << R"(
|
||||||
|
; Broken patch for error testing
|
||||||
|
org $008000
|
||||||
|
broken_routine:
|
||||||
|
invalid_opcode ; This will cause an error
|
||||||
|
lda unknown_symbol ; This will cause an error
|
||||||
|
sta $FFFFFF ; Invalid address
|
||||||
|
)";
|
||||||
|
broken_file.close();
|
||||||
|
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(broken_patch_path.string(), rom_copy);
|
||||||
|
|
||||||
|
// Should fail with proper error messages
|
||||||
|
EXPECT_FALSE(patch_result.ok());
|
||||||
|
EXPECT_THAT(patch_result.status().message(),
|
||||||
|
testing::AnyOf(
|
||||||
|
testing::HasSubstr("invalid"),
|
||||||
|
testing::HasSubstr("unknown"),
|
||||||
|
testing::HasSubstr("error")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AsarRomIntegrationTest, PatchValidationWorkflow) {
|
||||||
|
// Test the complete workflow: validate -> patch -> verify
|
||||||
|
|
||||||
|
// Step 1: Validate assembly
|
||||||
|
auto validation_result = wrapper_->ValidateAssembly(simple_patch_path_.string());
|
||||||
|
EXPECT_OK(validation_result);
|
||||||
|
|
||||||
|
// Step 2: Apply patch
|
||||||
|
std::vector<uint8_t> rom_copy = test_rom_;
|
||||||
|
auto patch_result = wrapper_->ApplyPatch(simple_patch_path_.string(), rom_copy);
|
||||||
|
ASSERT_OK(patch_result.status());
|
||||||
|
EXPECT_TRUE(patch_result->success);
|
||||||
|
|
||||||
|
// Step 3: Verify results
|
||||||
|
EXPECT_GT(patch_result->symbols.size(), 0);
|
||||||
|
EXPECT_GT(patch_result->rom_size, 0);
|
||||||
|
|
||||||
|
// Step 4: Test symbol operations
|
||||||
|
auto entry_symbol = wrapper_->FindSymbol("yaze_test_entry");
|
||||||
|
EXPECT_TRUE(entry_symbol.has_value());
|
||||||
|
|
||||||
|
if (entry_symbol) {
|
||||||
|
auto symbols_at_address = wrapper_->GetSymbolsAtAddress(entry_symbol->address);
|
||||||
|
EXPECT_GT(symbols_at_address.size(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace integration
|
||||||
|
} // namespace test
|
||||||
|
} // namespace yaze
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "test/integration/dungeon_editor_test.h"
|
#include "integration/dungeon_editor_test.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "test/testing.h"
|
#include "testing.h"
|
||||||
|
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
#include "mocks/mock_rom.h"
|
#include "mocks/mock_rom.h"
|
||||||
#include "test/testing.h"
|
#include "testing.h"
|
||||||
#include "app/transaction.h"
|
#include "app/transaction.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "test/test_editor.h"
|
#include "test_editor.h"
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
|
|||||||
156
test/test_utils.h
Normal file
156
test/test_utils.h
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#ifndef YAZE_TEST_TEST_UTILS_H
|
||||||
|
#define YAZE_TEST_TEST_UTILS_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class for handling test ROM files
|
||||||
|
*/
|
||||||
|
class TestRomManager {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Check if ROM testing is enabled and ROM file exists
|
||||||
|
* @return True if ROM tests can be run
|
||||||
|
*/
|
||||||
|
static bool IsRomTestingEnabled() {
|
||||||
|
#ifdef YAZE_ENABLE_ROM_TESTS
|
||||||
|
return std::filesystem::exists(GetTestRomPath());
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the path to the test ROM file
|
||||||
|
* @return Path to the test ROM
|
||||||
|
*/
|
||||||
|
static std::string GetTestRomPath() {
|
||||||
|
#ifdef YAZE_TEST_ROM_PATH
|
||||||
|
return YAZE_TEST_ROM_PATH;
|
||||||
|
#else
|
||||||
|
return "zelda3.sfc";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load the test ROM file into memory
|
||||||
|
* @return Vector containing ROM data, or empty if failed
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> LoadTestRom() {
|
||||||
|
if (!IsRomTestingEnabled()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string rom_path = GetTestRomPath();
|
||||||
|
std::ifstream file(rom_path, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
std::cerr << "Failed to open test ROM: " << rom_path << std::endl;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file size
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
size_t file_size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
// Read file
|
||||||
|
std::vector<uint8_t> rom_data(file_size);
|
||||||
|
file.read(reinterpret_cast<char*>(rom_data.data()), file_size);
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
std::cerr << "Failed to read test ROM: " << rom_path << std::endl;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return rom_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a minimal test ROM for unit testing
|
||||||
|
* @param size Size of the ROM in bytes
|
||||||
|
* @return Vector containing minimal ROM data
|
||||||
|
*/
|
||||||
|
static std::vector<uint8_t> CreateMinimalTestRom(size_t size = 1024 * 1024) {
|
||||||
|
std::vector<uint8_t> rom_data(size, 0);
|
||||||
|
|
||||||
|
// Add minimal SNES header at 0x7FC0 (LoROM)
|
||||||
|
const size_t header_offset = 0x7FC0;
|
||||||
|
if (size > header_offset + 32) {
|
||||||
|
// ROM title
|
||||||
|
std::string title = "YAZE TEST ROM ";
|
||||||
|
std::copy(title.begin(), title.end(), rom_data.begin() + header_offset);
|
||||||
|
|
||||||
|
// Map mode (LoROM)
|
||||||
|
rom_data[header_offset + 21] = 0x20;
|
||||||
|
|
||||||
|
// ROM size (1MB)
|
||||||
|
rom_data[header_offset + 23] = 0x0A;
|
||||||
|
|
||||||
|
// Calculate and set checksum
|
||||||
|
uint16_t checksum = 0;
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
if (i < header_offset + 28 || i > header_offset + 31) {
|
||||||
|
checksum += rom_data[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t checksum_complement = checksum ^ 0xFFFF;
|
||||||
|
rom_data[header_offset + 28] = checksum_complement & 0xFF;
|
||||||
|
rom_data[header_offset + 29] = (checksum_complement >> 8) & 0xFF;
|
||||||
|
rom_data[header_offset + 30] = checksum & 0xFF;
|
||||||
|
rom_data[header_offset + 31] = (checksum >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rom_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Skip test if ROM testing is not enabled
|
||||||
|
* @param test_name Name of the test for logging
|
||||||
|
*/
|
||||||
|
static void SkipIfRomTestingDisabled(const std::string& test_name) {
|
||||||
|
if (!IsRomTestingEnabled()) {
|
||||||
|
GTEST_SKIP() << "ROM testing disabled or ROM file not found. "
|
||||||
|
<< "Test: " << test_name << " requires: " << GetTestRomPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test macro for ROM-dependent tests
|
||||||
|
*/
|
||||||
|
#define YAZE_ROM_TEST(test_case_name, test_name) \
|
||||||
|
TEST(test_case_name, test_name) { \
|
||||||
|
yaze::test::TestRomManager::SkipIfRomTestingDisabled(#test_case_name "." #test_name); \
|
||||||
|
YAZE_ROM_TEST_BODY_##test_case_name##_##test_name(); \
|
||||||
|
} \
|
||||||
|
void YAZE_ROM_TEST_BODY_##test_case_name##_##test_name()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test fixture for ROM-dependent tests
|
||||||
|
*/
|
||||||
|
class RomDependentTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
TestRomManager::SkipIfRomTestingDisabled("RomDependentTest");
|
||||||
|
test_rom_ = TestRomManager::LoadTestRom();
|
||||||
|
ASSERT_FALSE(test_rom_.empty()) << "Failed to load test ROM";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> test_rom_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_TEST_TEST_UTILS_H
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "absl/debugging/failure_signal_handler.h"
|
#include "absl/debugging/failure_signal_handler.h"
|
||||||
#include "absl/debugging/symbolize.h"
|
#include "absl/debugging/symbolize.h"
|
||||||
#include "test/test_editor.h"
|
#include "test_editor.h"
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
absl::InitializeSymbolizer(argv[0]);
|
absl::InitializeSymbolizer(argv[0]);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "test/testing.h"
|
#include "testing.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "app/editor/message/message_data.h"
|
#include "app/editor/message/message_data.h"
|
||||||
#include "app/editor/message/message_editor.h"
|
#include "app/editor/message/message_editor.h"
|
||||||
#include "test/testing.h"
|
#include "testing.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "test/mocks/mock_rom.h"
|
#include "mocks/mock_rom.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#include "test_dungeon_objects.h"
|
#include "test_dungeon_objects.h"
|
||||||
#include "test/mocks/mock_rom.h"
|
#include "mocks/mock_rom.h"
|
||||||
#include "app/zelda3/dungeon/object_parser.h"
|
#include "app/zelda3/dungeon/object_parser.h"
|
||||||
#include "app/zelda3/dungeon/object_renderer.h"
|
#include "app/zelda3/dungeon/object_renderer.h"
|
||||||
#include "app/zelda3/dungeon/room_object.h"
|
#include "app/zelda3/dungeon/room_object.h"
|
||||||
#include "app/zelda3/dungeon/room_layout.h"
|
#include "app/zelda3/dungeon/room_layout.h"
|
||||||
#include "app/gfx/snes_color.h"
|
#include "app/gfx/snes_color.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "test/testing.h"
|
#include "testing.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "test/mocks/mock_rom.h"
|
#include "mocks/mock_rom.h"
|
||||||
#include "test/testing.h"
|
#include "testing.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|||||||
Reference in New Issue
Block a user